본문 바로가기

Spring/Spring Boot

[Spring Boot for Web Development] 강의 내용 정리

Spring Boot 애플리케이션 개발 및 실행 순서

  • Spring initializr 를 활용한 Spring Boot 프로젝트 생성 및 다운로드
  • 다운로드 한 Spring Boot 프로젝트를 IntelliJ에서 Import
  • 추가 코드 개발
  • Spring Boot 애플리케이션 실행
  • 웹 브라우저 또는 Postman을 활용하여 테스트

 

Spring Initializr

 

IP& PORT

IP

서버의 주소

request를 요청하는 주소

인터넷에 연결된 기기가 가질 수 있는 네트워크 상의 주소

IP 주소 정보로 원격에 있는 자원 (웹 페이지, 이미지 등)을 요청 가능

 

IPv4 vs. IPv6

더 많은 기기에 IP 주소를 줄 수 있음

 

공인 IP

공인 IP 주소는 인터넷 상에 고유한 IP 주소

공인 IP 주소는 IP 주소를 관리하는 기관으로부터 할당 받아 사용 가능

대륙별, 국가별 할당 받은 공인 IP 주소가 존재

누구나 접속 가능해야 하는 웹 서버 등은 공인 IP 주소가 필요

 

사설 IP

특정 조직 내부에의 사설 네트워크 안에서만 통신 가능한 IP 주소

(기관, 회사, 가정 등)

사설 네트워크 내부에서는 사설 IP로 통신이 가능하나, 외부에서는 사설 IP로 기기에 접근 불가능

회사의 동일한 공유기에 연결된 컴퓨터 간에는 사설 IP로 통신 가능

외부에서는 그 회사 컴퓨터의 사설 IP로 통신 불가

사설 네트워크 간에는 사설 IP가 중복되어도 무방

회사 A의 사설 IP 192.0.0.1과 회사 B의 사설 IP 192.0.0.1로 중복되어도 가능

회사 내부에서만 사용하기 때문

(cf. 공인 IP는 전 세계에서 unique 해야 함)

 

호스트명

긴 숫자 형태의 IP주소는 외워서 활용하기 어려움

따라서, 어려운 IP 대신 사람이 읽고 외우기 쉬운 형태의 주소 필요

호스트명

호스트는 인터넷 상에 IP 주소를 가진 기기 의미

호스트명으로 기기의 IP 주소를 찾을 수 있음

(ex. www.naver.com)

 

cf. 호스트명 vs. 도메인명

비슷하지만 엄밀히 말하면 다름

호스트명이 하위 개념

 

localhost

현재 작업을 수행 중인 기기를 지칭하는 특수한 호스트명

IP 주소로는 127.0.0.1로 표현

웹 서버를 원격이 아닌 내 컴퓨터에서 실행했을 경우

(localhost or 127.0.0.1로 연결)

 

Port

IP 주소가 특정 호스트(기기)까지의 주소라면, Port는 호스트 내부 프로세스의 네트워크 주소

호스트에서 0 ~ 65,535 까지 사용 가능

잘 알려진 포트는 정해져 있음

SSH는 22, HTTP는 80 등

호스트 내부에서 Port 주소는 유일해야 함

 

ex. 30.129.75.143:80

30.129.75.143 : IP 주소 - 호스트까지 주소

80 : Port 정보 - 호스트 내부의 프로세스 네트워크 주소

 

계층형 아키텍처의 이해

SW 아키텍처

SW의 구조를 정의한 것으로 SW를 구성하는 주요 요소들과 요소들의 관계를 정의한 것

건축을 할 때 설계도를 그려서 건물이 어떻게 지어질지 구상

 

디자인 패턴

패턴 : 되풀이되는 사건이나 물체의 형태로, 예측 가능한 방식으로 되풀이 됨

 

특정 상황의 문제를 해결하기 위한 일반화 된 솔루션

  •  카카오톡과 같은 메신저 서비스 개발을 위한 패턴
  • 웹 서비스 개발을 위한 패턴
  • 데이터 분석 시스템 개발을 위한 패턴

 

계층형 아키텍처 패턴

웹 서비스 개발에 주로 사용되는 패턴

SW를 서로 다른 역할을 하는 3 ~ 4개의 계층으로 구분

(Presentation, Application, Business, Data Access Layer)

계층 간에 호출을 하고 데이터를 주고 받으며 협력하며 전체 웹 서비스 구성

 

