스프링 MVC를 보기 전에 로깅에 대해 간단하게 알아보고 가자.
스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리(spring-boot-starter-logging
)가 함께 포함됨.
로그 라이브러리는 Logback, Log4J, Log4J2등 수많은 라이브러리가 있는데, 이를 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리다.
SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 라이브러리를 선택하면 됨.
스프링 부트가 기본적으로 제공하는 Logback을 보통 사용한다.
// 로그 선언의 3가지 방법
private final Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j
//로그 사용법
log.trace("trace log = {}" , name);
log.debug("debug log = {}" , name);
log.info("info log = {}" , name);
log.warn("warn log = {}" , name);
log.error("error log = {}", name);
로그가 출력된 콘솔을 보자
올바른 로그 사용법
log.info("info log=" + name);
log.trace("trace my log = " + name);
log.trace("trace my log = {}", name);
@RestController
- 반환 값으로 뷰를 찾는 것이 아니라 HTTP 메시지 바디에 바로 입력 한다. @Controller
와 @ResponseBody
를 조합해 놓은 애노테이션이다. 이렇게 하면 HTTP API를 만들때 편하게 사용 가능.
HTTP 메서드
@RequestMapping
에 method 속성으로 HTTP메서드를 따로 지정하지 않으면 HTTP 메스더와 무관하게 호출된다. 모두 허용 (GET,HEAD, POST, PUT, PATCH, DELETE)
만약 HTTP 메서드를 매핑해놨는데, 다른 HTTP 메서드를 요구하면 405 오류를 낸다.
@GetMapping
@PostMapping
@DeleteMapping
@PutMapping
@PatchMapping
의 편리한 축약 애노테이션이 있다. 꼭@RequestMapping
을 사용해야 되는 상황이 아니면 이런 애노테이션을 사용하자.
PathVariable
/**
*@PathVariable("userId") String data -> @PathVariable String userId
*
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}" , data);
return "ok";
}
@PathVariable
의 이름과 파라미터 이름이 같으면 생략할 수 있다. 보통 같게해서 생략하는 편@RequestMapping
은 url경로를 템플릿화 할 수 있는데, @PathVariable
을 사용하면 매칭 되는 부분을 편리하게 조회 가능.@PathVariable
자체는 생략 불가능.다중 @PathVariable
도 사용 가능
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
쿼리 파라미터를 조건으로 사용 가능
@GetMapping(value = "/mapping-param", params="mode=debug")
params="mode" //모드가 있으면
params="!mode" //모드가 없으면
params="mode=debug" //모드파라미터가 debug일때
params="mode!=debug" //모드파라미터가 debug가 아닐때
params ={"mode=debug", "data=good"} // mode가 debug거나, data가 good이거나..
이런식으로 param에 조건을 추가해 놓을 수 있다. 다만 요즘에는 잘 사용하지 않는다.
특정 헤더로 추가 메핑
@GetMapping(value="/mapping-headers", headers="mode=debug")
headers="mode" //모드가 있으면
headers="!mode" //모드가 없으면
headers="mode=debug" //모드파라미터가 debug일때
headers="mode!=debug" //모드파라미터가 debug가 아닐때
이런 기능들이 있다.
Content-Type 헤더 기반 추가 매핑 Media Type
@PostMapping(value="mapping-consume", consumes="application/json")
consumes="application/json"
consumes="!application/json"
consumes="application/"
consumes="\/"
MediaType.APPLICATION_JSON_VALUE
produces="application/json"
produces="!application/json"
produces="application/"
produces="\/"
실제로는 문자로 적는것보다는 상수로 정해져있는 MediaType.APPLICATION_JSON_VALUE같은 것으로 사용하자.
consume(클라이언트가 보내는 타입) , product(서버 쪽에서 보내온 데이터 타입)
회원목록조회 : GET '/users'
회원 등록 : POST '/users'
회원 조회 : GET '/users/{userId}'
회원 수정 : PATCH '/users/{userId}'
회원 삭제 : DELETE '/users/{userId}'
//현재 공통 url path인 "/users"는 클래스 단위에 @RequestMapping("/users")를 해놓으면
//밑의 Mapping들에서 생략 가능
@GetMapping("/users")
public String user() {
return "get users";
}
@PostMapping("/users")
public String addUser() {
return "post users";
}
@GetMapping("/users/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId =" +userId;
}
@PatchMapping("/users/{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId" + userId;
}
@DeleteMapping("/users/{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId = " + userId;
}
애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.
@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) {
return "ok";
}
HttpMethod : HTTP 메서드를 조회한다. org.springframework.http.HttpMethod
Locale : Locale 정보를 조회한다.(가장 우선순위가 높은 locale이 나옴)
@RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
@RequestHeader("host") String host
특정 HTTP 헤더를 조회한다.
@CookieValue(value = "myCookie", required = false) String cookie
이 외에 여러가지 파라미터들을 확인 하고 싶다면 Spring의 공식문서에 Method Arguments 부분에서 확인가능. Return values도 다양하게 넣을 수 있다. 이도 공식문서에서 확인 가능
3가지 방법
GET - 쿼리 파라미터, POST - HTML Form, HTTP message body
GET이든 POST든 방식이 같기 때문에 "요청 조회 방식" 이라 한다.
@GetMapping
@ResponseBody
public String requestParam(@RequestParam String username) {
return "ok";
}
파라미터에 오는 변수들을 바로 쿼리 파라미터들과 연관시켜줌.
변수명과 파라미터 네임이 같으면 @RequestParam("name")에서 name쪽 생략 가능
@RequestParam
자체도 단순 타입이고(int, String, Integer long 등등), 요청 파라미터와 이름이 같으면 생략 가능하다. 즉 String username만 써줘도 username이라는 쿼리 파라미터가 들어오면 적용된다
필수 여부도 설정 가능. @RequestParam(required =true), 기본값은 true. false면 필수값x
required=false를 설정하고 int age의 값을 보내지 않았을 경우? => 오류가 난다. 왜냐면 값을 넣지 않으면 자동으로 null이 들어가는데 int는 null을 사용하지 못하기 때문에. Integer를 사용하자.
null과 ""는 다르다. 빈문자는 값이 들어온 것 과 같다.
defaultValue = "설정가능" , 값이 없으면 그냥 기본적으로 defaultValue의 값이 쿼리 파라미터에 나오게 하겠다는 뜻.
defaultValue의 특이한점은 null뿐만 아니라 빈문자열도 처리해준다는 것이다. 빈문자열을 넣었을 경우에는 값이 없는것으로 생각해서 defaultValue를 출력해준다는 뜻이다.
파라미터를 Map, MultiValueMap으로 조회할 수 있다. Map은 key=value 값, MultiValueMap은 key=value1, value2, ...
실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어야 된다. 이때 스프링은 @ModelAttribute
라는 기능을 제공해 객체와 파라미터의 매핑을 자동으로 쉽게 할 수 있도록 도와준다.
@ResponseBody
@GetMapping
public String modelAttribute(@ModelAttribute User user) {
log.info("user name = {}", user.userName);
log.info("user name = {}", user.userId);
return "ok";
}
@ModelAttribute
를 사용하면 일일이 String userName, Integer userId를 @RequsetParam
으로 안받아도 된다.
요청 파라미터의 이름으로 User
객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 Setter를 호출해서 파라미터의 값을 입력(바인딩)한다.
잘못된 타입을 입력하면BindException
이 발생한다.
@ModelAttribute
도 생략 가능하다. 앞에서 단순 타입들은 @RequestParam
이 붙는다고 했다. 이런 단순 타입을 제외한 나머지에는 @ModelAttribute
가 붙는다.(argument resolver로 지정해둔 타입 외)
@ModelAttribute
는 Model 파라미터도 자동으로 넘겨준다.
ex) model.addAttribute("user", user); 안적어줘도 알아서 넘어감.
번외 = PRG
RedirectAttributes 클래스 사용.
redirect시에는 정보가 유지되지 않는다. 따라서 정보를 주기 위해서는 다른 방식을 사용 해야되는데 이때 사용 할 수 있는 클래스가 RedirectAttributes이다.
redirectAttribute.addAttribute("userId", user.getUserId);
이런식으로 넘겨준다면 redirect url에 pathvariable을 사용하고 있다면 그곳의 값으로 넘어가고, 없는 것은 쿼리 파라미터로 자동으로 넘어간다.
redirect 시에 return "redirect:/item/" + user.getUserId; 이런식으로 문자열 연산을 하면 안되고, redirectAttribute를 사용하도록 하자.
return "redirect:/item/{userId}"; 이런식으로 사용하도록 하자.
HTTP message body에 데이터를 담아 요청, 주로 JSON 사용한다.
요청 파라미터와 다르게 HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam
, @ModelAttribute
를 사용할 수 없다.
// HttpEntity<T>를 이용하는 방법
@PostMapping
public HttpEntity<String> requestBodyString(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
return new HttpEntity<>("ok");
}
//@RequestBody를 사용한 방법
@PostMapping
@ResponseBody
public String requesetBodyString(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
HttpEntity를 상속받은 RequestEntity, ResponseEntity도 있다. HttpEntity<> 대신 사용 가능. ResponseEntity는 ResponseEntity<>("ok", HttpStatus.CREATED); HTTP의 상태도 보낼 수 있다.
@RequestBody : HTTP 메시지 바디 정보를 편리하게조회할 수 있다. 헤더 정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용하면 된다.
@ResponseBody : 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달 할 수 있다.
HTTP API에서 주로 사용하는 JSON을 알아보자
// JSON 형식
{
"username":"hello"
"age":20
}
content-type: application/json
@PostMapping
@ResponseBody
public String requestBodyJson(@RequestBody User user) {
log.info("username = {}, age={}", user.getUsername(), user.getAge());
return "ok";
}
// 위의 JSON형식에 맞춰서 정보가 가게 된다. 사실 위의 단순 텍스트와 형식상은 크게 다를게 없다.
// JSON 형식을 리턴 타입으로 줄 수도있다.
@PostMapping
@ResponseBody
public User requestBodyJson(@RequestBody User user) {
log.info("username = {}, age={}", user.getUsername(), user.getAge());
return user;
}
// 리턴 타입으로 위의 JSON과 같은 타입을 준다.
// HttpEntity를 사용해도 된다.
@ResponseBody
@PostMapping
public String requestBodyJson(HttpEntity<User> httpEntity) {
User user = httpEntity.getBody();
log.info("username = {}, age={}", user.getUsername(), user.getAge());
return "ok"
}
스프링에서 응답 데이터를 만드는 방법은 크게 3가지
/static
, /public
, /resources
, /META-INF/resources
src/main/resources
는 리소스를 보관하는 곳. 클래스패스의 시작 경로이다.src/main/resources/templates
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
</html>
간단하게 만들어본 템플릿. Thymeleaf 사용. @GetMapping
public String responseView(Model model) {
model.addAttribute("data", "hello");
return "hello"; // 실제 경로 template/hello.html
}
//void일시 path와 일치하는 templates을 찾아서 화면에 보여줌.
//명시성이 너무 떨어지고 , 딱 맞아 떨어지는 상황이 별로 없어 권장하지 않음.
@GetMapping("/hello")
public void responseVieww(Model model) {
model.addAttribute("data", "hello");
}
Thymeleaf 라이브러리를 추가 해뒀을 시 스프링 부트가 자동으로 ThymeleafViewResolver
와 필요한 스프링 빈들을 등록. 그리고 다음과 같은 설정도 사용한다.
application.properties
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
위의 설정은 바꿀수 있다.
HTTP API, 메시지 바디에 직접 입력
- HTTP API를 제공하는 경우에는 데이터를 전달해야 하므로 HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
- 정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우를 말한다.
@GetMapping
public ResponseEntity<String> responseBody() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseBody
@GetMapping
public String responseBody() {
return "ok";
}
// 위의 두 게는 단순하게 텍스트 보내는 것.
@GetMapping
public ResponseEntity<User> responseBodyJson() {
User user = new User("userA", 20);
return new ResponseEntity<>(user, HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping
public User responseBody() {
User user = new User("userA", 20);
return user;
}
ResponseEntity의 경우 HTTP 상태 메시지를 동적으로 바꿀 수 있다. 하지만 @ResponseStatus를 사용하는 경우에는 그런 방법은 불가능 하다. 따라서 일반적으로는 애노테이션을 쓰다가 동적인 상태변화가 필요하다면 ResponseEntity를 사용하면 된다.
위의 코드들을 살펴보면 계속 @ResponseBody를 적고있다. 이때 클래스에 @RestController를 붙이면 된다. @Controller + @ResponseBody를 바로 클래스단위에 전부 적용 시켜 주는 것이다.
뷰 템플릿으로 HTMl을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
@ResponseBody 사용시
viewResolver 대신에 HttpConverter가 동작
기본 문자 처리 : StringHttpMessageConverter
기본 객체 처리 : MappingJackson2HttpMessageConverter
byte 처리 등등 기타 여려 HttpMessageConverter가 기본적으로 등록되어 있음.
스프링 MVC의 경우에는 다음과 같은 경우에 HTTP 메시지 컨버터를 적용한다.
HTTP 응답 : @ReqeustBody , HttpEntity(RequestEntity)
HTTP 요청 : @ResponseBody, HttpEntity(ResponseEntity)
HTTP 메시지 컨버터는 요청과 응답에 둘 다 사용된다.
스프링 부트의 대표적인 기본 메시지 컨버터
0 = ByteArrayMessageConverter
1 = StringHttpMessageConverter
2 = MaapingJackson2HttpMessageConverter
ByteArrayMessageConverter
StringHttpMessageConverter
MaapingJackson2HttpMessageConverter
:
HTTP 요청 데이터 읽기 - @ReuqestBody, HttpEntity 파라미터 사용
HTTP 응답 데이터 생성 - @ResponseBody, HttpEntity로 값이 반환
HTTP 메시지 컨버터는 스프링 MVC 어디에서 사용되는 것일까?
애노테이션 기반의 컨트롤러는 매우 다양한 파라미터 사용 가능.(HttpServletRequest, Modele, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity등..)
이렇게 다양하게 처리가 가능한 이유는 바로 Argument Resolver
덕분이다.
컨트롤러를 처리하는 RequestMappingHandlerAdaptor가 바로 이 ArgumentResolver를 호출해서 컨트롤러가 필요로하는 다양한 파라미터 값을 생성. 이렇게 준비된 값을 컨트롤러를 호출하면 값을 반환해 넘겨줌
스프링은 30개가 넘는 Argument Resolver
를 기본적으로 제공한다
(풀 네임 : HandlerMethodArgumentResolver
)
ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것이다.
HandlerMethodReturnValueHandler
를 줄여서 부르는 것. ArgumentResolver와 비슷한데, 이것은 응답 값을 반환하고 처리.
String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 이 ReturnValueHandler 덕분이다.
마찬가지로 ReturnValueHandler가 응답값을 반환하고 처리하기위해 이용하는것이 HTTP 메시지 컨버터 이다.
스프링은 ArgumentResolver와 ReturnValueHandler, HTTPMessageConverter를 인터페이스로 제공한다. 즉 우리가 기능을 확장 할 수있다는 것이다. (다만 필요한 기능은 거의 다 구현되있어서 확장 할 일이 거의 없다고 보면 된다.)
기능 확장은 WebMvcConfigurer
을 상속받아 스프링 빈으로 등록하면 된다.
참고 : 본 글은 김영한님의 스프링 강의를 정리한 것이다.