[Spring] 여러 개의 커스텀 HandlerInterceptor와 커스텀 HandlerMethodArgumentResolver를 간단하게 등록해보자

Jihoon Oh·2022년 7월 16일
1
post-thumbnail

스프링에는 컨트롤러 이전에 처리해줘야 하는 로직이 들어가는 인터페이스인HandlerInterceptor, HTTP 요청에서 필요한 값을 꺼내어 컨트롤러 메서드의 파라미터로 바인딩하는 HandlerMethodArgumentResolver 인터페이스가 있습니다. 기본적으로 스프링에 구현되어 있는 리졸버 구현체들이 있는데요, 이 구현체들은 스프링 시작 시 RequestMappingHandlerAdapter가 초기화되면서 afterPropertiesSet 메서드의 호출 시 등록됩니다. getDefaultArgumentResolvers 메서드로 기본적으로 구현된 리졸버들을 가져오기 때문이죠.

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
    ...
    @Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
    
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

        // Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
	    ...
        
		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		...

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver());
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}
}

하지만 스프링에서 제공되는 인터셉터나 리졸버 외에 추가적인 인터셉터나 리졸버가 필요한 경우가 있습니다. 예를 들어 인증/인가 등을 요청이 컨트롤러로 들어오기 전에 체크하거나, 요청을 보낸 회원 정보를 컨트롤러에 파라미터로 바인딩 해야 하는 경우가 있겠네요. 그리고 이럴 때 구현한 인터셉터나 리졸버는 보통 다음과 같이 WebMvcConfigurer를 구현한 객체에서 등록하게 됩니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(final InterceptorRegistry registry) {
        SampleInterceptor sampleInterceptor = new SampleInterceptor();
        registry.addInterceptor(sampleInterceptor);
    }

    @Override
    public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
        SampleArgumentResolver sampleArgumentResolver = new SampleArgumentResolver();
        resolvers.add(sampleArgumentResolver);
    }
}

그런데, 만약 인터셉터나 리졸버를 여러 종류를 만들어야 한다면 어떻게 될까요? AnotherInterceptorAnotherResolver를 추가해보도록 하겠습니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        SampleInterceptor sampleInterceptor = new SampleInterceptor();
        AnotherInterceptor anotherInterceptor = new AnotherInterceptor();
        registry.addInterceptor(sampleInterceptor);
        registry.addInterceptor(anotherInterceptor);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        SampleArgumentResolver sampleArgumentResolver = new SampleArgumentResolver();
        AnotherArgumentResolver anotherArgumentResolver = new AnotherArgumentResolver();
        resolvers.add(sampleArgumentResolver);
        resolvers.add(anotherArgumentResolver);
    }
}

보시는 것 처럼 인터셉터 하나, 리졸버 하나가 추가될 때마다 계속해서 코드를 추가해나가야 합니다. 등록된 인터셉터와 리졸버가 많다면 귀찮으면서도 관리가 어려운 작업이죠. 그렇다면 이들을 빈으로 등록하면 자동으로 주입되지 않을까요?

인터셉터나 리졸버를 등록하는 과정은 등록하려는 인터셉터나 리졸버가 스프링 빈으로 등록되어 있는지 여부는 중요하지 않습니다. 위에서 만든 모든 인터셉터와 리졸버에 @Component를 붙여서 스프링 빈으로 만든다고 가정해보겠습니다. 스프링 빈으로 등록하면 자동으로 인터셉터와 리졸버로서의 기능을 할 것 같지만 그렇지 않습니다. 스프링 빈으로 등록되어 있더라 하더라도 위에서 설명한 RequestMappingHandlerAdapter는 저희가 구현한 인터셉터와 리졸버 구현체들을 바로 인식할 수가 없기 때문이죠. 결국 다시 WebMvcConfigurer에서 의존성을 주입받아서 직접 등록해줘야 합니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final SampleInterceptor sampleInterceptor;
    private final SampleArgumentResolver sampleArgumentResolver;

    private final AnotherArgumentResolver anotherArgumentResolver;
    private final AnotherInterceptor anotherInterceptor;

    public WebConfig(final SampleInterceptor sampleInterceptor, final SampleArgumentResolver sampleArgumentResolver,
                     final AnotherArgumentResolver anotherArgumentResolver,
                     final AnotherInterceptor anotherInterceptor) {
        this.sampleInterceptor = sampleInterceptor;
        this.sampleArgumentResolver = sampleArgumentResolver;
        this.anotherArgumentResolver = anotherArgumentResolver;
        this.anotherInterceptor = anotherInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(sampleInterceptor);
        registry.addInterceptor(anotherInterceptor);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(sampleArgumentResolver);
        resolvers.add(anotherArgumentResolver);
    }
}

이 과정 역시 new 키워드로 객체를 직접 생성해서 등록해주는 것과 그닥 차이가 없어보입니다. 어떻게 하면 많은 인터셉터와 리졸버를 한번에 등록해줄 수 있을까요?

우리는 여기서 인터페이스 타입의 DI에 대해 한번 다시 생각하고 넘어갈 필요가 있습니다. 어떤 인터페이스 타입의 구현체 여러 개가 스프링 빈으로 등록될 경우, 하나의 빈을 주입하려면 필드 이름의 구분이나 @Primary, @Qualifier 같은 어노테이션의 힘을 빌리지 않고는 주입할 수 없습니다. 하지만 List 타입으로는 해당 인터페이스를 구현한 모든 구현체 빈을 주입받을 수 있죠. 이 부분을 이용해보도록 하겠습니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final List<HandlerInterceptor> interceptors;
    private final List<HandlerMethodArgumentResolver> argumentResolvers;

    public WebConfig(final List<HandlerInterceptor> interceptors,
                     final List<HandlerMethodArgumentResolver> argumentResolvers) {
        this.interceptors = interceptors;
        this.argumentResolvers = argumentResolvers;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        interceptors.forEach(registry::addInterceptor);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.addAll(argumentResolvers);
    }
}

보시는 것 처럼 List<HandlerInterceptor>, List<HandlerMethodArgumentResolver> 타입으로 모든 커스텀 인터셉터와 리졸버를 주입받을 수 있습니다. 이렇게 주입받은 리스트를 등록하는 코드 한 줄씩만 작성하면 손쉽게 커스텀 인터셉터와 커스텀 리졸버를 등록할 수 있습니다. 이런 식으로 등록하게 되면, 앞으로 커스텀 인터셉터와 커스텀 리졸버가 추가되더라도 WebConfig는 건드릴 필요 없이 새로 만드는 커스텀 객체에 @Component만 붙이면 자동으로 등록됩니다.

참고로, 스프링에 기본으로 구현되어 있는 리졸버들은 빈으로 등록되어 있지 않고 RequestMappingHandlerAdaptor에서 직접 리졸버로 등록하기 때문에 스프링의 의존성 주입 기능으로는 주입되지 않습니다.

profile
Backend Developeer

0개의 댓글