TIL

Todo 프로젝트 리팩토링 하기

haseung22 2024. 10. 30. 18:37

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를 이용하여 해결해봤는데 정말 좋은 기능 같다. 여러 곳에서 사용되는 공통적인 로직을 줄이고, 매개변수로 필요한 정보를 간단한게 가져와 사용할 수 있게 된 점이 가장 좋은 점 인것같다. 무엇보다 내가 직접 내가 짠 코드를 보면서 문제점을 찾고, 그 문제점을 보완까지 해보니 재미있고 유익한 시간이였던 것 같다.