Spring - 스프링에서 지원하는 @ 어노테이션(Annotation)에 관하여

4 분 소요

해당 글은 김영한님의 인프런 강의 “스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술”의 내용을 학습 후 정리하기 위해 작성하는 글입니다.


예전에는 자바에 어노테이션 기능 조차 없었지만.. 자바에 어노테이션 @이 추가되고 스프링에서 어노테이션 기반의 인터페이스들을 지원하게 되면서 개발자들은 더욱더 클린 코드를 작성할 수 있게 되었다. 스프링에서 제공하는 어노테이션들에 대해 정리해보고자 한다.

Class-Level Annotations


@Controller

MVC 패턴의 Controller 클래스임을 명시한다. 해당 클래스에서 URL 매핑된 메소드들은 리턴 값으로 viewName(String)을 반환한다. 따라서 ViewResolver를 통해 viewName에 맞는 뷰를 클라이언트에게 제공한다.

@RestController

REST API를 제공하는 컨트롤러를 위한 어노테이션 해당 클래스에서 URL 매핑된 메소드들은 리턴 값으로 String을 반환한다. html view 형식이 아닌 JSON과 같은 데이터를 클라이언트에게 제공한다.

Method-Level Annotations


@ResponseBody

@Controller로 지정된 클래스 내에서 어떤 비즈니스 로직을 처리하는 메소드는 클라이언트에게 뷰가 아닌 데이터(JSON)를 전송하고 싶을때 사용하는 어노테이션이다.


@Controller
public class SomeController {
    
    /*
    * 뷰를 리턴하는 여러 메소드들...
    */

    // 해당 클래스 내에서 데이터 BODY를 리턴하고 싶은 경우
    @ResponseBody
    @RequestMapping("/get-data")
    public String dataToClient() {
        // View가 아닌 실제 데이터 전송이 가능하다.
        return "some data";
    }
}

@RequestBody

HTTP 요청 파라미터 형식이 아닌 HTTP BODY 형식의 데이터를 클라이언트에게서 받아서 처리해야 할 때 사용할 수 있는 어노테이션이다.

먼저 기존 서블릿 클래스를 이용한 방법은 아래와 같다.

/*
* 서블릿을 이용한 기존의 BODY 데이터 처리 방법
*/
@PostMapping("/request-body-servlet")
public void requestBodyServlet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    
    // ServletInputStream 클래스를 이용한다.
    // BODY 데이터를 입력 스트림의 형태로 가져오기 때문에 반드시 인코딩 명시 필요
    ServletInputStream inputStream = req.getInputStream();
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    /*
    * some logic...
    */
    resp.getWriter().write("response");
}

@RequestBody 어노테이션을 사용하면 매우 간단하게 String 타입의 BODY를 가져올 수 있다.

/*
* @RequestBody 어노테이션 이용
*/
@ResponseBody
@PostMapping("request-body-annotation")
public String requestBodyAnnotation(@RequestBody String messageBody) {
    /*
    * some logic...
    */
    return "response";
}

@RequestMapping

어노테이션의 이름과 같이 요청(URL)을 컨트롤러의 메소드와 매핑할 때 사용하는 어노테이션이다. 메소드 뿐만 아니라 클래스 레벨에서도 사용이 가능하다.

기존 서블릿의 구조와 비교해본다면 다음과 같다.

// (1) 기존 @WebServlet의 구조
@WebServlet(name = "someServlet", urlPatterns = "/someURL")
public class SomeServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // HttpServlet 클래스의 service 메소드를 오버라이드 해야한다.
        // 파라미터로 request, response 두가지만 받을 수 있다.
    }
}

// (2) 스프링 @RequestMapping 구조
@Controller
public class SomeController {
    @RequestMapping("/someURL")
    public String someRequestMapping(/*매우 많은 파라미터들*/) {
        // 메소드 형식으로 여러 매핑 형태를 구현할 수 있다.
        // 만든 메소드의 파라미터로 여러가지 값들을 가져올 수 있다.
    }
}

파라미터로 받을 수 있는 Method Arguments들은 종류가 매우 많다.
공식 Spring docs 에 자세하게 설명되어 있다.

@GetMapping, @PostMapping, etc

@RequestMapping와 동일하지만, HTTP 메소드를 따로 지정해줘야 했던 @RequestMapping과 다르게 원하는 메소드 매핑을 직접 사용할 수 있다.

// 두가지 방법 모두 동일한 기능을 한다.
@RequestMapping(value = "/someGetPage", method = RequestMethod.GET)