Client

Presentation Layer : @Controller

Application Layer : @Service

Data Access Layer : @Repository

DB

 

각각의 역할별로 독립적으로 개발

왜 이렇게 하는지 이해하고 설명할 수 있어야 함.

 

Spring Controller

계층형 아키텍처의 Presentation Layer에 해당

Client (웹 브라우저, 모바일 앱)의 요청을 받고 

Application Layer에 요청에 대한 처리 위임

Client에 최종 응답을 하는 역할 > 요청과 응답 처리의 역할 담당

  • view : Client가 요청에 대한 응답의 결과로 보게 되는 웹 페이지
  • data : Client가 요청에 대한 응답으로 받는 데이터

 

Controller 코드

3개의 기본 Annotation 사용됨

 

@Controller, @RestController 

  • Controller 역할 하는 클래스 지정
  • 클래스 상단에 명시
    • cf. @Controller vs. @RestController
    • @RestController에서는 json 데이터만 관리하고, controller에서는 view만 관리하게 됨
    • 과거에는 Spring에서 html이나 jsp 등과 같은 화면 렌더링까지 담당했었음. 하지만 이제는 백엔드와 프론트엔드를 완전히 분리하고, 백엔드에서는 API를 통해 json 포맷의 데이터 만을 반환하기 때문에 이를 보다 간편하게 개발하고자 @RestController가 등장.
    • Controller : view를 응답 (html 파일 등)
    • RestController : data를 응답 (문자열, Json, xml 등)

@RequestMapping

  • 특정 Request를 처리하는 메소드를 지정, 클래스 또는 메소드 상단에 명시

 

@Controller
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }
    
    @GetMapping(value = "/users/detailView")
    public String detailView(Model model, @RequestParam("userName") String userName){
        User user = userService.findUser(userName);
        model.addAttribute("user", user);
        return "/users/detailView";
    }
}

 

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public User findUser(@RequestParam("userName") String userName){
        return userService.findUser(user);
    }

    @GetMapping(value = "/users")
    public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }
}

 

 

Annotation

Java 소스코드에 추가적인 정보를 제공하는 방법

@로 시작, 클래스, 메소드, 멤버변수, 파라미터 등에 부착 가능

3가지 유형의 Annotation

  • 자바 컴파일러에게 정보를 제공 (에러 등을 찾아내기 위해) 
  • SW 툴에 의해 사용되어 코드 생성이나 추가 작업 진행 ex. @Controller, @RestController, @RequestMapping
  •  run time 시 특정 동작을 추가적으로 실행

 

URI와 Spring RequestMapping

Spring Controller의 메소드들은 URI에 따라 호출이 결정됨

기본 도메인에 이어지는 '/path'들에 의해 결정됨

ex. http://localhost:8080/user

Request Mapping Annotation에 URI Path 정보를 명시

ex. @RequestMapping(value = "/user")

 

URI는 계층 관계로 표현 가능 / 로 계층 구분 > API를 설계한다고도 표현함

ex.

www.codepresso-blog.com/user/paid  

www.codepresso-blog.com/user/enterprise  

www.codepresso-blog.com/user/enterprise/google  

 

네이밍 규약

  • 소문자를 사용
  • 요청하는 자원에 대한 명사 형태로 작성
  • 두 단어 이상 연결될 경우 '-'를 사용
  • 의미 있는 이름으로 일관성 있게 작성

 

Controller 클래스에 @RequestMapping 사용 가능

특정 Controller 클래스 내부의 모든 메소드에 Path를 적용 가능

 

API (Application Programming Interface)

Interface란 두 개체 간의 정보를 공유하기 위한 방법 (규약)

API는 컴퓨터(프로그램) 간의 정보를 공유하기 위한 방법

  • 함수나 메소드를 호출하는 형식의 API
  • HTTP 등의 기술로 네트워크를 통한 원격 자원을 호출하는 API

 

HTTP API vs. REST API

HTTP API

HTTP를 활용하여 원격의 데이터를 공유하기 위한 API

 

REST API

Roy Fielding이 저술한 논문에 소개된 개념

웹 상에서 효율적으로 데이터 공유하기 위한 아키텍처 스타일

다양한 조건이 만족되어야 하며 실무에서 모든 조건 만족하여 구현 어려움

  • HTTP와 REST API는 실무에서 혼용되어 사용
  • 엄밀하게는 서로 다른 개념

 

