在spring web mvc中实现拦截功能时,有三种方式
1. 使用功能servlet filter
2. 使用springmvc 提供的 HandlerInterceptor
3. 使用spring core 提供的 MethodInterceptor(spring aop)
1. servlet的过滤器Filter
我们自己写的Filter类,Filter是Servlet规范的一部分,是Servlet容器(如Tomcat)实现的。
在spring boot下注册一个filter的三种方式(servlet、listener也是如此)
方式1:可以使用@WebFilter+@ServletComponentScan的方式
方式2:可以使用FilterRegistrationBean 进行API级别的注册,注意,在这种情况下可以对Filter order进行设置,而使用spring @Order注解是无效的
@Bean
public ServletRegistrationBean asyncServletServletRegistrationBean(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new AsyncServlet(),"/");
registrationBean.setName("MyAsyncServlet");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
方式3:创建一个类去实现 ServletContextInitializer 接口,并把它注册为一个 Bean,Spring Boot 会负责调用这个接口的 onStartup 方法。
@Bean
public ServletContextInitializer servletContextInitializer() {
return servletContext -> {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
FilterRegistration.Dynamic registration = servletContext.addFilter("filter", filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/");
};
}
FilterRegistrationBean其实也是通过 ServletContextInitializer 来实现的,它实现了 ServletContextInitializer 接口
特点:
可以拿到原始的http请求和响应的信息,但是拿不到真正处理这个请求的方法的信息
存在的问题:
通过Filter只能拿到http的请求和响应,只能从请求和响应中获得一些参数。当前发过来的这个请求实际上真正是由哪个控制器的哪个方法来处理的,在Filter里面是不知道的,因为javax.servlet.Filter是J2EE规范中定义的,J2EE规范里面实际上并不知道与spring相关的任何内容。而我们的controller实际上是spring mvc定义的一套机制。如果你需要这些信息,那么就需要使用拦截器Interceptor
2. springmvc的拦截器Interceptor
public class TimeInterceptor implements HandlerInterceptor {}
在我们真正访问的Controller的某个方法被调用之前,会调用preHandler方法,在Controller的方法调用之后,会调用postHandler方法,如果你的controller中的方法抛出了异常,那么postHandler这个方法不会被调用。但无论controller中的方法是否抛出异常,afterCompletion方法都是会被调用的。
参考:spring mvc 处理流程源码解析 中关于HandlerInterceptor部分
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){}
最后一个参数,Object handler,这个是我真正用来处理这个请求的Controller的方法声明
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
其中的Exception就是表示其是否发生了异常
日志信息
注意:其不仅会打印出发生错误的controller的方法的异常信息,而且其还会拦截BasicErrorController的error方法
interceptor不仅会拦截我们自己写的controller,spring提供的controller也会被拦截
特点:
Filter是servlet提供的,所以其不知道spring及springmvc的任何内容。
在Filter中是不知道这个请求是哪个控制器的哪个方法来处理的。如果你需要这个信息的话,那么需要使用springmvc的interceptor。
存在的问题:
拦截器可以拿到原始的http请求和响应的信息,也能拿到真正处理这个请求的方法的信息,但是其拿不到这个方法被调用的时候真正调用的参数的值
LOGGER.info(((HandlerMethod) handler).getBean().getClass().getName());
LOGGER.info(((HandlerMethod) handler).getMethod().getName());
通过handler虽然可以拿到处理此请求的controller和method,但是其没办法拿到这个method的真正的参数的值,这个handler只是那个方法的声明,但实际调用的时候,比如说调用getInfo的时候,我的这个id传的是多少,在拦截器里面用handler是拿不到的
为什么会这样呢? 从源码上看一下
org.springframework.web.servlet.DispatcherServlet#doDispatch
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
这个方法就是调用我们拦截器的preHandler方法
如果我们的interceptor的preHandler返回的是false,就直接return掉不进行处理,否则执行目标controller的目标method
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
而方法参数的拼装是在ha.handle这个方法中完成的,所谓的方法参数的拼装就是把我请求中的参数组装成我们controller的method中所要的参数/对象
另外,在interceptor中可以指定指定的url才需要拦截
springmvc 异步interceptor
配置拦截器的时候,开启异步支持
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
super.configureAsyncSupport(configurer);
}
如果想拦截异步处理的请求,那么要通过registerCallableInterceptors和registerDeferredResultInterceptors来单独注册拦截器
- CallableProcessingInterceptor
- DeferredResultProcessingInterceptor
设置好下面的两个方法:
setDefaultTimeout:因为是开了另一个线程,这个线程有可能阻塞,有可能死掉,没有响应了,那么在多长时间内我的http请求就返回回去,连接释放掉。
setTaskExecutor:在默认情况下,使用Callable,spring会使用内部一个简单的异步线程池来进行处理。这个简单的异步线程池并不是真正意义上的池,其不会重用线程,而是每一次被调用的时候,都会新建一个线程。通过这个方法可以设置一个可重用的线程池。
3. spring aop
在spring中,Advice的实现接口就是Interceptor
注意,Advice和Interceptor都是在org.aopalliance包下,并非在spring包下,为spring引入的外部依赖
Interceptor接口在spring中的实现类MethodInterceptor,通过before, after, afterReturing, afterThrowing等增强器来对目标方法进行增强
spring aop源码解析参考:spring aop源码解析1: 创建、初始化并注册AnnotationAwareAspectJAutoProxyCreator
特点:
可以拿到方法被调用的时候真正调用的参数的值
存在的问题:
但是拿不到原始的http请求和响应的信息
4. 拦截起作用的顺序
-
Filter最先其作用
-
之后是Interceptor
-
再之后才是Aspect
-
之后才会进入到Controller中去
可以看到springmvc中的拦截器是用来拦截请求的,在MethodInterceptor之前执行
而MethodInterceptor是spring AOP项目中的拦截器,它拦截的目标是方法,即使其不是Controller中的请求方法。
参考:SpringMVC项目中,什么情况在控制层使用的AOP,什么情况使用拦截器?
“HandlerInterceptor和servlet的Filter比较相似,是一个链式的处理模式,每个请求都会在这个链下跑一遍,实际上很多请求我们是不想去处理的,如果在方法里判断显得很不spring.而自己用AOP实现,一切皆可配置,不需要的方法不去拦截(配合annotation使用)”
当Controller中抛出异常时
-
最先捕获到异常的是Aspect
-
如果声明了ControllerAdvice,那么其会接收到的Aspect未处理的异常
-
如果ControllerAdvice也不处理,那么就会继续向上抛,直到最后抛出到tomcat中
总结
总结来说:spring MethodInterceptor与springmvc HandlerInterceptor在功能上有区别,springmvc interceptor是拦截请求,而spring interceptor更为灵活
除了spring提供的拦截功能外,还有Servlet本身提供的Filter也可以实现拦截功能,在springmvc下使用Filter会拦截所有请求,可以使用其做全局的拦截。
另外,这三点在获取目标controller的目标method,以及获取目标方法的请求参数的能力上,也是有区别的
拦截方式 | 特点 |
---|---|
servlet的过滤器Filter | 可以拿到原始的http请求和响应的信息,但是拿不到真正处理这个请求的方法的信息 |
springmvc的interceptor | 在Filter中是不知道这个请求是哪个控制器的哪个方法来处理的。如果你需要这个信息的话,那么需要使用springmvc的interceptor。 拦截器可以拿到原始的http请求和响应的信息,也能拿到真正处理这个请求的方法的信息,但是其拿不到这个方法被调用的时候真正调用的参数的值 |
spring的切片Aspect | 可以拿到方法被调用的时候真正调用的参数的值,但是拿不到原始的http请求和响应的信息 |