본문 바로가기
Spring

Annotation based Interceptor

by yzzzzun 2021. 9. 10.

Interceptor를 사용하는 방법은 이전 포스팅인 filter, interceptor에서 정리를 해봤는데요. 그중 Interceptor를 Annotation을 활용해 사용하는 방법이 있습니다.

Custom Annotation을 등록하고 HandlerMethod를 통해 Annotation을 찾아 Interceptor를 실행하도록 구현하는 방법인데요. 이 방법이 Annotation을 추가하고 등록할 때마다 Handler에서 Annotation을 찾는 로직이 반복되는 걸 확인할 수 있었습니다. 그래서 이번엔 공통적인 로직을 한번 분리해보려고 합니다.

Custom Annotation 생성

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomInterceptor {
}

우선 메서드를 타겟으로 하는 @CustomInterceptor라는 어노테이션을 만들겠습니다. (어노테이션에 대한 설명은 하지 않겠습니다.)

이제 @CustomInterceptor가 붙은 메서드에 대해서 interceptor가 동작하도록 만들어볼게요.

Interceptor 구현

public class CustomInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
		Exception {
		
        HandlerMethod handlerMethod = (HandlerMethod) handler;
		CustomInterceptor customInterceptor = handlerMethod.getMethodAnnotation(CustomInterceptor.class);
		if(customInterceptor!=null){
			//TODO preHandle
			return true;
		}
		return false;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
		ModelAndView modelAndView) throws Exception {
		//TODO postHandle
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
		Exception ex) throws Exception {
		//TODO afterCompletion
	}
}

  HandlerMethod를 통해 Annotation을 찾고 Annotation이 있으면 로직을 수행하도록 Interceptor를 구현합니다.

일반적으로 Custom Annotation을 만들어서 Interceptor를 동작하게 만드는 방법인데요. 만약 @CustomInterceptor 말고 추가적으로 Custom Annotation을 만들어서 Interceptor를 사용하게 된다면  HandlerMethod로부터 Annotation이 있는지 확인하는 코드가 반복됩니다.

AbstractInterceptor

  Interceptor에 해당하는 Annotation을 찾는 로직은 공통적으로 가져가는 추상 클래스를 생성하고, Interceptor에 해당하는 로직들은 추상 클래스를 상속받아 구현하는 방식으로 개선해봅시다.

public abstract class AbstractInterceptor<T extends Annotation> implements HandlerInterceptor {

	private final Class<T> clazz;

	protected AbstractInterceptor() {
		this.clazz = (Class<T>)GenericTypeResolver.resolveTypeArgument(this.getClass(), AbstractInterceptor.class);
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
		Exception {

		if (isRegistedAnnotation(handler)) {
			return doPreHandle(request, response, handler);
		}

		return false;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
		ModelAndView modelAndView) throws Exception {
		this.doPostHandle(request, response, handler, modelAndView);
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
		Exception ex) throws Exception {
		this.doAfterCompletion(request, response, handler, ex);
	}

	private boolean isRegistedAnnotation(Object handler) {
		HandlerMethod handlerMethod = (HandlerMethod)handler;
		Annotation[] annotations = handlerMethod.getMethod().getDeclaredAnnotations();

		return Arrays.stream(annotations)
			.anyMatch(annotation -> annotation.annotationType().isAssignableFrom(clazz));
	}

	abstract boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
		Exception;

	abstract void doPostHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
		ModelAndView modelAndView) throws Exception;

	abstract void doAfterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
		Exception ex) throws Exception;
}

추상클래스를 상속받을 때 Annotation을 generic 타입으로 지정하도록 했고, GenericTypeResolver를 통해 Annotation의 클래스 타입을 찾습니다. 그 후 HandlerMethod의 Annotation을 비교하며 등록된 Annotation과 일치하는 게 있는지 찾아주는 로직을 추상 클래스에서 가져가는 거죠. 이렇게 되면 Annotation을 찾는 로직이 분리가 됩니다. 그리고 구체적인 Interceptor 수행 로직들은 하위 클래스에서 직접 구현하고, 추상 클래스에서는 호출만 하는 방향으로 만들어줍니다.

CustomAnnotation을 지정한 Interceptor 구현

public class LogCustomInterceptor extends AbstractInterceptor<CustomInterceptor> {

	@Override
	boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		System.out.println("LogCustomInterceptor.doPreHandle");
		return true;
	}

	@Override
	void doPostHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
		ModelAndView modelAndView) throws Exception {
		System.out.println("LogCustomInterceptor.doPostHandle");
	}

	@Override
	void doAfterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
		Exception ex) throws Exception {
		System.out.println("LogCustomInterceptor.doAfterCompletion");
	}
}

상속받을 때 생성한 @CustomInterceptor 어노테이션을 지정합니다. AbstractInterceptor클래스에서 해당 어노테이션을 찾는 로직을 수행합니다. Interceptor 기능은 해당 클래스에서 구현하고 AbstractInterceptor 클래스에서 호출합니다.


Wrap-up

스터디를 하면서 Annotation 기반의 Interceptor활용을 좀 더 확장성 있게 구현해보자는 이야기가 나와서 포스팅을 작성하게 되었는데요. Interceptor를 잘 학습했다면 개선하는 과정이 크게 어렵게 느껴지지 않을 거라고 생각합니다. 추상 클래스에서 Generic으로 지정한 Annotation을 찾는데 GenericTypeResolver를 활용하면 된다는 점을 알게 되었구요. Generic에 대해 추가적인 학습이 필요할 것 같다는 생각이 들었습니다. 앞으로 이런 개선 과정에 대해서 지속적으로 고민해보면 좋겠다는 생각이 들었습니다 :)

'Spring' 카테고리의 다른 글

Spring MVC - Filter, Interceptor  (0) 2021.08.11
Spring Transaction  (0) 2021.08.09
AOP를 직접 구현해보자 - Proxy적용  (0) 2021.08.02

댓글