본문 바로가기
개발 공부/Spring

Spring | 🔥 Spring 프레임워크의 주요 어노테이션

by Hoya324 2024. 9. 24.

들어가기 전

Spring을 공부하다보면 여러 어노테이션을 사용하게 되는데, 어떤 종류가 있는지를 알아보고 각각의 어노테이션이 어떤 역할을 하는지 간단하게 알아보겠습니다!

🔥 Annotation이 무엇인가

Annotation이란?

  • 사전적인 의미로는 주석이라는 뜻이다.
  • 자바에서는 Annotation은 코드 사이에 주석처럼 쓰이며 특별한 의미, 기능을 수행하도록 하는 기술이다.
    • 프로그램에게 추가적인 정보를 제공해주는 메타데이터라고 볼 수 있다.
    • meta data : 데이터를 위한 데이터

Annotation 사용 이유

  • Annotation을 통하여 코드량이 감소하고 유지보수하기 쉬우며, 생산성이 증가된다.
  • 기존의 자바 웹 어플리케이션은 구성과 설정값들을 외부 XML설정 파일에 명시하여 프로그래밍 되었다.
  • 외부에서 변경될 수 있는 값을 관리하기에 재컴파일 없이 쉽게 변경사항을 적용할 수 있었지만, 프로그램의 규모가 커질수록 설정을 다 적어줘야했고, 프로그램을 작성할때 마다 많은 설정을 작성해야했기에 문제점이 있었다.
  • 이를 해결하기 위해 고안된 문법이 어노테이션이다.
  • 어노테이션은 런타임 또는 컴파일에 어노테이션은 해석될 수 있다.
  • 어노테이션은 보통 문서화, 컴파일러 체크, 코드 분석을 위한 용도로 사용되는데 본질적인 목적은 소스 코드에 메타데이터를 표현하는 것이다.

Annotation 사용 순서

  1. 어노테이션을 정의한다.
  2. 클래스에 어노테이션을 배치한다.
  3. 코드가 실행되는 중에 Reflection을 이용하여 추가 정보를 획득하여 기능을 실시한다.

Reflection이란?

  • Reflection이란 프로그램이 실행 중에 자신의 구조와 동작을 검사하고, 조사하고, 수정하는 것이다.
  • Reflection은 프로그래머가 데이터를 보여주고, 다른 포맷의 데이터를 처리하고, 통신을 위해 serialization(직렬화)를 수행하고, bundling을 하기 위해 일반 소프트웨어 라이브러리를 만들도록 도와준다.
    • 직렬화(serialize)란 자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할수 있도록 바이트 스트림(stream of bytes) 형태로 연속전인(serial) 데이터로 변환하는 포맷 변환 기술을 일컫는다.
    • 그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.
  • Java와 같은 객체 지향 프로그래밍 언어에서 Reflection을 사용하면 컴파일 타임에 인터페이스, 필드, 메소드의 이름을 알지 못해도 실행 중에 클래스, 인터페이스, 필드 및 메소드에 접근할 수 있다.
  • 새로운 객체의 인스턴스화 및 메소드 호출을 허용한다.
  • Java와 같은 객체 지향 프로그래밍 언어에서 Reflection을 사용하여 멤버 접근 가능성 규칙을 무시할 수 있다.
  • Spring에서 BeanFactory라는 Container에서 객체가 호출되면 객체의 인스턴스를 생성하게 되는데 이 때 필요하게 된다. 즉, 프레임워크에서 유연성있는 동작을 위해 쓰인다.
  • Annotation 자체는 아무런 동작을 가지지 않는 단순한 표식일 뿐이지만, Reflection을 이용하면 Annotation의 적용 여부와 엘리먼트 값을 읽고 처리할 수 있다.
  • Class에 적용된 Annotation 정보를 읽으려면 java.lang.Class를 이용하고
    필드, 생성자, 메소드에 적용된 어노테이션 정보를 읽으려면 Class의 메소드를 통해 java.lang.reflect 패키지의 배열을 얻어야 한다.
  • Class.forName(), getName(), getModifier(), getFields() getPackage() 등등 여러 메소드로 정보를 얻을 수 있다.
  • Reflection을 이용하면 Annotation 지정만으로도 원하는 클래스를 주입할 수 있다.

🔥 Spring Annotation

@ComponentScan

  • @Component와 @Service, @Repository, @Controller, @Configuration이 붙은 클래스 Bean들을 찾아서 Context에 bean등록을 해주는 Annotation이다.
  • @Component Annotation이 있는 클래스에 대하여 bean 인스턴스를 생성
  • ApplicationContext.xml에 <bean id="jeongpro" class="jeongpro" /> 과 같이 xml에 bean을 직접등록하는 방법도 있고 위와 같이 Annotation을 붙여서 하는 방법도 있다.
  • base-package를 넣으면 해당 패키지 아래에 있는 컴포넌트들을 찾고 그 과정을 spring-context-버전(4.3.11.RELEASE).jar에서 처리한다.
  • Spring에서 @Component로 다 쓰지 않고 @Repository, @Service, @Controller등을 사용하는 이유는, 예를들어 @Repository는 DAO의 메소드에서 발생할 수 있는 unchecked exception들을 스프링의 DataAccessException으로 처리할 수 있기 때문이다.
    • 또한 가독성에서도 해당 애노테이션을 갖는 클래스가 무엇을 하는지 단 번에 알 수 있다.
      자동으로 등록되는 Bean의 이름은 클래스의 첫문자가 소문자로 바뀐 이름이 자동적용된다.
    • HomeController -> homeController

