SMALL

HandlerMethodArgumentResolver 란

 

HandlerMethodArgumentResolver는 Controller Method에서 특정 조건에 맞는 파라미터가 있을 때 원하는 값을 바인딩해주는 인터페이스이다.

 

Spring 공식 문서에는 아래와 같이 설명되어있다.

Strategy interface for resolving method parameters into argument values in the context of a given request.

주어진 요청으로부터, method의 parameter를 argument로 주입해주는 전략 패턴의 interface

 

Spring MVC를 작성할때, Controller에 정의한 Method들에도 @PathVariable, @RequestParam을 사용하는데 이러한 것들도 모두 HandlerMethodArgumentResolver를 구현하여 작성한 기능들이다.

 

Spring Example

위의 코드는 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter의 코드로 Spring MVC에서 기본적으로 등록된 MethodArgumentResolver들이다.

 

간단하게 많이 사용하는 MethodArgumentResolver를 살펴보면 아래와 같다.

  • PathVariableMethodArgumentResolver
    • @PathVariable
  • RequestParamMethodArgumentResolver
    • @ReqeustParam

 

Custom HandlerMethodArgumentResolver 

 

@Controller
public class indexController{
    private final HttpSession httpSession;
    @GetMapping("/")
    public String index(Model model){
        User user = (User) httpSession.getAttribute("user");
        if(user != null){
            model.addAttribute("userName", user.getName());
        }
        return "index";
    }
}

위의 예시에서 HttpSession에서 부터 User를 가져오는 코드를 User가 필요한 모든 Controller의 Method에 작성하는 일은 중복 코드가 발생하고, 비효율적이다.

 

이러한 비효율성을 HandlerMehtodArguementResolver를 통해서 해결해보자.

 

HandlerMehtodArguementResolver

@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { 
// 조건에 맞는 경우 메소드가 있다면 구현체가 지정한 값으로 해당 메소드의 파라미터를 넘길 수 있다
    private final HttpSession httpSession;

    @Override
    public boolean supportsParameter(MethodParameter parameter) { 
    // 컨트롤러 메서드의 특정 파라미터를 지원하는지 판단 
    // 파라미터 클래스 타입이 SessionUser인 경우 true 반환 
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());

        return isUserClass;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            // 파라미터에 전달할 객체를 생성 

        return httpSession.getAttribute("user");
    }

}

여기서 사용된 SessionUser는 로그인시 HttpSession에 담아놓은 사용자가 정의한 User Class이다. 

 

HandlerMehtodArguementResolver 등록

 

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer { 
    private final LoginUserArgumentResolver loginUserArgumentResolver;

    @Override 
    // HandlerMethodArgumentResolver는 항상 WebMvcConfigurer의 addArgumentResulvers()를 통해 추가해야 한다 
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserArgumentResolver);
    }
}

 

Controller

 

@Controller
@RequiredArgsConstructor
public class IndexController {
    private final PostsService PostsService;
    // private final HttpSession httpSession;

    @GetMapping("/")
    public String index(Model model, SessionUser user) {
        model.addAttribute("posts", PostsService.findAllDesc());
        // SessionUser user = (SessionUser) httpSession.getAttribute("user"); 
        if (user != null) { // 세선에 저장된 값이 있을 때만 model에 userName을 등록해 준다
            model.addAttribute("userName", user.getName());
        }
        return "index";
    }
}

 

 

LIST
SMALL

서버 개발을 하다 보면 오류 처리를 해야 될 경우가 매우 많이 존재한다.

오류를 제대로 처리하지 않는다면, 서버가 안정적이지 못하게 동작할 수 있다.

 

보통의 경우에는 Exception 처리를 try-catch를 통해 해결한다.

하지만 이러한 로직들은 비즈니스 로직이 존재하는 곳에 코드가 써져 

코드를 복잡하게 하고 유지보수를 어렵게 만든다.

 

이런 문제를 개선하기 위해 @ExceptionHandler와 @ControllerAdvice를 사용해 보자.

 

@ExceptionHandler

@Controller, @RestController가 적용된 bean 내에서 발생하는 예외를 처리할 수 있게 해주는 어노테이션

 

@RestController 
public class HelloController {
    @GetMapping("/error")
    public void test() {
    	throw new IllegalArgumentException("test error");
    }
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handle(Exception ex) {
        return new ResponseEntity<>(new ResponseError(LocalDateTime.now(), "error"), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

@ExceptionHandler는 등록된 해당 Controller에만 적용이 된다.

다른 Controller에서 발생하는 예외는 잡을 수 없다.

여러 Controller에서 동일하게 발생하는 예외는 Controller에 적용된 @ExcptionHandler가 아닌 다른 방법으로 처리해야 한다.

 

@ControllerAdvice

@Controller, @RestController에서 발생한 예외를 한 곳에서 처리할 수 있는 어노테이션

스프링에서 예외처리를 전역적으로 핸들링하기 위해 사용

@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(IllegalArgumentException.class)
	public ResponseEntity<?> handleIllegalArguException(IllegalArgumentException e) {
		return new ResponseEntity<>(new ResponseError(LocalDateTime.now(), "error"), 
			HttpStatus.INTERNAL_SERVER_ERROR);
	}
}

Controller에 해당하는 모든 예외를 처리할 수 있지만, 사용자가 원한다면 특정 패키지로 제한을 할 수 있다

@ControllerAdvice("com.exam.controller")

 

@RestControllerAdvice

추가적으로 @RestControllerAdvice에 대해서 간단하게 알아보자

해당 어노테이션은 @ControllerAdvice + @ResponseBody 조합 어노테이션이라 생각하면 된다.

( @Controller, @RestController 관계와 비슷하게 @ResponseBody 설정이 추가된 어노테이션)

( https://joomn11.tistory.com/53?category=936835 )

 

다만 위에 예시처럼 Exception을 처리한 메서드의 리턴 값을 ResponseEntity로 지정을 한다면 @RestControllerAdvice 어노테이션을 사용하지 않고도 ResponseBody에 결괏값을 실을 수 있다.

 

@RestControllerAdvice 사용 예시

@RestControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public ResponseError handleIllegalArguException(IllegalArgumentException e) {
		return new ResponseError(LocalDateTime.now(), "error"));
	}
}
@Getter
@Setter
@AllArgsConstructor
public class ResponseError {
	private LocalDateTime timestamp;
    private String details;
}

다만, @RestControoerAdvice 어노테이션을 사용하는 경우 각각의 Handler 메서드에 @ResponseStatus 어노테이션을 추가해 주어야 한다.

( 해당 정보를 세팅하지 않는다면 디폴트로 200으로 응답)

LIST

+ Recent posts