티스토리 뷰

정황 소개


저는 운동 동행 구하기 웹 어플리케이션 'Helparty'를 만드는 중 입니다. 

 

잘 만들고 있던 중에 게시물 작성 기능과, 수정 기능 그리고 삭제 기능을 만들던 과정에서 로그인 되어 있는 유저의 정보를 확인하기 위해 반복적으로 컨트롤러 클래스에서 session을 통해 로그인 되어 있는 이메일을 가져오는 로직이 반복된다는 것을 알았습니다. 그리고 이런 로그인을 통한 유저 확인 로직은 계속해서 반복되어 나타날 것임을 알고 있습니다. 

 

따라서 저는 이 로그인 정보를 가져오는 로직을 핵심로직의 앞에 반복적으로 나타나는 중복적인 부가로직을 판단하고 분리하기로 했습니다. 

게시물 생성 메서드
게시물 업데이트 메서드

 

 

1. AOP를 통한 분리


먼저, 메서드 단위로 중복적인 로직을 분리하는 AOP를 통해 구분하였습니다. 

AOP에는 CGlib, Java Proxy, aspectJ가 있는데 저는 AspectJ로 구현하였습니다. 

 

이 3의 특징을 간단하게 비교해보면 CGlib와 Java Proxy는 프록시 방식으로 AOP를 구현합니다. 그리고 다시 이 2개를 구분해보면 CGlib는 enhancer란 클래스를 통해 구현하고 Java Proxy는 인터페이스 방식으로 구현합니다. AspectJ는 이와는 다르게 전용 컴파일러로 컴파일 타임 또는 로드 타임에 바이트 코드로 파일 자체에 부가로직을 더해줍니다. 

 

저는 런타임 이전에 걸리는 시간은 다른 2개의 AOP보다 오래걸리지만 한번 컴파일이 되면 런타임에서 더 좋은 성능을 보여주는 AspectJ를 통해 구현하였습니다. 

 

이제 코드 수정을 시작하려할 때, 저는 어떻게 AOP로 로직을 분리하여 리턴 값을 email 변수에다 넣어줄까에 대해 한참 고민했습니다. 고민으로는 답이 안나와서 일단 @PathVariable처럼 앞에 어노테이션으로 ApspectJ를 넣어주는 포인트를 만들었습니다. 

 

어노테이션으로 AOP 적용 받을 변수 설정


그리고 이 어노테이션에 대한 인터페이스를 만들었습니다.  

 

@GetSessionEmail 어노테이션 인터페이스


마지막으로 로그인 되어 있는 정보를 session에서 가져오고 그것을 AOP를 통해 컨트롤러 클래스의 email변수에 넣어주어야 합니다. 이를 위한 GetSessionEmailAspect클래스를 만들었습니다. 

 


 

포인트 컷 부분을 보면 Around를 사용했습니다. 컨트롤러에서 파라미터로서 사용되는 email 변수를 사용하는데 이 변수에 값을 넣어주기 위해서는 ProceedingJoinPoint을 사용해서 값을 전달해줘야합니다. 그리고 이 클래스는 Around를 사용해야 하는 조건이 있습니다. 

 

그리고 execution(public * *(.., @GetSessionEmail(*), ..)) 이 부분을 살펴보겠습니다. 

  1. execution은 파라미터 어노테이션에 적용될 수 있도록 섬세한 작업이 필요한 곳에 사용합니다. 
  2. 맨 앞의 *은 리턴 타입을 나타냅니다.
  3. *(.., @GetSessionEmail(*), ..)은 @GetSessionEmail 양 옆에 있는 다른 파라미터를 0개 이상을 허용하겠다는 패턴입니다. 

그리고 다음으로 파라미터에 있는 어노테이션을 가져오는 로직을 살펴보겠습니다. 

 

 

 

MethodSignature 클래스를 사용하여 AOP를 사용하는 메서드의 이름과 사용되는 파라미터를 가져옵니다. 

 

 

그리고 ProceedingJoinpoint를 사용하여 방금 구한 메서드의 이름과 파라미터들의 타입을 사용하여 파라미터에 사용된 어노테이션들을 구합니다. 

 

여기서 SoftException으로 예외를 던지는 것을 볼 수 있습니다. 저는 이것을 처음 봤기에 이것에 대해 알아보았습니다. 