@Component

  • @Component 은 개발자가 직접 작성한 Class를 Bean으로 등록하기 위한 Annotation이다.
  • Component에 대한 추가 정보가 없다면 Class의 이름을 camelCase로 변경한 것이 Bean id로 사용된다.
  • @Component는 @Bean과 다르게 name이 아닌 value를 이용해 Bean의 이름을 지정한다.
  • Spring은 해당 Annotation을 보고 Spring의 Bean으로 등록합니다.

@Bean

  • @Bean은 개발자가 직접 제어가 불가능한 외부 라이브러리등을 Bean으로 만들려할 때 사용되는 Annotation이다.
  • ArrayList같은 라이브러리등을 Bean으로 등록하기 위해서는 별도로 해당 라이브러리 객체를 반환하는 Method를 만들고 @Bean Annotation을 사용하면 된다.
  • @Bean에 name이라는 값을 이용하면 자신이 원하는 id로 Bean을 등록할 수 있다.

@Autowired

  • 속성(field), setter method, constructor(생성자)에서 사용하며 Type에 따라 알아서 Bean을 주입 해준다.
  • 무조건적인 객체에 대한 의존성을 주입시킨다.
  • @Autowired를 사용할 시, 스프링이 자동적으로 값을 할당한다.
  • Controller 클래스에서 DAO나 Service에 관한 객체들을 주입 시킬 때 많이 사용한다.
  • 필드, 생성자, 입력 파라미터가 여러 개인 메소드(@Qualifier는 메소드의 파라미터)에 적용 가능하다.
  • Type을 먼저 확인한 후 못 찾으면 Name에 따라 주입한다.

Bean을 주입받는 방식 (3가지)

  1. @Autowired
  2. setter
  3. 생성자 (@AllArgsConstructor 사용) -> 권장방식

@Inject

  • @Autowired 어노테이션과 비슷한 역할을 한다.

@ResponseBody

  • HttpMessageConverter를 이용하여 JSON 혹은 xml 로 요청에 응답할수 있게 해주는 Annotation이다.
  • view가 아닌 JSON 형식의 값을 응답할 때 사용하는 Annotation으로 문자열을 리턴하면 그 값을 http response header가 아닌 response body에 들어간다.
  • 이미 RestController Annotation이 붙어 있다면, 쓸 필요가 없다.
  • 허나 그렇지 않은 단순 컨트롤러라면, HttpResponse로 응답 할 수 있게 해준다.
  • 만약 객체를 return하는 경우 JACKSON 라이브러리에 의해 문자열로 변환되어 전송된다.
  • context에 설정된 viewResolver를 무시한다고 보면된다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String getUser(@RequestParam String nickname, @RequestParam(name="old") String age {
        // GET method, /user 요청을 처리
        // https://naver.com?nickname=dog&old=10
        User user = new User();
        user.setName(nickname);
        user.setAge(age);
        return user;
    }
}

@RequestHeader

  • Request의 header값을 가져올 수 있다. 메소드의 파라미터에 사용한다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @RequestMapping(method = RequestMethod.GET)
    public String getUser(@RequestHeader(value="Accept-Language") String acceptLanguage) {
        //  GET method, /user 요청을 처리
    }
}

@RequestMapping

  • @RequestMapping(value=”“)와 같은 형태로 작성하며, 요청 들어온 URI의 요청과 Annotation value 값이 일치하면 해당 클래스나 메소드가 실행된다.
  • Controller 객체 안의 메서드와 클래스에 적용 가능하다.
  • Class 단위에 사용하면 하위 메소드에 모두 적용된다.
  • 요청 URL을 어떤 method가 처리할지 mapping해주는 Annotation이다.
  • Controller나 Controller의 method에 적용한다.
  • 요청을 받는 형식인 GET, POST, PATCH, PUT, DELETE 를 정의하기도 한다.
  • 요청 받는 형식을 정의하지 않는다면, 자동적으로 GET으로 설정된다.
  • @RequestMapping에 대한 모든 매핑 정보는 Spring에서 제공하는 HandlerMapping Class가 가지고 있다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @RequestMapping(method = RequestMethod.GET)
    public String getUser(Model model) {
        //  GET method, /user 요청을 처리
    }
    @RequestMapping(method = RequestMethod.POST)
    public String addUser(Model model) {
        //  POST method, /user 요청을 처리
    }
    @RequestMapping(value = "/info", method = RequestMethod.GET)
    public String addUser(Model model) {
        //  GET method, /user/info 요청을 처리
    }
}

@PathVariable

  • method parameter 앞에 사용하면서 해당 URL에서 {특정값}을 변수로 받아 올 수 있다.
  • HTTP 요청에 대해 매핑되는 request parameter 값이 자동으로 Binding 된다.
  • uri에서 각 구분자에 들어오는 값을 처리해야 할 때 사용한다.
  • REST API에서 값을 호출할 때 주로 많이 사용한다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}

@RequestParam

  • @PathVariable과 비슷하다.
  • request의 parameter에서 가져오는 것이다. method의 파라미터에 사용된다.
  • ?moviename=thepurge 와 같은 쿼리 파라미터를 파싱해준다.
  • HTTP GET 요청에 대해 매칭되는 request parameter 값이 자동으로 들어간다.
  • url 뒤에 붙는 parameter 값을 가져올 때 사용한다.
  • @RequestParam 어노테이션의 괄호 안의 문자열이 전달 인자 이름(실제 값을 표시)이다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @RequestMapping(method = RequestMethod.GET)
    public String getUser(@RequestParam String nickname, @RequestParam(name="old") String age {
        // GET method, /user 요청을 처리
        // https://naver.com?nickname=dog&old=10
        String sub = nickname + "_" + age;
        ...
    }
}

