관리 메뉴

제뉴어리의 모든것

[Section3] [Spring MVC] API 계층 - Controller 본문

코드스테이츠/정리 블로깅

[Section3] [Spring MVC] API 계층 - Controller

제뉴어리맨 2022. 8. 20. 20:15

Controller란?

앞선 "[Section3] [Spring MVC] API 계층 - Spring MVC 아키텍처" 포스트에서 설명 하였듯이,

클라이언트와 서버와의 엔드포인트, 즉 접점이라고 할 수 있는 영역이다.

쉽게 생각하면, 스프링으로 웹 개발을 하는 개발자가 실질적으로 클라이언트의 요청을 받아들이는 부분이다.

자바 클래스로 구현을 하며,

스프링컨테이너에 빈으로 등록 되어야한다.

그리고 스프링에 해당 클래스가 Controller임을 알리는데는 여러 방법이 있을 수 있지만,

@Controller 라는 애노테이션을 붙임으로써 알린다.

 

Controller에 대한 설명은 프로젝트 내용으로 살펴보자.

 

패키지 구조

 


+ 패키지 구조는 크게 2가지의 형태로 구성할 수 있다.

  • 기능 기반 패키지 구조(package-by-feature)
    coffe 관련 처리에 대한 클래스와 인터페이스를 묶어 놓은 coffe 패키지

 

  • 계층 기반 패키지 구조(package-by-layer)
    역할로써 각 클래스와 인터페이스를 나누었다.

 

코드 내용

  • 엔트리 포인트 코드
    main() 메소드가 있는 프로그램의 시작 진입점이다.
@SpringBootApplication
public class Be39Section3Week1HomeworkControllerApplication {

	public static void main(String[] args) {
		SpringApplication.run(Be39Section3Week1HomeworkControllerApplication.class, args);
	}

}

- @SpringBootApplication 의 기능 3가지
@SpringBootApplication 애노테이션은 내부적으로 아래에 나와있는 3개의 애노테이션을 랩핑하고 있다.

 

1. @ComponentScan 
@Component 가 붙은 클래스들을 스프링빈으로 등록하여 준다.

 

2. @SpringBootConfiguration

스프링의 @Configuration을 대체하며 스프링 부트 전용 어노테이션이다. 테스트 어노테이션을 사용할 때 계속 이 어노테이션을 찾기 때문에 스프링 부트에서는 필수 어노테이션이다.


3. @EnableAutoConfiguration

자동 설정의 핵심 어노테이션이다. 클래스 경로에 지정된 내용을 기반으로 설정 자동화를 수행한다.

 

참조 : https://velog.io/@adam2/SpringBoot-%EC%9E%90%EB%8F%99-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95AutoConfiguration

 

[SpringBoot] 자동환경설정::AutoConfiguration 까보기

스프링부트가 어떻게 자동으로 환경설정 하는지 알아봅시다.

velog.io

 

 

  • Coffe컨트롤러 코드
@RestController
@RequestMapping("/v1/coffees")
public class CoffeeController {
    private final Map<Long, Map<String, Object>> coffees = new HashMap<>();

    @PostConstruct
    public void init() {
        Map<String, Object> coffee1 = new HashMap<>();
        long coffeeId = 1L;
        coffee1.put("coffeeId", coffeeId);
        coffee1.put("korName", "바닐라 라떼");
        coffee1.put("engName", "Vanilla Latte");
        coffee1.put("price", 4500);

        coffees.put(coffeeId, coffee1);
    }

    //---------------- 여기서 부터 아래에 코드를 구현하세요! -------------------//
    // 1. 커피 정보 수정을 위한 핸들러 메서드 구현
    @PatchMapping
    public ResponseEntity patchCoffe(@RequestParam("coffeeId") long coffeId,
                                     @RequestParam("korName") String korName,
                                     @RequestParam("price") int price) {

        Map<String, Object> findCoffe = coffees.get(coffeId);
        findCoffe.put("korName", korName);
        findCoffe.put("price", price);

        coffees.put(coffeId,findCoffe);

        System.out.println("수정된 커피 : " + findCoffe);
        return new ResponseEntity<>(findCoffe, HttpStatus.OK);
    }
    // 2. 커피 정보 삭제를 위한 핸들러 서드 구현

