Spring - Controller Annotation

유재학·2022년 11월 14일
0

Controller Annotation

이번 포스트에서는 여러가지 어노테이션 중 Controller에서 주로 사용하는 어노테이션은 어떤 것들이 있는지, 어떤 기능을 하는지에 대해서 다뤄보려고 한다.

@RequestParam

@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)
                        
                        ...
}

@RequestParam 어노테이션에는 name, required, defaultValue라는 속성이 있다.

  • name : 요청 파라미터 이름으로 만약 파라미터 이름을 주지 않으면 매개변수의 이름과 동일한 파라미터를 찾는다.
  • required : 요청 파라미터의 필수 여부를 나타내며 기본 값은 true이다. 만약 존재하지 않으면 404 Bad Request
  • defaultValue : 요청 파라미터가 필수가 아닌 경우 기본값을 지정할 수 있다.

모든 요청 파라미터를 가지고 오고 싶은 경우 Map<String,String> 을 사용하여 데이터를 받으면 되고 하나의 요청 파라미터에 여러 값을 가지고 오고 싶으면 List<String>을 사용하면 된다.
@RequestParam은 생략이 가능하다.

@PathVariable

@PathVariable 어노테이션은 URL 변수에서 파라미터를 받는 경우 사용한다.

예를들어 localhost:8080/login/a 이런 URL이 있을 때 다음과 같이 사용할 수 있다.

@GetMapping("/login/{uId}")
public String findMemberById(@PathVariable String uId) {
	   ...
}

속성으로는 value, required가 있다.

  • value : @PathVariable(value = "uId") URL 변수 값을 지정한다.
  • required : 요청 파라미터의 필수 여부를 나타내며 기본 값은 true이다. 만약 존재하지 않으면 404 Bad Request

여러개의 URL 변수가 있는 경우 @PathVariable Map<String, String> map을 사용하여 파라미터를 사용할 수 있다. 그 외에도 다양한 경우가 존재하는데 이에 대한 예시는 다음과 같다.

  • 선택적 URL 변수의 경우
@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

@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는 빈 검증기를 통해 객체 제약조건을 검증하도록 하는 어노테이션이다.

데이터 바인딩시에 데이터 바인딩 → @Valid 확인 → BindingResult 에러 담기의 순서로 진행되어 데이터들의 유효성을 검사할 수 있다. (스프링에서는 LocalValidatorFactoryBean이 제약조건을 검증한다.)

@Valid는 기본적으로 Controller에서만 동작한다.

다른 계층에서 검증이 되지 않으며 다른 계층에서 파라미터를 검증하기 위해서는 @Validatied를 사용해야 한다.

@Valid는 어떻게 동작할까?

모든 Request는 Dispatcher Servlet을 통해 컨트롤러로 전달된다. 전달 과정에서 컨트롤러 메소드를 객체로 만들어주는 ArgumentResolver가 동작하고 @ValidArgumentResolver에 의해 처리가 된다. 하지만 만약 @ModelAttribute를 사용한다면 ModelAttribueMethodProcessor에 의해 @Valid가 처리된다.

만약 검증에 오류가 있다면 MethodArgumentNotValidException이 발생하고 Dispatcher Servlet에 기본으로 등록된 자바 Exception Resolver인 DefaultHandlerExceptionResolver에 의해 400 BadRequest 에러를 발생한다.

@Valid를 위한 어노테이션 정리

  • 테스트 관련
    • @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를 선언해주어야 함

@Validated는 어떻게 동작할까?

특정 ArgumnetResolver에 의해 유효성 검사가 진행되었던 @Valid와 달리 @Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위한 인터셉터(MethodValidationInterceptor)가 등록된다. 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행하고 만약 검증에 오류가 있으면 ConstraintViolationException 예외를 발생하는 방식으로 동작한다.

@RequestBody, ResponseBody

@RequestBody

웹에서 화면 전환없이 일부 요소만 비동기 통신 할 경우 사용한다. (보통 JSON으로 비동기 통신을 한다.)

클라이언트가 서버로 요청 메시지를 보낼 때 본문에 데이터를 담아서 보낼 경우 요청 메시지의 바디 내용 전체를 자바 객체로 변환해서 파라미터로 전달한다.

@ResponseBody

@RequestBody처럼 웹에서 화면 전환없이 일부 요소만 비동기 통신 할 경우 사용한다.

서버에서 클라이언트로 응답을 보낼 때에도 본문에 데이터를 담아서 보낼 경우 자바 객체를 응답 메세지의 본문 객체로 변환해서 클라이언트에게 전달한다.

profile
github : https://github.com/kiaeh2323 , email : kiaeh9269@gmail.com

0개의 댓글