@RequestBody

  • 요청이 온 데이터(JSON이나 XML형식)를 바로 Class나 model로 매핑하기 위한 Annotation이다.
  • POST나 PUT, PATCH로 요청을 받을때에, 요청에서 넘어온 body 값들을 자바 타입으로 파싱해준다.
  • HTTP POST 요청에 대해 request body에 있는 request message에서 값을 얻어와 매핑한다.
  • RequestData를 바로 Model이나 클래스로 매핑한다.
  • 이를테면 JSON 이나 XML같은 데이터를 적절한 messageConverter로 읽을 때 사용하거나 POJO 형태의 데이터 전체로 받는 경우에 사용한다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @RequestMapping(method = RequestMethod.POST)
    public String addUser(@RequestBody User user) {
        //  POST method, /user 요청을 처리
        String sub_name = user.name;
        String sub_old = user.old;
    }
}

@ModelAttribute

  • view에서 전달해주는 parameter를 Class(VO/DTO)의 멤버 변수로 binding 해주는 Annotation이다.
  • binding 기준은 <input name="id" /> 처럼 어떤 태그의 name값이 해당 Class의 멤버 변수명과 일치해야하고 setmethod명도 일치해야한다.
  • RequestBody와 다르게 HTTP Body 내용은 multipart/form-data 형태를 요구한다.
  • @RequestBody가 json을 받는 것과 달리 @ModenAttribute 의 경우에는 json을 받아 처리할 수 없다.
  • 실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어줘야한다.
  • 이를 @ModelAttribute를 사용하지 않은 경우
@ResponseBody
@RequestMapping ("/request-param-map")
public String requestParamMap (@RequestParam Map<String, Object> paramMap) {
  log.info("username={}, age={}", paramMap.get("username") , paramMap.get("age")) ;
  return "ok";
  • 이와 같은 코드가 작성될 것이다.
  • @ModelAttribute를 사용하면 아래와 같이 표현할 수 있다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1 (@ModelAttribute HelloData helloData) {
  log.info("username={}, age={}", helloData.getUsername(), helloData .getAge());
  log.info("helloData={}", helloData);
  return "ok";

@GetMapping

  • @RequestMapping(Method=RequestMethod.GET)과 같다.
  • @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping 등 도 있다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @GetMapping("/")
    public String getUser(Model model) {
        //  GET method, /user 요청을 처리
    }

    ////////////////////////////////////
    // 위와 아래 메소드는 동일하게 동작합니다. //
    ////////////////////////////////////

    @RequestMapping(method = RequestMethod.GET)
    public String getUser(Model model) {
        //  GET method, /user 요청을 처리
    }
}

@PostMapping

  • RequestMapping(Method=RequestMethod.POST)과 똑같은 역할을 한다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {
    @RequestMapping(method = RequestMethod.POST)
    public String addUser(Model model) {
        //  POST method, /user 요청을 처리
    }

    ////////////////////////////////////
    // 위와 아래 메소드는 동일하게 동작합니다. //
    ////////////////////////////////////

    @PostMapping('/')
    public String addUser(Model model) {
        //  POST method, /user 요청을 처리
    }
}

@Controller

  • Spring의 Controller를 의미한다.
  • Spring MVC에서 Controller클래스에 쓰인다.
@Controller                   // 이 Class는 Controller 역할을 합니다
@RequestMapping("/user")      // 이 Class는 /user로 들어오는 요청을 모두 처리합니다.
public class UserController {

    @RequestMapping(method = RequestMethod.GET)
    public String getUser(Model model) {
        //  GET method, /user 요청을 처리
    }
}

@RestController

  • Spring에서 Controller 중 View로 응답하지 않는, Controller를 의미한다.
  • method의 반환 결과를 JSON 형태로 반환한다.
  • 이 Annotation이 적혀있는 Controller의 method는 HttpResponse로 바로 응답이 가능하다.
  • @ResponseBody 역할을 자동적으로 해주는 Annotation이다.
  • @Controller + @ResponseBody를 사용하면 @ResponseBody를 모든 메소드에서 적용한다.

@Controller 와 @RestController 의 차이

@Controller

  • API와 view를 동시에 사용하는 경우에 사용한다.
  • 대신 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환한다.
  • view(화면) return이 주목적이다.

@RestController

  • view가 필요없는 API만 지원하는 서비스에서 사용한다.
  • Spring 4.0.1부터 제공
  • @RequestMapping 메서드가 기본적으로 @ResponseBody 의미를 가정한다.
  • data(json, xml 등) return이 주목적이다.
  • 즉 @RestController = @Controller + @ResponseBody 이다.

@Service

  • Service Class에서 쓰인다.
  • 비즈니스 로직을 수행하는 Class라는 것을 나타내는 용도이다.

@Transactional

  • 데이터베이스 트랜잭션을 설정하고 싶은 method에 Annotation을 적용하면 method 내부에서 일어나는 데이터베이스 로직이 전부 성공하게되거나 데이터베이스 접근중 하나라도 실패하면 다시 롤백할 수 있게 해주는 Annotation이다.
  • @Transaction(readOnly=true, rollbackFor=Exception.class)에서 readOnly는 읽기전용임을 알리고 rollbackFor는 해당 Exception이 생기면 롤백하라는 뜻이다.
  • @Transaction(noRollbackFor=Exception.class)는 해당 Exception이 나타나도 롤백하지 말라는 뜻이다.
  • @Transaction(timeout = 10)은 10초안에 해당 로직을 수행하지 못하면 롤백하라는 뜻이다.
  • 메소드 내에서 Exception이 발생하면 해당 메소드에서 이루어진 모든 DB 작업을 초기화한다.
  • save 메소드를 통해서 10개를 등록해야 하는데 5번째에서 Exception이 발생하면 앞에 저장된 4개 까지 모두 롤백한다.
  • 정확히 얘기하면, 이미 넣은걸 롤백시키는건 아니며, 모든 처리가 정상적으로 됐을때만 DB에 커밋하며 그렇지 않은 경우엔 커밋하지 않는 것이다.
  • 비지니스 로직과 트랜잭션 관리는 대부분 Service에서 관리한다.
  • 따라서 일반적으로 DB 데이터를 등록/수정/삭제 하는 Service 메소드는 @Transactional를 필수적으로 가져간다.

@Repository

  • DAO class에서 쓰인다.
  • DataBase에 접근하는 method를 가지고 있는 Class에서 쓰인다.

@EnableAutoConfiguration

  • Spring Application Context를 만들 때 자동으로 설정하는 기능을 켠다.
  • classpath의 내용에 기반해서 자동으로 생성해준다.
  • 만약 tomcat-embed-core.jar가 존재하면 톰캣 서버가 setting된다.

@Configuration

  • @Configuration을 클래스에 적용하고 @Bean을 해당 Class의 method에 적용하면 @Autowired로 Bean을 부를 수 있다.

@Required

  • setter method에 적용해주면 Bean 생성시 필수 프로퍼티 임을 알린다.
  • Required Annotation을 사용하여 optional 하지 않은, 꼭 필요한 속성들을 정의한다.
  • 영향을 받는 bean property를 구성할 시에는 XML 설정 파일에 반드시 property를 채워야 한다.

@Qualifier("id123")

  • @Autowired와 같이 쓰이며, 같은 타입의 Bean 객체가 있을 때 해당 아이디를 적어 원하는 Bean이 주입될 수 있도록 하는 Annotation이다.
  • 같은 타입이 존재하는 경우 ex) 동물 = 원숭이, 닭, 개, 돼지
  • 같은 타입의 Bean이 두 개 이상이 존재하는 경우에 Spring이 어떤 Bean을 주입해야 할지 알 수 없어서 Spring Container를 초기화하는 과정에서 예외를 발생시킨다.
  • 이 경우 @Qualifier을 @Autowired와 함께 사용하여 정확히 어떤 bean을 사용할지 지정하여 특정 의존 객체를 주입할 수 있도록 한다.
  • xml 설정에서 bean의 한정자 값(qualifier value)을 설정한다.
  • @Autowired 어노테이션이 적용된 주입 대상에 @Qualifier 어노테이션을 설정한다.