이것은 우선 이 예외는 체크 예외를 런타임 예외로 바꿔주는 기능을 합니다. 저는 왜 이런 기능이 왜 필요한가 생각이 들었습니다. 

 

그래서 더 찾아본 결과 AspectJ는 전에 말했듯이 런타임 이전에 위빙을 합니다. 그래서 런타임 예외를 던지지 못하고 보낼 수 있는 예외가 체크 예외 밖에 없습니다. 

 

이럴 경우 문제가 되는 것은 한번 예외 발생하면 그 이후에는 해당 예외나 그 예외의 자식만을 보낼 수 있게 됩니다. 그럼 개발자는 런타임 중에 자신이 계획한 특정한 상황에서의 예외를 만들 수 없게 됩니다. 그래서 보통 체크 예외는 범용적인 예외를 사용하게 됩니다. 하지만 이럴 경우 예외는 상황이 구체적이어야 한다는 점에 맞지 않으므로 soft exception을 통해 예외를 위버가 각 어드바이스 앞에서 runtime exception으로 받을 수 있게 해줍니다. 

 

이 경우는 런타임 이전에 바이트 코드로 AOP를 구현하는 AspectJ의 상황에 관한 것입니다. 런타임에 프록시 방식으로 구현하는 AOP에는 해당하지 않습니다. 

 


 

아규먼트들의 어노테이션을 조사하여 GetSessionEmail.class와 같은 것을 찾는 로직입니다. 

그리고 마지막으로 해당 아규먼트들을 모두 리턴해줍니다. 

 

ProceedingJoinPoint에서 proceed메서드는 모든 아규먼트들을 리턴해줘야 합니다. 


드디어 AOP를 통해서 로그인 정보를 가져오는 로직을 분리하였습니다. 

 

하지만 AOP를 다 구현하고 난 뒤 HandlerArgumentResolver라는 것에 대해 알게 되었습니다. 

 

HandlerArgumentResolver은 컨트롤러 메서드에서 특정 조건에 맞는 파라미터가 있을 때 원하는 값을 바인딩해주는 인터페이스 입니다. 지금 제가 만든 로그인 정보를 가져오는 로직은 컨트롤러에서 반복적으로 한번씩 구현되는 로직을 분리하고 리턴값을 파라미터에 바인딩하기 때문에 HandlerArgumentResolver를 사용하기에 아주 알맞아 보입니다.

 

바로 교체하겠습니다. 

 

2. HandlerMethodArgumentResolver를 통한 분리


@Component
@RequiredArgsConstructor
public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
    
    private final HttpSession httpSession;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(GetSessionEmail.class);
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return httpSession.getAttribute(SessionKeys.LOGIN_USER_EMAIL);
    }
}

 

어노테이션 부분은 변함이 없기에 로그인 정보를 가져오는 로직을 구현한 클래스만 수정하였습니다. 

  • supportsParameter 메서드는 현재 파라미터를 resolver가 지원하는지에 대한 boolean을 리턴합니다. 
  • resolverArument 메서드는 실제로 바인딩을 할 객체를 리턴합니다.

 

마지막으로 CustomArgumentResolver 클래스를 스프링에 등록합니다. 

 

@Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(customArgumentResolver);
    }

이렇게 저는 컨트롤러 클래스의 메서드에서 반복되는 로그인 정보 얻는 로직을 메서드의 파라미터로 위치를 옮기고 HandlerMethodArgumentResolver를 사용하여 분리하였습니다. 

 

 

 

 

 

참고

www.eclipse.org/aspectj/doc/next/runtime-api/org/aspectj/lang/SoftException.html

 

SoftException (AspectJ(tm) runtime API)

clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

www.eclipse.org

stackoverflow.com/questions/11765996/aspectj-pointcut-expression-match-parameter-annotations-at-any-position/15605403#15605403

 

AspectJ pointcut expression match parameter annotations at any position

I'm trying to define a pointcut expression to match methods which contain a parameter annotated with a specific annotation, regardless of what position the parameter is in. In my case I'm looking f...

stackoverflow.com

docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

velog.io/@kingcjy/Spring-HandlerMethodArgumentResolver%EC%9D%98-%EC%82%AC%EC%9A%A9%EB%B2%95%EA%B3%BC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함