Controller Annotation
이번 포스트에서는 여러가지 어노테이션 중 Controller에서 주로 사용하는 어노테이션은 어떤 것들이 있는지, 어떤 기능을 하는지에 대해서 다뤄보려고 한다.
@RequestParam
예를 들어 localhost:8080/login?uId=a&uPw=a1234
같은 URL이 있는 경우 ?뒤에 있는 uId=a&uPw=a1234
이 부분이 Request에서 들어온 파라미터 값인데 이 값을 Controller에서 사용하고 싶은 경우 사용한다.
코드로 보면 다음과 같다.
@PostMapping("/login")
public String doLogin(@RequestParam String uId(name = "uId", @RequestParam String uPw,
@RequestParam(required = false, defaultValue = "") String save)
...
}
모든 요청 파라미터를 가지고 오고 싶은 경우 Map<String,String>
을 사용하여 데이터를 받으면 되고 하나의 요청 파라미터에 여러 값을 가지고 오고 싶으면 List<String>
을 사용하면 된다.
@RequestParam은 생략이 가능하다.
@PathVariable
예를들어 localhost:8080/login/a
이런 URL이 있을 때 다음과 같이 사용할 수 있다.
@GetMapping("/login/{uId}")
public String findMemberById(@PathVariable String uId) {
...
}
@PathVariable(value = "uId")
URL 변수 값을 지정한다.여러개의 URL 변수가 있는 경우 @PathVariable Map<String, String> map
을 사용하여 파라미터를 사용할 수 있다. 그 외에도 다양한 경우가 존재하는데 이에 대한 예시는 다음과 같다.
@ResponseBody
@GetMapping(value = { "/members/withrequired", "/members/withrequired/{uId}" })
public String getMemberByIdWithRequired(@PathVariable(required = false) String uId) {
return (uId == null) ? "none" : uId;
}
람다식과 함께 사용하는 경우
@ResponseBody
@GetMapping(value = { "/members/withrequired", "/members/withrequired/{uId}" })
public String getMemberByIdWithRequired(@PathVariable Optional<String> uId) {
if (uId.isPresent()) {
return uId.get();
} else {
return "none";
}
}
@ModelAttribute
메소드 레벨에서 사용될 경우 컨트롤러가 호출되기 전에 공통적으로 호출되어서 뷰에 호출할 데이터를 저장하거나, 뷰로 넘어가기 전에 공통적인 로직(예외처리/로깅)이 있는 경우 사용한다.
// 모든 컨트롤러가 호출되기 전에 전역적으로 호출되는 메서드
// 보통 공통적인 예외처리나 로깅 찍을 때 사용
@ControllerAdvice
public class GlobalController {
@ModelAttribute("serverTime")
public String getServerTime() {
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
return localDateTime.toString();
}
}
인수 레벨에서 사용될 경우 객체를 자동 생성해서 요청 파라미터에서 들어온 데이터들을 자동 바인딩하는 용도로 사용한다.
@PostMapping("/signup")
public String doSignup(@ModelAttribute MemberDTO memberDTO,
Model model, HttpServletRequest request, HttpSession session, HttpServletResponse response) {
String view = signupPage(session);
Status respStatus = Status.FAIL;
boolean res = memberService.signup(memberDTO.getuId(), memberDTO.getuPwStr(), memberDTO.getuEmail());
if (res) {
view = "redirect:/";
respStatus = Status.SUCCESS;
}
session.setAttribute("signup", respStatus);
return view;
}
MemberDTO 에는 uId,uPw,uEmail 필드가 존재하는데 자동 바인딩시 생성자를 통해 객체를 생성하고 요청 파라미터 이름과 동일한 필드가 있는지 확인하여 해당 필드의 setter
를 호출한다.
만약 @ModelAttribute 에서 사용자 입력 데이터와 모델의 데이터 타입이 맞지 않을 경우 500 에러가 발생한다. 보통 @ModelAttribute을 사용하게 될 경우 BindingResult 객체를 같이 입력하여 모델 바인딩시 오류가 없었는지 확인한다. 만약 오류가 발생했을 때는 오류 페이지를 보여주거나 다시 뷰 단으로 redirect 하여 사용자 요청 데이터를 다시 입력받게 할 수 있다.
@PostMapping("/signup")
public String doSignup(@ModelAttribute WrongMemberDTO memberDTO, BindingResult bindingResult,
Model model, HttpServletRequest request, HttpSession session, HttpServletResponse response) {
String view = signupPage(session);
Status respStatus = Status.FAIL;
if (bindingResult.hasErrors()) {
bindingResult.getAllErrors().forEach(error -> System.out.println(error));
session.setAttribute("signup", respStatus);
return view;
}
boolean res = memberService.signup(memberDTO.getuId().toString(), memberDTO.getuPwStr(), memberDTO.getuEmail());
if (res) {
view = "redirect:/";
respStatus = Status.SUCCESS;
}
session.setAttribute("signup", respStatus);
return view;
}
@Valid
데이터 바인딩시에 데이터 바인딩 → @Valid 확인 → BindingResult 에러 담기의 순서로 진행되어 데이터들의 유효성을 검사할 수 있다. (스프링에서는 LocalValidatorFactoryBean이 제약조건을 검증한다.)
다른 계층에서 검증이 되지 않으며 다른 계층에서 파라미터를 검증하기 위해서는 @Validatied를 사용해야 한다.
모든 Request는 Dispatcher Servlet을 통해 컨트롤러로 전달된다. 전달 과정에서 컨트롤러 메소드를 객체로 만들어주는 ArgumentResolver
가 동작하고 @Valid도 ArgumentResolver
에 의해 처리가 된다. 하지만 만약 @ModelAttribute를 사용한다면 ModelAttribueMethodProcessor
에 의해 @Valid가 처리된다.
만약 검증에 오류가 있다면 MethodArgumentNotValidException
이 발생하고 Dispatcher Servlet에 기본으로 등록된 자바 Exception Resolver인 DefaultHandlerExceptionResolver
에 의해 400 BadRequest 에러를 발생한다.
@AssertFalse
- Assert 문이 거짓인지@AssertTrue
- Assert 문이 참인지@NotNull
- null이 아닌지 확인@Null
- null인지 확인 test == null
@Nullable
- null 허용@NotEmpty
- null, “” 이 아닌지 확인 test.size() == 0
@NotBlank
- null, “”, “ “ 이 아닌지 확인 test.strip().size() == 0
@Size(min = ..., max = ...)
min ~ max 길이의 문자열 / 배열인지 확인@Digits(integer = ..., fraction = ...)
integer
- 허용 가능한 정수 자리수fraction
- 허용 가능한 소수점 이하 자리수@DecimalMax
- 지정 값 이하의 실수인지@DecimalMin
- 지정 값 이상의 정수인지@Max
- 숫자가 지정값 이하인지@Min
- 숫자가 지정 값 이상인지@Positive
- 양수만 가능@PositiveOrZero
- 양수와 0만 가능@Negative
- 음수만 가능@NegativeOrZero
- 음수와 0만 가능@DateTimeFormat(pattern = "yyyyMMdd")
- 날짜 형식이 맞는지 확인@Past
- 과거 날짜인지 확인@Future
- 미래 날짜인지 확인@Pattern
- 정규식을 만족하는지 확인@Email
- 이메일 형식인지 확인@Validated
입력 파라미터의 유효성 검증은 최대한 컨트롤러에서 처리하는 것이 좋다. 하지만 불가피하게 다른 곳에서 파라미터 검증이 필요한 경우가 있다. 스프링에서는 이런 경우를 위해 AOP기반으로 메소드 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공한다.
@Validated를 사용하면 컨트롤러,서비스,레포지토리 등 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다. 클래스에는 유효성 검증 인터셉터를 등록하도록 @Validated를, 검증을 진행할 메소드에는 @Valid를 선언해주어야 함
특정 ArgumnetResolver에 의해 유효성 검사가 진행되었던 @Valid와 달리 @Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위한 인터셉터(MethodValidationInterceptor)가 등록된다. 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행하고 만약 검증에 오류가 있으면 ConstraintViolationException 예외를 발생하는 방식으로 동작한다.
@RequestBody, ResponseBody
웹에서 화면 전환없이 일부 요소만 비동기 통신 할 경우 사용한다. (보통 JSON
으로 비동기 통신을 한다.)
클라이언트가 서버로 요청 메시지를 보낼 때 본문에 데이터를 담아서 보낼 경우 요청 메시지의 바디 내용 전체를 자바 객체로 변환해서 파라미터로 전달한다.
@RequestBody처럼 웹에서 화면 전환없이 일부 요소만 비동기 통신 할 경우 사용한다.
서버에서 클라이언트로 응답을 보낼 때에도 본문에 데이터를 담아서 보낼 경우 자바 객체를 응답 메세지의 본문 객체로 변환해서 클라이언트에게 전달한다.