@Resource

  • @Autowired와 마찬가지로 Bean 객체를 주입해주는데 차이점은 Autowired는 타입으로, Resource는 이름으로 연결해준다.
  • javax.annotation.Resource 표준 자바(JSR-250 표준) Annotation으로, Spring Framework 2.5.* 부터 지원 가능한 Annotation이다.
  • Annotation 사용으로 인해 특정 Framework에 종속적인 어플리케이션을 구성하지 않기 위해서는 @Resource를 사용할 것을 권장한다.
  • @Resource를 사용하기 위해서는 class path 내에 jsr250-api.jar 파일을 추가해야 한다.
    필드, 입력 파라미터가 한 개인 bean property setter method에 적용 가능하다.

@PostConstruct, @PreConstruct

  • 의존하는 객체를 생성한 이후 초기화 작업을 위해 객체 생성 전/후에(pre/post) 실행해야 할 method 앞에 붙인다.

@PreDestroy

  • 객체를 제거하기 전(pre)에 해야할 작업을 수행하기 위해 사용한다.

@PropertySource

  • 해당 프로퍼티 파일을 Environment로 로딩하게 해준다.
  • 클래스에 @PropertySource("classpath:/settings.properties")라고 적고 클래스 내부에 @Resource를 - Environment타입의 멤버 변수앞에 적으면 매핑된다.

@ConfigurationProperties

  • yaml파일 읽는다.
  • default로 classpath:application.properties 파일이 조회된다.
  • 속성 클래스를 따로 만들어두고 그 위에 (prefix="mail")을 써서 프로퍼티의 접두사를 사용할 수 있다.

@Lazy

  • 지연로딩을 지원한다.
  • @Component나 @Bean Annotation과 같이 쓰는데 Class가 로드될 때 스프링에서 바로 bean등록을 마치는 것이 아니라 실제로 사용될 때 로딩이 이뤄지게 하는 방법이다.

@Value

  • properties에서 값을 가져와 적용할 때 사용한다.
  • private String valueFromFile; 이라고 구성되어 있으면 value.from.file의 값을 가져와서 해당 변수에 주입해준다.
  • spEL을 이용해서 조금 더 고급스럽게 쓸 수 있다.

@SpringBootApplication

  • @Configuration, @EnableAutoConfiguration, @ComponentScan 3가지를 하나의 애노테이션으로 합친 것이다.

@CookieValue

  • 쿠키 값을 parameter로 전달 받을 수 있는 방법이다.
  • 해당 쿠키가 존재하지 않으면 500 에러를 발생시킨다.
  • 속성으로 required가 있는데 default는 true다.
  • false를 적용하면 해당 쿠키 값이 없을 때 null로 받고 에러를 발생시키지 않는다.

@CrossOrigin

  • CORS 보안상의 문제로 브라우저에서 리소스를 현재 origin에서 다른 곳으로의 AJAX요청을 방지하는 것이다.
  • @RequestMapping이 있는 곳에 사용하면 해당 요청은 타 도메인에서 온 ajax요청을 처리해준다.

@SessionAttributes

  • Session에 data를 넣을 때 쓰는 Annotation이다.
  • @SessionAttributes("name")이라고 하면 Model에 key값이 "name"으로 있는 값은 자동으로 세션에도 저장되게 한다.

@Valid

  • 유효성 검증이 필요한 객체임을 지정한다.

@InitBinder

  • @Valid 애노테이션으로 유효성 검증이 필요하다고 한 객체를 가져오기전에 수행해야할 method를 지정한다.

@RequestAttribute

  • Request에 설정되어 있는 속성 값을 가져올 수 있다.

@RequestPart

  • Request로 온 MultipartFile을 바인딩해준다.

