본문 바로가기
Spring|Spring-boot

[Spring] Interceptor , ArgumentResolver

by oncerun 2020. 7. 20.
반응형

인터셉터는 Dispatcher servlet에서 Handler 즉 Controller로 요청을 보낼 때 또는 그 반대일 경우에 동작합니다.

비슷한 기능으로는 servlet filter가 있는 데요

서블릿 필터는 dispatcherServlet이 실행되기 이전에 발생하고 인터셉터는 dispatcherServlet 이후 handler로 넘어가기 전에 발생을 합니다.

 

 

Interceptor 가 여러 개라면?

 

  • xml 설정이라면 정의된 순서대로 동작합니다.

  • java bean 설정이라면 WebMvcConfigurerAdapter를 상속받은 addInterceptors에 추가되는 대로 일 것이다.

 

HandlerInterceptorAdapter를 상속받는 interceptor클래스를 생성합니다.

 

package kr.or.connect.guestbook.intercepter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LogInterceptor extends HandlerInterceptorAdapter{
	
	//컨트롤러가 실행된 후
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		System.out.println(handler.toString() + "가 종료되었습니다." + modelAndView.getViewName() + "가 사용됩니다." );
	}

	//컨트롤러가 실행되기 전
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
		System.out.println(handler.toString() +"를 호출");
		
		return true;
	}
}

xml을 통해 설정할 수 있으며 java config를 이용해 설정할 수 있습니다.

 

package kr.or.connect.guestbook.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import kr.or.connect.guestbook.intercepter.LogIntercepter;


@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"kr.or.connect.guestbook.controller"})
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {

	//모든요청을 받기때문에 요청되는 js, css파일등을 웹루트 하위디렉토리인 css, js폴더에서 찾으라는 설정이다.
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }
 
    // default servlet handler를 사용하게 합니다.
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
   
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
    		System.out.println("addViewControllers가 호출됩니다. ");
        registry.addViewController("/").setViewName("index");
    }
    
    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
    
    	@Override
    	public void addInterceptors(InterceptorRegistry registry) {
//    		registry.addInterceptor(new GuestBookInterceptor()).addPathPatterns("/auth/**");
    		registry.addInterceptor(new LogIntercepter());
    		
    	}
}

addInterceptors 메서드를 구현을 할 때 위와 같이 구현을 하며, 인터셉터 구현 시 메서드 체이닝을 되어있어 연속적으로 다른 추가 패턴도 등록할 수 있습니다.

addPathPatterns는 적용할 url 패턴을 설정합니다. *, **등을 사용한 URI Pattern String을 전송하거나, URI Pattern 배열을 전송할 수 있습니다.

excludePathPatterns의 경우 인터셉터를 제외할 url 패턴을 등록하는 메서드로써 해당 url로 접근 시에는 인터셉터를 적용하지 않게 됩니다.

 

 

아큐 먼트 리졸버란??

 

컨트롤러의 메서드의 인자로 사용자가 임의의 값을 전달하는 방법을 제공하고자 할 때 사용됩니다.

예를 들어 세션에 저장되어 있는 값 중 특정 이름의 값을 메서드 인자로 전달할 수 있습니다.

 

아규먼트 리졸버 작성방법 1/2

  • org.springframework.web.method.support.HandlerMethodArgumentResolver를 구현한 클래스를 작성합니다.
  • supportsParameter메서드를 오버 라이딩한 후, 원하는 타입의 인자가 있는지 검사한 후 있을 경우 true가 리턴되도록 합니다.
  • resolveArgument메서드를 오버 라이딩한 후, 메서드의 인자로 전달할 값을 리턴합니다.

 

아규먼트 리졸버 작성방법 2/2

  • Java Config에 설정하는 방법
    - WebMvcConfigurerAdapter를 상속받은 Java Config 파일에서 addArgumentResolvers 메서드를 오버 라이딩한 후 원하는 아규먼트 리졸버 클래스 객체를 등록합니다.
  • xml 파일에 설정하는 방법
   <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="아규먼트리졸버클래스"></bean>      
        </mvc:argument-resolvers>
    </mvc:annotation-driven>
 

 

우리가 httpservlet session, request등을 적을 때 값이 전달되는 것은 기본으로 제공하는 아규먼트 리졸버가 존재하기 때문입니다.

 

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

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

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

		return resolvers;
	}

참고로 Map 객체나 Map을 상속받은 객체는 스프링에서 이미 선언한 아규먼트 리졸버가 처리하기 때문에 전달할 수 없다.

그래서 Map 객체를 전달하려면 Map 객체를 필드로 가지고 있는 별도의 객체를 선언한 후에 사용해야 된다.

 

기본적으로 흔히 사용하는 @Controller와 @RequestMapping 어노테이션을 통해 결정되는 컨트롤러의 경우에는 RequestMappingHandlerMapping 구현체의 의해 핸들러가 결정된다.

거기에 대응되는 Adapter로는 RequestMappingHandlerAdapter 클래스에 의해 컨트롤러의 메서드가 호출된다.

참고로 3.1 이전에는 DefaultAnnotationHandlerMapping 클래스로 결정되었으며 그에 대응되는 AnnotationMethodHandlerAdapter에 의해 호출이 일어났다.

현재는 Deprecated 처리 되어 있다.

 

Spring 3.0의 DefaultAnnotationHandlerMapping과 AnnotationMethodHandlerAdapter가  Spring 3.1 에선 @Deprecated 되고,

대신 RequestMappingHandlerMapping과 RequestMappingHandlerAdapter로 바뀌었다.

 

 이유인즉슨, Controller의 요청이 메서드 단위로 세분화되면서 , DefaultAnnotationHandlerMapping 이 AnnotationMethodHandlerAdapter로 handler를 전달해줄 때, 문제가 생겼기 때문이다.



 

package kr.or.connect.guestbook.argumentresolver;

import java.util.HashMap;
import java.util.Map;

public class HeaderInfo {

	private Map<String ,String> map;

	
	public HeaderInfo() {
		
		map = new HashMap<>();
		
	}

	public void put(String name, String value) {
		
		map.put(name, value);
	}
	
	public String get(String name) {
		
		return map.get("name");
		
	}
	
}
	@Override
    	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    		
    		System.out.println("등록");
    		argumentResolvers.add(new HeaderMapArugementResolver());
    	
    	}
package kr.or.connect.guestbook.argumentresolver;

import java.util.Iterator;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class HeaderMapArugementResolver implements HandlerMethodArgumentResolver {
	
	
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		HeaderInfo info = new HeaderInfo();
		
		Iterator<String> headerNames = webRequest.getHeaderNames();
		
		while(headerNames.hasNext()) {
			String headerName = headerNames.next();
			String headerValue = webRequest.getHeader(headerName);
			
			System.out.println(headerName +  " , " + headerValue);
			info.put(headerName, headerValue);
		}
		return info;
	}
	
	@Override
	public boolean supportsParameter(MethodParameter parameter) {

		return parameter.getParameterType() == HeaderInfo.class;
	}

}
반응형

'Spring|Spring-boot' 카테고리의 다른 글

Gradle 멀티 모듈  (0) 2020.08.06
Lombok  (0) 2020.08.03
[Spring] Spring security  (0) 2020.07.20
[Spring] Spring MVC Session  (0) 2020.07.20
[Spring] MessageConverter  (0) 2020.07.17

댓글