1. 현재 문제점
현재 Todo프로젝트에서는 토큰에 저장되어있는 유저 정보를 사용하기 위해 각 컨트롤러 마다 아래와 비슷한 코드들이 중복으로 발생하게된다. 무엇보다 각 컨트롤러마다 getUser라는 메서드를 만들기 때문에 만약 내가 request.setAttribute로"user"가 아닌 "users"로 저장하게된다면 각 컨트롤러마다 들어가서 바꿔줘야하는 불편함이 생긴다. 그래서 이 부분을 고쳐보고자 한다.
@GetMapping("/myInfo")
public ResponseEntity<UserResponseDto> findMyInfo(HttpServletRequest request) {
return ResponseEntity.ok(userService.findById(getUser(request)));
}
@PutMapping("/{id}")
public ResponseEntity<Void> modify(@PathVariable("id") Long id, @Valid @RequestBody UserModifyRequestDto userModifyRequestDto, HttpServletRequest request) {
userService.modify(id, userModifyRequestDto.getUserName(), getUser(request));
return ResponseEntity.status(HttpStatus.OK).build();
}
private User getUser(HttpServletRequest request) {
return (User) request.getAttribute("user");
}
2. 해결방안
1. User Entity 클래스에 static 메서드로 getUser 메서드 생성 후 각 컨트롤러에서 사용
UserClass{
public static User getUser(HttpServletRequest request){
return (User) request.getAttribute("user");
}
}
Controller{
@GetMapping("/myInfo")
public ResponseEntity<UserResponseDto> findMyInfo(HttpServletRequest request) {
return ResponseEntity.ok(userService.findById(User.getUser(request)));
}
}
2. HandlerMethodArgumentResolver를 이용하여 User 주입받기
@GetMapping("/myInfo")
public ResponseEntity<UserResponseDto> findMyInfo(@LoginUser User user) {
return ResponseEntity.ok(userService.findById(user);
}
1번 같은 경우에는 2번 방법에 비해 비교적 간단하고 쉽게 각 컨트롤러에서 user 객체를 사용할 수 있게 된다. 하지만 결국 매 메서드들마다 User.getUser()이라는 공통적인 코드가 생기는건 매한가지이다. 그렇다면 2번 방법은 어떨까 2번 방법으로 하기위해선 3가지의 과정을 거쳐야한다. 하지만 1번 방법과 달리 공통된 코드가 사라지고 User객체를 매개변수로 받아 사용할 수 있게된다. 이렇게 되면 Entity 클래스에 request를 넘기지 않아도 되고 각 컨트롤러마다 User.getUser() 메서드를 적어주지 않아도 되기 때문에 2번 방법으로 User 객체를 주입받는 방향으로 리팩토링을 진행해보자.
3. 리팩토링 진행
1. @LoginUser 어노테이션을 만들어준다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
2. HandlerMethodArgumentResolver를 구현한 클래스를 만든다.
HandlerMethodArgumentResolver을 implements하기 때문에 두개의 메서드를 구현해야한다.
@Component
public class LoginUserResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return null;
}
}
2 - 1 supportsParameter
supportsParameter 메서드는 해당 메서드의 매개변수가 해당 resolver가 지원하는지를 체크하는 것이다.
나의 경우에는 @LoginUser을 포함하는지 여부에 따라 boolean 값을 리턴해준다.
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginUser.class);
}
2 - 2 resolveArgument
실제로 바인딩할 객체를 리턴한다 나의 경우에는 User 객체를 반환한다.
아래에서 사용한 assert는 조건문이 fasle일 경우 Assertionerror를 발생시킨다.
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
assert request != null;
return request.getAttribute("user");
}
3. 구현한 HandlerMethodArgumentResolver를 등록해준다.
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final LoginUserResolver loginUserResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserResolver);
}
}
4. 사용해보기
@GetMapping("/myInfo")
public ResponseEntity<UserResponseDto> findMyInfo(@LoginUser User user) {
return ResponseEntity.ok(userService.findById(user));
}
잘 받아오는 것을 볼 수 있다 !!
4. 회고
이렇게 문제점으로 삼았던 중복되어 Request에서 user객체를 빼오는 코드들을 HandlerMethodArgumentResolver를 이용하여 해결해봤는데 정말 좋은 기능 같다. 여러 곳에서 사용되는 공통적인 로직을 줄이고, 매개변수로 필요한 정보를 간단한게 가져와 사용할 수 있게 된 점이 가장 좋은 점 인것같다. 무엇보다 내가 직접 내가 짠 코드를 보면서 문제점을 찾고, 그 문제점을 보완까지 해보니 재미있고 유익한 시간이였던 것 같다.
'TIL' 카테고리의 다른 글
AOP (0) | 2024.10.31 |
---|---|
기능 개선 과제 (0) | 2024.10.31 |
뉴스피드 프로젝트 리팩토링 (1) | 2024.10.25 |
REST, REST API, RESTful (2) | 2024.10.21 |
JPA - 일정관리 앱 만들기 (1) | 2024.10.17 |