@ExceptionHandler(ExceptionClassName.class)

  • 해당 클래스의 예외를 캐치하여 처리한다.

@ControllerAdvice

  • Class 위에 ControllerAdvice를 붙이고 어떤 예외를 잡아낼 것인지는 각 메소드 상단에 + @ExceptionHandler(예외클래스명.class)를 붙여서 기술한다.

@RestControllerAdvice

@ControllerAdvice + @ResponseBody다.

@ResponseStatus

  • 사용자에게 원하는 response code와 reason을 return해주는 Annotation이다.
  • @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "my page URL + changed..") => 예외처리 함수 앞에 사용한다.

@EnableEurekaServer

  • Eureka 서버로 만들어준다.

@EnableDiscoveryClient

  • Eureka 서버에서 관리될 수 있는 클라이언트 임을 알려주기위한 Annotation이다.

@Cacheable

  • method 앞에 지정하면 해당 method를 최초에 호출하면 캐시에 적재하고 다음부터는 동일한 method 호출이 있을 때 캐시에서 결과를 가져와서 return하므로 method 호출 횟수를 줄여주는 Annotation이다.
  • 주의할 점은 입력이 같으면 항상 출력이 같은 method(=순수 함수)에 적용해야한다.
  • 그런데 또 항상 같은 값만 뱉어주는 메서드에 적용하려면 조금 아쉬울 수 있다.
  • 따라서 메서드 호출에 사용되는 자원이 많고 자주 변경되지 않을 때 사용하고 나중에 수정되면 캐시를 없애는 방법을 선택할 수 있다.

@CachePut

  • 캐시를 업데이트하기 위해서 method를 항상 실행하게 강제하는 Annotation 이다.
  • 해당 Annotation이 있으면 method 호출을 항상한다. 그러므로 @Cacheable과 상충되어 같이 사용하면 안된다.

@CacheEvict

  • 캐시에서 데이터를 제거하는 트리거로 동작하는 method에 붙이는 Annotation이다.
  • 캐시된 데이터는 언제가는 지워져야한다. 그러지 않으면 결과값이 변경이 일어났는데도 기존의 데이터(캐시된 데이터)를 불러와서 오류가 발생할 수 있다.
  • 물론 캐시 설정에서 캐시 만료시간을 줄 수도 있다.
  • allEntries는 전체 캐시를 지울지 여부를 선택하는 것이다.

@CacheConfig

  • 클래스 레벨에서 공통의 캐시설정을 공유하는 기능이다.

@Scheduled

  • Linux의 crontab처럼 정해진 시간에 실행해야하는 경우에 사용한다.
  • @Scheduled(cron = "0 0 07 * * ?") : "초 분 시 일 월 요일 년(선택)에 해당 메서드 호출

Lombok Annotation

@NoArgsConstructor

  • 기본생성자를 자동으로 추가한다.
  • 기본생성자의 접근 권한을 protected로 제한한다.
  • 생성자로 protected Posts() {}와 같은 효과
  • Entity Class를 프로젝트 코드상에서 기본생성자로 생성하는 것은 금지하고, JPA에서 Entity 클래스를 생성하는것은 허용하기 위해 추가한다.@AllArgsConstructor
  • 모든 필드 값을 파라미터로 받는 생성자를 추가한다.@RequiredArgsConstructor
  • final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 추가한다.
  • final: 값이 할당되면 더 이상 변경할 수 없다.

@Getter

  • Class 내 모든 필드의 Getter method를 자동 생성한다.

@Setter

  • Class 내 모든 필드의 Setter method를 자동 생성한다.
  • Controller에서 @RequestBody로 외부에서 데이터를 받는 경우엔 기본생성자 + set method를 통해서만 값이 할당된다.
  • 그래서 이때만 setter를 허용한다.
  • Entity Class에는 Setter를 설정하면 안된다.
  • 차라리 DTO 클래스를 생성해서 DTO 타입으로 받도록 하자

@ToString

  • Class 내 모든 필드의 toString method를 자동 생성한다.
  • 특정 필드를 toString() 결과에서 제외한다.
  • 클래스명(필드1이름=필드1값, 필드2이름=필드2값, …) 식으로 출력된다.

@EqualsAndHashCode

  • equals와 hashCode method를 오버라이딩 해주는 Annotation이다.
  • allSuper 속성을 통해 equals와 hashCode 메소드 자동 생성 시 부모 클래스의 필드까지 감안할지 안 할지에 대해서 설정할 수 있다.
  • 즉, callSuper = true로 설정하면 부모 클래스 필드 값들도 동일한지 체크하며, callSuper = false로 설정(기본값)하면 자신 클래스의 필드 값들만 고려한다.

@Builder

  • 빌더에 포함된다.

@Data

  • @Getter, @Setter, @EqualsAndHashCode, @AllArgsConstructor을 포함한 Lombok에서 제공하는 필드와 관련된 모든 코드를 생성한다.
  • 실제로 사용하지 않는것이 좋다.
  • 전체적인 모든 기능 허용으로 위험 존재

JPA Annotation

  • JPA를 사용하면 DB 데이터에 작업할 경우 실제 쿼리를 사용하지 않고 Entity 클래스의 수정을 통해 작업한다.

@Entity

  • 실제 DB의 테이블과 매칭될 Class임을 명시한다.
  • 즉, 테이블과 링크될 클래스임을 나타낸다.

Entity Class

  • 가장 Core한 클래스로 클래스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭한다.

Controller에서 쓸 DTO 클래스란??

Request와 Response용 DTO는 View를 위한 클래스로, 자주 변경이 필요한 클래스이다.

Entity 클래스와 DTO 클래스를 분리하는 이유는 View Layer와 DB Layer를 철저하게 역할 분리하기 위해서다.