@GetMapping("/someGetPage")

스프링에서는 다음과 같은 HTTP 통신 메소드 어노테이션을 지원한다.

img

@RequestHeader, @CookieValue

Mapping된 메소드에 파라미터로 HTTP 통신의 헤더값들을 전달받을 수 있다.

서블릿에서 사용했던 HttpServletRequest, HttpServletResponse 뿐만 아니라 HTTP 통신 메소드를 정의하는 HttpMethod, 클라이언트의 locale 정보를 담는 Locale 처럼 스프링에서 제공하는 클래스들 말고도 아래처럼 어노테이션 형식의 파라미터를 지원한다.

@RequestMapping("/headers")
public String getHeadersWithAnnotations(
    HttpServletRequest req,
    HttpServletResponse resp,
    // GET, POST, PATCH, etc..
    HttpMethod httpMethod,
    // ko_KR
    Locale locale,
    // 헤더 정보 모두 가져오기
    // MultiValueMap<K, V>: 하나의 key에 여러 value를 받을 수 있다!
    @RequestHeader MultiValueMap<String, String> headerMap,
    // 특정 헤더값 가져오기
    @RequestHeader("host") String host,
    // 쿠키
    @CookieValue(value = "someCookie", required = false) String cookie) {
    
    /*
    * some logic...
    */

    return "";
}

어노테이션의 파라미터들에 대해 자세하게 알아보면 다음과 같다.

@RequestHeader(
    // header의 이름을 명시한다. 아래 name의 별칭이다.
    String value, 

    // value 와 같은 기능
    String name,  

    // header 값의 필요 여부, true 일 경우 헤더 값이 존재하지 않으면 exception 발생
    boolean required, 

    // required=false 일 때 헤더 값이 존재하지 않는 경우 기본 값을 설정해줌
    String defaultValue
    )

@RequestParam

HTTP 요청 파라미터를 쉽게 받을 수 있는 어노테이션이다.

위의 @RequestHeader와 동일한 파라미터 값들을 지원한다


/*
* http://localhost:8080/params?id=myid&pw=1234
*/
@RequestMapping("/params")
public String getParamsWithAnnotations(
    @RequestParam(value = "id", defaultValue = "guest") String id,
    @RequestParam("pw") int password) {
    /*
    * some logic...
    */
    return "";
}

아래와 같이 @RequestParam 어노테이션 생략 또한 가능하다.

// @RequestParam 어노테이션 생략 가능
// 1. 파라미터의 value name과 값을 저장할 변수 이름이 같은 경우
// 2. int, Integer, String과 같은 단순타입 인 경우
@RequestMapping("/params")
public String getParamsWithAnnotations(
    @RequestParam(defaultValue = "guest") String id, 
    int password) {
    /*
    * some logic...
    */
    return "";
}
  • 만약 HTTP 요청에서의 파라미터 값이 공백(“”) 이라면? http://localhost:8080/params?username= 와 같은 요청의 경우 username의 파라미터에는 “” 공백데이터로 인식되어 정상 작동한다.

"”과 null은 다르기 때문에.. 개발자는 이를 조심해야 한다. 한가지 예방법으로는 defaultValue 값을 설정해주면 된다.

@RequestParam(defaultValue="guest") String id
// 'http://localhost:8080/params?username=' HTTP 요청 시
// username = "guest"

@ModelAttribute

요청 파라미터의 값들을 통해 객체를 만들때 하나하나 파라미터값을 불러오지 않아도 @ModelAttribute를 이용하여 객체 생성을 자동화할 수 있다.

class SomeClass {
    private String someData;

    /*
    * Getter & Setter
    */
}

@Controller
public class ModelAttributeTestClass {

@RequestMapping("/model-attribute")
    public String makeModel(@ModelAttribute SomeClass someInstance) {
        /*
        * some logic...
        */
        return "";
    }

@RequestParam과 동일하게 어노테이션 생략이 가능하다.

public String makeModel(SomeClass someInstance) { ... }
  • 생략된 @RequestParam과 @ModelAttribute는 어떻게 구분될까?
    1. 단순 타입(int, Integer, String, etc)의 경우 @RequestParam
    2. 단순 타입을 제외한 나머지 클래스의 경우 @ModelAttribute

하지만 HttpServletRequest와 같은 클래스 파라미터들은 @ModelAttribute로 인식하지 않는다. 그 이유는 Argument resolver로 지정해둔 클래스 타입이기 때문이다.

댓글남기기