cf. Path parameter를 선택적인 데이터로 설정해주는 경우에도 사용가능은 한가?

사용 가능하긴 한데, 이렇게 좀 복잡하게 바꿔줘야 함... Path Parameter는 URI에 진짜로 포함되기 때문..!

따라서 강의 내용처럼 선택적인 데이터에 대해서는 Path Parameter를 안 쓰는 게 좋음

그냥 Query String 쓸 것!!!

 

@RequestMapping(value = {"/enterprise", "/enterprise/{id}"})
public String getEnterpriseUser(@PathVariable(value = "id", required = false) String id) {
    if (id != null) {
        return id;
    } else {
        return "1";
    }
}

 

Query String vs. Path Parameter

  • 특정 자원을 요청하는 경우는 Path Param, 정렬이나 추가 필터링을 위한 데이터는 Query String 사용
  • 필수 데이터는 Path Param으로 선택적 데이터는 Query String 사용
    • Path Param이 포함된 URI는 Client가 영향을 받기 때문에 변경 비용 높음
    • Query String은 상대적으로 편하게 확장 가능

 

Response 데이터와 JSON 포맷의 이해

Spring Controller와 Response 데이터

  • Controller Annotation은 HTML 파일과 같은 View 를 응답
  • RestController Annotation은 메소드 반환 값 자체를 응답
    • 단순 문자열, JSON

 

JSON (JavaScript Object Notation)

키 - 값 쌍으로 이루어진 데이터 오브젝트를 텍스트 형태로 전달하는 개방형 표준 포맷

객체를 반환하면 JSON 형식의 데이터로 Spring이 변환해서 Client로 최종 응답해줌

웹 개발 시 가장 일반적으로 사용하는 응답 데이터 포맷

각 REST API 별로 어떤 JSON 데이터를 응답할 것인지 사전에 정함

 

배열을 만들 때 데이터 타입이 다른 데이터도 원소로 포함 가능 ex. String 원소, 객체 원소 둘 다 포함 가능!

객체 내의 프로퍼티들에 대한 순서는 정의되어 있지 않음!

 

데이터 교환 시 사용됨

기존의 방법(XML)보다 가벼움

XML에 의해 상대적으로 사람이 읽고 이해하기 쉬움

 

 

 

@RequestMapping(value = "/user-detail")
public UserDTO getUser() {
    List<String> specialties = new ArrayList<>();
    specialties.add("Java");
    specialties.add("Spring Boot");

    return new UserDTO(1, "Jin", "jin@codepresso.kr", specialties);
}

 

{
    "id": 1,
    "name": "Jin",
    "email": "jin@codepresso.kr",
    "specialties": [
        "Java",
        "Spring Boot"
    ]
}

 

JSON 형태로 응답함

 

한편, 리스트의 원소 데이터 타입을 객체로 했을 땐, 아래와 같이 응답이 돌아옴

 

@RequestMapping(value = "/user-detail")
public UserDTO getUser() {
    List<SpecialtyDTO> specialties = new ArrayList<>();
    specialties.add(new SpecialtyDTO("Java", "Advanced"));
    specialties.add(new SpecialtyDTO("Spring Boote", "Intermediate"));

    return new UserDTO(1, "Jin", "jin@codepresso.kr", specialties);
}

 

{
    "id": 1,
    "name": "Jin",
    "email": "jin@codepresso.kr",
    "specialties": [
        {
            "subject": "Java",
            "level": "Advanced"
        },
        {
            "subject": "Spring Boote",
            "level": "Intermediate"
        }
    ]
}

 

[] 형태로 되어 있는 리스트 안에 {} 로 되어있는 객체가 들어있음

 

JSON 형태의 요청을 받을 수 있는 @RequestBody

요청을 다음과 같이 JSON 형태의 문자열로 보내면

 

{
    "id" : 1,
    "title" : "hello",
    "content" : "Nice to meet you",
    "username" : "haein"
}

 

아래와 같이 @RequestBody로 요청을 받을 수 있다. 

@RequestBody가 받은 JSON 문자열을 가지고 PostDTO 객체를 생성해서 postDTO 변수에 파라미터로 넣어준다.

 

@PostMapping
public String savePost(@RequestBody PostDTO postDTO) {
    System.out.println(postDTO.getId());
    System.out.println(postDTO.getTitle());
    System.out.println(postDTO.getContent());
    System.out.println(postDTO.getUsername());

    return "POST /";
}

 