테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request/ Response 클래스)는 자주 변경되므로 분리해야 한다.

@Table

  • Entity Class에 매핑할 테이블 정보를 알려준다.
  • Annotation을 생략하면 Class 이름을 테이블 이름 정보로 매핑한다.

@Id

  • 해당 테이블의 PK 필드를 나타낸다.

@GeneratedValue

  • PK의 생성 규칙을 나타낸다.
  • 가능한 Entity의 PK는 Long 타입의 Auto_increment를 추천
  • 스프링 부트 2.0에선 옵션을 추가하셔야만 auto_increment가 된다.
  • 기본값은 AUTO로, MySQL의 auto_increment와 같이 자동 증가하는 정수형 값이 된다.

@Column

  • 테이블의 컬럼을 나타내며, 굳이 선언하지 않더라도 해당 Class의 필드는 모두 컬럼이 된다.
  • @Column을 생략하면 필드명을 사용해서 컬럼명과 매핑
  • @Column을 사용하는 이유는, 기본값 외에 추가로 변경이 필요한 옵션이 있을 경우 사용한다.
  • 문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나(ex: title),
  • 타입을 TEXT로 변경하고 싶거나(ex: content) 등의 경우에 사용

Member Entity 예시

@Entity
@Getter
@RequiredArgsConstructor
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id", nullable = false, unique = true)
    private Long id;

    @Column(unique = true)
    private String username;

    private String password;

    @Column(unique = true)
    private String email;

    @OneToMany(mappedBy = "member")
    private final List<RegisteredEvent> events = new ArrayList<>();

    public Member(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }
}

구체적인 어노테이션 사용 예시

요청 매핑

MappingController

@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }
}

매핑 정보

@RestController

  • @Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.
  • @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다. 따라서 실행 결과로 ok 메세지를 받을 수 있다. @ResponseBody 와 관련이 있는데, 뒤에서 더 자세히 설명한다.

@RequestMapping("/hello-basic")

  • /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑한다.
  • 대부분의 속성을 배열[] 로 제공하므로 다중 설정이 가능하다. {"/hello-basic", "/hello-go"}

HTTP 메서드 매핑

/**
* method 특정 HTTP 메서드 요청만 허용  
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
    log.info("mappingGetV1");
    return "ok";
}
  • 만약 여기에 POST 요청을 하면 스프링 MVC는 HTTP 405 상태코드(Method Not Allowed)를 반환한다.

HTTP 메서드 매핑 축약

/**
 * 편리한 축약 애노테이션 (코드보기) 
 * @GetMapping
 * @PostMapping
 * @PutMapping
 * @DeleteMapping
 * @PatchMapping
 */
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
    log.info("mapping-get-v2");
    return "ok";
}
  • HTTP 메서드를 축약한 애노테이션을 사용하는 것이 더 직관적이다. 코드를 보면 내부에서 @RequestMapping 과 method 를 지정해서 사용하는 것을 확인할 수 있다.

PathVariable(경로 변수) 사용

/**
 * PathVariable 사용
 * 변수명이 같으면 생략 가능
 * @PathVariable("userId") String userId -> @PathVariable userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId={}", data);
    return "ok";
}

PathVariable 사용 - 다중

/**
 * PathVariable 사용 다중
 */
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}

특정 파라미터 조건 매핑

/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    log.info("mappingParam");
    return "ok";
}
  • 특정 파라미터가 있거나 없는 조건을 추가할 수 있다. 잘 사용하지는 않는다.

특정 헤더 조건 매핑

/**
 *특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
 */
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}
  • 파라미터 매핑과 비슷하지만, HTTP 헤더를 사용한다.

미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    log.info("mappingConsumes");
    return "ok";
}
  • HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다.
  • 만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.

미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

/**
 * Accept 헤더 기반 Media Type * produces = "text/html"
 * produces = "!text/html"
 * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}
  • HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
  • 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환한다.

요청 매핑 - API 예시

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {

    /**
     * - 회원 목록 조회:  GET        /users
     * - 회원 등록:      POST      /users
     * - 회원 조회:      GET         /users/{userId}
     * - 회원수정:       PATCH      /users/{userId}
     * - 회원 삭제:      DELETE     /users/{userId}
     */

    @GetMapping
    public String user(){
        return "get users";
    }

    @PostMapping
    public String addUser() {
        return "post user";
    }
    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId=" + userId;
    }

    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }

    @DeleteMapping("/{userId}")
    public String deleteUser(@PathVariable String userId) {
        return "delete userId=" + userId;
    }

}

HTTP 요청 - 기본, 헤더 조회

@Slf4j
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie
                          ) {
        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "ok";

    }
}
  • HttpMethod : HTTP 메서드를 조회한다. org.springframework.http.HttpMethod
  • Locale : Locale 정보를 조회한다.
  • @RequestHeader MultiValueMap<String, String> headerMap
    • 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
  • @RequestHeader("host") String host
    • 특정 HTTP 헤더를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값 속성: defaultValue
  • @CookieValue(value = "myCookie", required = false) String cookie
    • 특정 쿠키를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값: defaultValue

MultiValueMap

  • MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
  • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
    • keyA=value1&keyA=value2

HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
    • /url?username=hello&age=20
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식
  • POST - HTML Form
    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    • 예) 회원 가입, 상품 주문, HTML Form 사용
  • HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용, JSON, XML, TEXT
    • 데이터 형식은 주로 JSON 사용
    • POST, PUT, PATCH

요청 파라미터 - 쿼리 파라미터, HTML Form

HttpServletRequest 의 request.getParameter() 를 사용하면 다음 두가지 요청 파라미터를 조회할 수 있다.

GET, 쿼리 파라미터 전송

예시

http://localhost:8080/request-param?username=hello&age=20

POST, HTML Form 전송

예시