    @DeleteMapping
    public ResponseEntity deleteCoffe(@RequestParam("coffeeId") long coffeId) {

        coffees.remove(coffeId);

        System.out.println("커피 리스트 : " + coffees);

        return new ResponseEntity<>(null, HttpStatus.NO_CONTENT);

    }
}

- @RestController :

@Controller에 @ResponseBody가 추가된 의미이다.

즉, Json 형태로 객체 데이터를 반환하는 Controller라는 것이다.

(객체를 전달하면 알아서 Json 형태로 전달하여 준다. 그러므로
@RequestMapping 애노테이션의 속성으로 produces = {MediaType.APPLICATION_JSON_VALUE} 를 지정해줄 필요 없다)

 

@Controller와의 차이점은 아래에서 자세히 설명하겠다.

 

- @RequestMapping :

클라이언트로부터 들어 온 URI와 Controller 매핑시켜 주는 애노테이션이다.

CoffeeController 클래스 내의 핸들러 메소드(URI와 매핑되어 실제로 EndPoint가 되는 메소드)는

모두 http://서버 Root URL/v1/coffees 으로 시작하는 URI에 매핑이 된다.

메소드에도 붙일 수 있다.

 

- @PostConstruct :

스프링빈의 초기화 메소드임을 스프링에게 알리는 애노테이션.

@PostConstruct가 붙은 메소드는 WAS 가 뜰 때 bean(해당 메소드를 가진 클래스)이 생성된 다음 딱 한번만 실행

 

+ @PreDestroy : 소멸자 메소드임을 알리는 애노테이션

 

 

- @PatchMapping :

HTTP 메소드 중에 Patch를 수행하는 메소드와 매핑을 시키겠다는 애노테이션

 

- @RequestParam : 괄호안에 지정한 String값으로 지정된, URI 내의 Key값과 매칭되는 Value값을 변수에 담아줌.

EX )  아래의 URI내의 Key값과 @RequestParam 괄호안에 Key값이 매칭되어 Value가 변수에 할당됨.

 

URI : http://localhost:8080/v1/coffees?coffeeId=1&korName=바닐라 빈 라떼&price=5000

 

핸들러메소드의 선언부 :

public ResponseEntity patchCoffe(@RequestParam("coffeeId") long coffeId,
                                 @RequestParam("korName") String korName,
                                 @RequestParam("price") int price)

@RequestParam 는 많이 쓰이고 유용한 점들이 많다. 아래 블로그를 참조하자.

 

 

+ @PathVariable :

@RequestParam 처럼 URI에 있는 URI에서 데이터를 받아오는 애노테이션이다.

그러나 쿼리스트링의 형태가 아닌

http://localhost:8000/board/1 과 같은 형태의 URI에서 1의 해당하는 값들을 받아 올 수 있는 애노테이션입니다.

@RequestParam는 다수의 파라미터를 지정하여 URI에서 매핑을 할 수 있는 반면,

@PathVariable는 오직 1개의 데이터만 매핑이 가능하다.

 

 

 

참조 : (유용) https://byul91oh.tistory.com/434

 

@RequestParam과 @PathVariable?

컨트롤러에서 Requestparam으로 파라미터 값을 넘겨받을 때 사용하는 어노테이션 스프링에서는 컨트롤러로 사용할 클래스 상단에 @Controller를 지정합니다. 주로 사용하는 형태의 파라메터를 전달하

byul91oh.tistory.com

https://velog.io/@dongscholes/JavaSpringBoot-RequestParam-vs-PathVariable-%EC%93%B0%EC%9E%84%EC%83%88-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%B0%A8%EC%9D%B4%EC%A0%90

 

 

- @DeleteMapping :

HTTP Request가 Delete 메소드 형태로 왔을때, 매핑되는 애노테이션이다.

 

ResponseEntity 클래스