계층형 아키텍처 패턴

 

Spring Service

시스템의 핵심 비즈니스 로직을 구현하는 계층

view의 종류와 database 종류에 영향을 받지 않는 독립적인 계층 (영향 받지 않게 설계해야 재사용 가능)

파라미터로 전달된 데이터들의 검증 작업 수행

Service 계층의 단일 메소드가 transaction 의 단위가 됨 (??)

애플리케이션의 세부 영역별로 클래스 생성하여 구현

인터페이스의 사용이 권장되나, 다형성을 활용한 기능 확장의 요구사항이 없는 경우 사용하지 않기도 함

 

ex. 이미지, 글 등의 컨텐츠 정보 저장

사용자가 선호할만한 컨텐츠 추천

회원가입, 로그인, 회원 탈퇴 등의 회원 관련 처리

 

애플리케이션의 세부 영역별로 클래스 생성하여 구현

 

 

[ Dependency Injection ]

객체의 활용과 의존성

일반적으로 다른 객체의 기능을 사용하기 위해서는 멤버 변수에 new로 객체를 생성하여 참조

생성한 객체의 메소드 호출

어떤 객체를 생성하여 사용할 것인지 코드 상에 명시 

> Run time에 의존성이 생성되는 게 아니라 Compile time에 의존성이 생성됨

 

Dependency Injection

객체 생성을 외부에서 대신 수행

활용할 객체에 대한 의존성(참조)설정을 외부에서 대신 해 줌

활용할 클래스(인터페이스)타입의 멤버 변수만 선언 후 생성자 구현

> new 키워드로 객체 생성 직접 하지 않음

> 스프링 컨테이너에서 객체를 생성하고 넣어주는 것임

> 컴파일 타임에선 어떤 객체를 주입 받을지 알 수 없음 (인터페이스 구현체이거나 부모 클래스 상속받았을 시)

실행 시 외부에서 구체적인 생성자의 객체를 전달해주어야 함 > 사용하는 곳에서 결정해주어야 함

멤버 변수가 참조해야 하는 구체적인 객체를 코드 상에 명시하는 게 아니라 런타임에 외부에서 결정해서 넣어줌

 

Spring Framework가 객체 생성과 관리 역할 수행

Spring Framework가 특정 조건 만나면 객체 생성

클래스 상단의 Annotation (@Controller, @RestController, @Service 등)

@Configuration 클래스의 @Bean 어노테이션 -> 메소드의 리턴하는 객체가 Spring Bean으로 등록됨

(Spring에 의해 생성되고 관리되는 자바 객체가 됨)

XML 설정

 

Spring IoC 컨테이너와 Spring Bean

Spring IoC 컨테이너

Spring Framework에서 객체의 생성과 관리, 의존성 연결의 역할을 하는 컴포넌트

 

Spring Bean

Spring IoC 컨테이너에 의해서 생성되어 관리되는 Java 객체

component scan 단계에서 PostService 객체를 생성해서 들고 있다가 PostController 객체를 생성할 때 생성자를 보고 PostService가 필요한 것을 알고 기존에 만든 bean을 주입해줌

 

@RestController
@RequestMapping("/user")
public class UserController {

    private final PostService postService; // 왜 private final 쓰지?

    public UserController(PostService postService) { 
        this.postService = postService;
    }
    
}

 

왜 private final을 쓰는지 모르겠음..

 

application.properties에 

 

logging.level.org.springframework.beans = DEBUG

 

추가하면

 

아래와 같은 로그가 찍히는데, Bean이 생성되고 Autowiring ~ 하면서 의존성을 주입해주는 로그를 볼 수 있다.

 

Initializing Spring embedded WebApplicationContext
2023-08-01 11:26:12.822  INFO 16416 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1826 ms
2023-08-01 11:26:12.830 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'requestContextFilter'
2023-08-01 11:26:12.833 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'formContentFilter'
2023-08-01 11:26:12.833 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration'
2023-08-01 11:26:12.838 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'characterEncodingFilter'
2023-08-01 11:26:12.920 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'controllerexerciseApplication'
2023-08-01 11:26:12.922 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'helloController'
2023-08-01 11:26:12.926 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'requestParamController'
2023-08-01 11:26:12.930 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'userController'
2023-08-01 11:26:12.932 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'postService'
2023-08-01 11:26:12.934 DEBUG 16416 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'userController' via constructor to bean named 'postService'