POST /request-param ...
content-type: application/x-www-form-urlencoded
username=hello&age=20

GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다. 이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

HTTP 요청 파라미터 - @RequestParam

@Slf4j
@Controller // @RestController 사용하면 HTTP 메시지 바디에 바로 입력한다. 때문에 @ResponseBody 필요없음.
public class RequestParamController {

    @RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        response.getWriter().write("ok");
    }

    // @Controller이면서 String이면 viewResolver가 나옴(논리 이름을 물리 이름으로 바꿔준)
    @ResponseBody // 또는 @ResponseBody를 제외하고 @Controller 대신에 @RestController일 때 -> response body에 직접 입력
    @RequestMapping("/request-param-v2")
    public String requestParamV2(
            @RequestParam("username") String memberName,
            @RequestParam("age") int memberAge) {

        log.info("username={}, age={}", memberName, memberAge);
        return "ok";

    }
    // 이게 더 권장됨
    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4(String username, int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }
}
  • @RequestParam : 파라미터 이름으로 바인딩
  • @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

@RequestParam의 name(value) 속성이 파라미터 이름으로 사용

  • @RequestParam("username") String memberName ->request.getParameter("username")

파라미터 필수 여부 - requestParamRequired

@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}
  • required 값이 true인 username을 안 넣었을 떄 400 에러

  • required 값이 false인 age를 안 넣었을 때 (int 값에 null이 들어가서 오류가 생김-> Integer 객체로 변경

  • @RequestParam.required

    • 파라미터 필수 여부
    • 기본값이 파라미터 필수( true )이다.
  • /request-param 요청

    • username 이 없으므로 400 예외가 발생한다.

💡 주의 - 파라미터 이름만 사용

/request-param?username=
위처럼 파라미터 이름만 있고 값이 없는 경우 빈문자("")로 통과

💡 주의 - 기본형(primitive)에 null 입력

/request-param 요청
@RequestParam(required = false) int age
null 을 int 에 입력하는 것은 불가능(500 예외 발생) 따라서 null 을 받을 수 있는 Integer 로 변경하거나, 또는 다음에 나오는 defaultValue 사용

기본 값 적용 - requestParamDefault

@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") Integer age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}
  • 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다. 이미 기본 값이 있기 때문에 required 는 의미가 없다.
  • defaultValue는 빈 문자의 경우에도 설정한 기본 값이 적용된다.

파라미터를 Map으로 조회하기 - requestParamMap

@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
    log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
}

파라미터를 Map, MultiValueMap으로 조회할 수 있다.

  • @RequestParam Map ,
    • Map(key=value)
  • @RequestParam MultiValueMap
    • MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])

파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자. -> 거의 파라미터의 값은 1개이긴 한다

HelloData, @ModelAttribute 적용

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    log.info("helloData={}", helloData);
    return "ok";
}

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

  • HelloData 객체를 생성한다.
  • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
    • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

프로퍼티 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다. username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername() 이 호출된다.

HTTP 요청 메시지 - 단순 텍스트

HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

  • 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
  • HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다.
@Slf4j
@Controller
public class RequestBodyStringController {

    // servlet에서 사용했던 방식
    @PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        // Byte 코드를 받을 때는 항상 어떤 방식으로 인코딩할 것인지 지정해야함.
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        log.info("messageBody={}", messageBody);

        response.getWriter().write("ok");
    }

    // java.io.InputStream을 받을 수 있음
    @PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
//        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messageBody={}", messageBody);

        responseWriter.write("ok");
    }
    ...
  }

스프링 MVC는 다음 파라미터를 지원한다.

  • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력

HttpEntity

// request랑 response를 둘다 조작할 수 있음
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {

    String messageBody = httpEntity.getBody();
    HttpHeaders messageHeaders = httpEntity.getHeaders();
    log.info("messageBody={}", messageBody);
    log.info("messageHeaders={}", messageHeaders);

    return new HttpEntity<>("ok");
}

스프링 MVC는 다음 파라미터를 지원한다.

HttpEntity: HTTP header, body 정보를 편리하게 조회

  • 메시지 바디 정보를 직접 조회
  • 요청 파라미터를 조회하는 기능과 관계 없음 @RequestParam X, @ModelAttribute X
    • 요청파라미터는 GET에 queryParameter 오는 것, content Type이 x-www-form-urlencoded인 경우에만 사용

HttpEntity는 응답에도 사용 가능

  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회X

HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공한다.

RequestEntity

  • HttpMethod, url 정보가 추가, 요청에서 사용

ResponseEntity

  • HTTP 상태 코드 설정 가능, 응답에서 사용
  • 'return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED)'

@RequestBody - 가장 많이 사용 됨

/**
 * @RequestBody
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
 * 
 * @ResponseBody
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
    log.info("messageBody={}", messageBody);
    return "ok";
}

@RequestBody

  • @RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.

⭐️이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다.

⭐️요청 파라미터 vs HTTP 메시지 바디

  • 요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody

@ResponseBody

  • @ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다. 물론 이 경우에도 view를 사용하지 않는다.

**HTTP 요청 메시지 - JSON

**

/**
 * {"username":"hello", "age":20}
 * content-type: application/json
 */

@Slf4j
@Controller
public class RequestBodyJsonController {
    private ObjectMapper objectMapper = new ObjectMapper();


    @PostMapping("/request-body-json-v1")
    public void requestBodyJsonV1(HttpServletRequest request,
        HttpServletResponse response) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messageBody={}", messageBody);
        HelloData data = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        response.getWriter().write("ok");
    }
    ...
}
  • HttpServletRequest를 사용해서 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환한다.
  • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해서 자바 객체로 변환한다.

@RequestBody 문자 변환