Spring Framework에서 제공하는 클래스 중 HttpEntity라는 클래스가 존재한다. 이것은 HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader HttpBody를 포함하는 클래스이다. HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity 클래스이다. ResponseEntity는 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다. 따라서 HttpStatus, HttpHeaders, HttpBody를 포함한다. 

 

즉, Http 통신에서 Response를 전달할때 필요한 데이터들이 포함 된 클래스라고 할 수가 있다.

개발자는 단지 ResponseEntity에 비즈니스 로직을 거쳐 나온 결과만 넣어주기 때문이다.

나머지 Http 통신에 필요한 데이터들은 알아서 가지고 있는것이다.

 

참조 : https://devlog-wjdrbs96.tistory.com/182

 

  • Member 컨트롤러 코드
@RestController
@RequestMapping("/v1/members")
public class MemberController {
    private final Map<Long, Map<String, Object>> members = new HashMap<>();

    @PostConstruct
    public void init() {
        Map<String, Object> member1 = new HashMap<>();
        long memberId = 1L;
        member1.put("memberId", memberId);
        member1.put("email", "hgd@gmail.com");
        member1.put("name", "홍길동");
        member1.put("phone", "010-1234-5678");

        members.put(memberId, member1);
    }

    //---------------- 여기서 부터 아래에 코드를 구현하세요! -------------------//
    // 1. 회원 정보 수정을 위한 핸들러 메서드 구현
    @PatchMapping
    public ResponseEntity patchMember(@RequestParam("memberId") long memberId, @RequestParam("phone") String phone){

        System.out.println("# memberId : " + memberId);
        System.out.println("# phone : " + phone);

        Map<String, Object> findMember = members.get(memberId);

        findMember.put("phone", phone);
        members.put(memberId,findMember);

        System.out.println("수정 멤버 : " + findMember);

        return new ResponseEntity<>(findMember, HttpStatus.OK);
    }

    // 2. 회원 정보 삭제를 위한 핸들러 메서드 구현
    @DeleteMapping
    public ResponseEntity deleteMember(@RequestParam("memberId") long memberId){

        members.remove(memberId);
        System.out.println("멤버 리스트 : " + members);
        return new ResponseEntity<>(null, HttpStatus.NO_CONTENT);
    }

}

 

위에 Coffe 컨트롤러에서 사용된 애노테이션, 클래스(ResponseEntity) 들이 그대로 사용되었기 때문에 설명은 생략한다.

 

 

 

@Controller VS @RestController

 

  • @Controller
    View를 반환하여 주는 Controller임을 스프링에게 알림.
    클라이언트의 요청이 들어왔는데 해당 Controller 객체에 @Controller가 붙어 있는 경우의 처리 순서는 아래와 같다.

    1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
    2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
    3. HandlerMapping을 통해 요청을 Controller로 위임한다.
    4. Controller는 요청을 처리한 후에 ViewName을 반환한다.
    5. DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다. 

    즉, HTML 파일 자체를 제공한다.


  • @RestController
    해당 Controller는 클라이언트에게 반환하는 리소스가 Videw가 아니라 Data임을 알리는 애노테이션.
     
    1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다. 
    2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
    3. HandlerMapping을 통해 요청을 Controller로 위임한다.
    4. Controller는 요청을 처리한 후에 객체를 반환한다.
    5. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

    위에 코드를 보면 
    Controller에서 new ResponseEntity<>(findMember, HttpStatus.OK) 형태로 리턴을 한다.
    즉, @Controller처럼 view의 이름을 리턴하는게 아니라, 객체를 반환하고 있다.

    컨트롤러를 통해 객체를 반환할 때에는 일반적으로 ResponseEntity로 감싸서 반환을 합니다. 그리고 객체를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter가 동작합니다. HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라집니다. 단순 문자열인 경우에는 StringHttpMessageConverter가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter가 작동하게 됩니다. Spring은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 이를 처리합니다. MessageConverter가 동작하는 시점은 HandlerAdapter와 Controller가 요청을 주고 받는 시점이다. 그림의 4번에서는 메세지를 객체로, 6번에서는 객체를 메세지로 변환하는데 메세지 컨버터가 사용된다. 

 

참조 : https://mangkyu.tistory.com/49