/**
 * @RequestBody
 * HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
 * @ResponseBody
 * - 모든 메서드에 @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws
    IOException {
    HelloData data = objectMapper.readValue(messageBody, HelloData.class);
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return "ok";
}
  • @RequestBody 를 사용해서 HTTP 메시지에서 데이터를 꺼내고 messageBody에 저장한다.
  • 문자로 된 JSON 데이터인 messageBody 를 objectMapper 를 통해서 자바 객체로 변환한다.

@RequestBody 객체 변환

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type: application/json)
 */
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}

@RequestBody 객체 파라미터

  • @RequestBody HelloData data
  • @RequestBody 에 직접 만든 객체를 지정할 수 있다.

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다. HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해주는데, V2에서 했던 작업을 대신 처리해준다.(HelloData data = objectMapper.readValue(messageBody, HelloData.class);)

@RequestBody는 생략 불가능 @ModelAttribute 에서 학습한 내용을 떠올려보자.

스프링은 @ModelAttribute , @RequestParam 과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용한다.

  • String , int , Integer 같은 단순 타입 = @RequestParam
  • 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

따라서 이 경우 HelloData에 @RequestBody 를 생략하면 @ModelAttribute 가 적용되어버린다.

HelloData data -> @ModelAttribute HelloData data 따라서 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.

주의💡
HTTP 요청시에 content-type이 application/json인지 꼭! 확인해야 한다. 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다

HttpEntity

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(@RequestBody HttpEntity<HelloData> httpEntity) {
    HelloData data = httpEntity.getBody();
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return "ok";
}
/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-
type: application/json)
 *
 * @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
(Accept: application/json)
 */
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return data;
}

응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다. 물론 이 경우에도 HttpEntity 를 사용해도 된다.

@RequestBody 요청

  • JSON 요청 -> HTTP 메시지 컨버터 -> 객체

@ResponseBody 응답

  • 객체 -> HTTP 메시지 컨버터 -> JSON 응답

HTTP 응답 - 정적 리소스, 뷰 템플릿

스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.

  1. 정적 리소스
    • 예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다. ( /static , /public , /resources , /META-INF/resources )
    • 정적 리소스 경로: src/main/resources/static
  2. 뷰 템플릿 사용
    • 예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
    • 뷰 템플릿 경로: src/main/resources/templates
  3. HTTP 메시지 사용
    • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

뷰 템플릿을 호출하는 컨트롤러

@Controller
public class ResponseViewController {

    @RequestMapping("/response-view-v1")
    public ModelAndView responseViewV1() {
        ModelAndView mav = new ModelAndView("response/hello")
                .addObject("data", "hello");

        return mav;
    }
    // @ResponseBody -> "response/hello"가 그대로 출력되버림
    @RequestMapping("/response-view-v2")
    public String responseViewV2(Model model) {
        model.addAttribute("data", "hello!");
        // @Controller에서 String 반환하면 return 값이 view의 물리적 이름이 됨
        return "response/hello";
    }

    @RequestMapping("/response/hello")
    public void responseViewV3(Model model) {
        model.addAttribute("data", "hello!!");
    }
}

String을 반환하는 경우 - View or HTTP 메시지

  • @ResponseBody 가 없으면 response/hello 로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.
  • @ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력된다.

여기서는 뷰의 논리 이름인 response/hello 를 반환하면 다음 경로의 뷰 템플릿이 렌더링 되는 것을 확인할 수 있다.

  • 실행: templates/response/hello.html

Void를 반환하는 경우

  • @Controller 를 사용하고, HttpServletResponse , OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용
    • 요청 URL: /response/hello
    • 실행: templates/response/hello.html
  • 참고로 이 방식은 명시성이 너무 떨어지고 이렇게 딱 맞는 경우도 많이 없어서, 권장하지 않는다.

HTTP 메시지

  • @ResponseBody , HttpEntity 를 사용하면, 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 응답 데이터를 출력할 수 있다.

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

@Slf4j
@Controller
public class ResponseBodyController {

    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }

    /**
     * HttpEntity, ResponseEntity(Http Status 추가)
     * @return
     */

    @GetMapping("/response-body-string-v2")
    public ResponseEntity<String> responseBodyV2() {
        return new ResponseEntity<>("ok", HttpStatus.OK);
    }


    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3() {
        return "ok";
    }

    // 상태코드를 조건문으로 프로그래밍할 때는 ResponseEntity 아니면 @ResponseStatus(HttpStatus.OK)로 상태코드 작성
    @ResponseBody
    @GetMapping("/response-body-json-v1")
    public ResponseEntity<HelloData> responseBodyJsonV1() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);

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

    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);

        return helloData;
    }
}

responseBodyV1

  • 서블릿을 직접 다룰 때 처럼 HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 ok 응답 메시지를 전달한다.
  • response.getWriter().write("ok")

responseBodyV2

  • ResponseEntity 엔티티는 HttpEntity 를 상속 받았는데, HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다. ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.
  • HttpStatus.CREATED 로 변경하면 201 응답이 나가는 것을 확인할 수 있다.

responseBodyV3

  • @ResponseBody 를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다. ResponseEntity 도 동일한 방식으로 동작한다.

responseBodyJsonV1

  • ResponseEntity 를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.

responseBodyJsonV2

  • ResponseEntity 는 HTTP 응답 코드를 설정할 수 있는데, @ResponseBody 를 사용하면 이런 것을 설정하기 까다롭다.
  • @ResponseStatus(HttpStatus.OK) 애노테이션을 사용하면 응답 코드도 설정할 수 있다.
  • 물론 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. 프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity 를 사용하면 된다.

@RestController

  • @Controller 대신에 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다.
  • 따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다. 이름 그대로 Rest API(HTTP API)를 만들 때 사용하는 컨트롤러이다.

참고로 @ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 에노테이션 안에 @ResponseBody 가 적용되어 있다.

Reference