文章目录
Interceptor 拦截器
所有的拦截器都需要实现 HandIerInterceptor 接口
HandlerInterceptor 源码
public interface HandlerInterceptor {
// 处理器执行前方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
// 处理器处理后方法
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
// 处理器完成后方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
Interceptor依赖于web框架,我们经常在Spring MVC中用到该配置,在这个场景下Interceptor 就依赖于SpringMVC框架。
Interceptor 基于Java的反射机制,属于AOP的一种运用
优点:
- 由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用
缺点:
- 只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
拦截器中方法的执行流程
- 请求到达 DispatcherServlet,DispatcherServlet 发送至 Interceptor ,执行 preHandle 方法,该方法会返回一个布尔值。如果为 false ,则结束所有流程:如果为 true , 则执行下一步。
- 请求达到 Controller,执行处理器逻辑,它包含控制器的功能 。
- 执行 postHandle 方法。
- 执行视图解析和视图渲染 。
- 执行 afterComp letion 方法。
传统项目拦截器的配置
基于Spring MVC的项目 ,我们之前的案例配置拦截器的方式如下:
拦截器的开发还是一样的没有变化,那如何注册和实例化拦截器呢? 上面是通过xml的方式来加载的 ,那基于Spring Boot的呢?
Spring Boot2.1.2整合拦截器Interceptor 示例
pom.xml仅需添加 spring-boot-starter-web即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Step1 实现HandlerInterceptor接口编写拦截器
继承HandlerInterceptorAdapter也可以
package com.artisan.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 实现 Handlerlnterceptor接口,覆盖其对应的方法即完成了拦截器的开发
*
* @author yangshangwei
*
*/
public class MyInterceptor implements HandlerInterceptor {
/**
* preHandle在执行Controller之前执行
* 返回true:继续执行处理器逻辑,包含Controller的功能
* 返回false:中断请求
*
* 处理器执行前方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor-处理器执行前方法preHandle,返回true则不拦截后续的处理");
return true;
}
/**
* postHandle在请求执行完之后 渲染ModelAndView返回之前执行
*
* 处理器处理后方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
System.out.println("MyInterceptor-处理器处理后方法postHandle");
}
/**
* afterCompletion在整个请求执行完毕后执行,无论是否发生异常都会执行
*
* 处理器完成后方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println("MyInterceptor-处理器完成后方法afterCompletion");
}
}
Step2 实现WebMvcConfigurer接口注册拦截器
先说明下几个可以用来注册拦截器的类和接口
- WebMvcConfigurerAdapter: 2.0以后的版本已失效
- WebMvcConfigurationSupport: 不需要返回逻辑视图,可以选择继承此类
- WebMvcConfigurer:返回逻辑视图,可以选择实现此方法,重写addInterceptor方法
我们这里选择使用实现WebMvcConfigurer接口
package com.artisan.conf;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.artisan.interceptor.MyInterceptor;
/**
* 实现 WebMvcConfigurer 接 口, 最后覆盖其addInterceptors方法进行注册拦截器
* @author yangshangwei
*
*/
// 标注为配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器到 Spring MVC 机制, 然后 它会返 回 一个拦截器注册
InterceptorRegistration regist = registry.addInterceptor(new MyInterceptor());
// 指定拦截匹配模式,限制拦截器拦截请求
regist.addPathPatterns("/artisan/interceptor/*");
// 不拦截的路径
regist.excludePathPatterns("/artisan/interceptor/exclude/*");
}
}
注意设置的规则,下面测试的时候需要对应上
Step3 验证
package com.artisan.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/artisan")
public class ArtisanController {
@GetMapping("/interceptor/test")
public String testInterceptor() {
System.out.println("执行处理器逻辑testInterceptor....");
return "请观察控制台中拦截器的日志输出";
}
@GetMapping("/interceptor/exclude/test")
public String testExcludeInterceptor() {
System.out.println("执行处理器逻辑testExcludeInterceptor....");
return "exclude排除,不会被拦截器拦截";
}
}
启动Spring Boot工程,访问
http://localhost:8080/artisan/interceptor/test 因为匹配到了拦截的规则,所以会被拦截
日志如下
访问 http://localhost:8080/artisan/interceptor/exclude/test 因为匹配到了exclude的规则,所以不会被拦截。 日志中没有拦截器信息的输出
多个拦截器的执行顺序
实际的开发中,多个拦截器是很正常的一个事儿,那么他们的执行顺序又是怎样的呢?
再新建两个拦截器MyInterceptor2和MyInterceptor3,为了验证下执行顺序,不搞的太复杂,代码和MyInterceptor一样,仅仅方法中的输出为了区分改成了对应的类名,如下所示
这3个拦截器的preHandle方法都返回true的情况下
先注册下拦截器
启动工程,访问 http://localhost:8080/artisan/interceptor/test
日志输出
上述结果是责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成方法则是先注册后执行的规则
如果将第二个拦截器MyInterceptor2 的 preHandle 方法修改返回为 false 呢?
测试下,日志输出如下
可以看出处理器前( preHandle )方法会执行,但是一旦返回 false ,则后续的拦截器、 处理器和所有拦截器的处理器后( postHandle ) 方法都不会被执行 。 完成方法 afterCompletion则不一样,它只会执行返回 true 的拦截器的完成方法,而且顺序是先注册后执行 。
Filter 过滤器
在开发传统的Spring项目时web.xml中配置的编码过滤器不知道你还记不记得?
比如这篇很久前写的这个基于Spring的SSM整合文章SSM-Spring+SpringMVC+MyBatis整合案例从0到1 中为了避免编码不一致增加了编码过滤器配置
既然是配置在web.xml中,那肯定是依赖于servlet容器.
优点:
- 在实现上Filter是基于函数回调,可以对几乎所有请求进行过滤
缺点:
- 一个过滤器实例只能在容器初始化时调用一次 . 当容器第一次加载该过滤器时,init() 方法将被调用
使用场景:
- 比如设置编码、过滤敏感词汇、禁止浏览器缓存所有动态页面、实现用户自动登陆、实现URL级别的权限认证等等 ,具体案例参考Filter(过滤器)常见应用
传统的JavaEE项目开发filter的主要2个步骤
- 实现Filter接口,并实现其doFilter方法。
- 在 web.xml 文件中使用
<filter>
和<filter-mapping>
元素对编写的filter类进行注册,并设置它所能拦截的资源 - 可以开发编写多个Filter,组成一个Filter链,根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter
Spring Boot中整合过滤器Filter的两种方式
方式一 FilterRegistrationBean注册
Step1 实现Filter接口 开发过滤器
package com.artisan.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HttpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HttpFilter init");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("HttpFilter doFilter begin");
HttpServletRequest req =(HttpServletRequest) request;
HttpServletResponse res =(HttpServletResponse) response;
System.out.println("HttpFilter name:" + request.getParameter("name"));
// 将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,如果没有filter那就是请求的资源。
chain.doFilter(request, response);
System.out.println("HttpFilter doFilter end");
}
@Override
public void destroy() {
System.out.println("HttpFilter destroy");
Filter.super.destroy();
}
}
Step2 在配置类中注册该过滤器
package com.artisan.conf;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.artisan.filter.HttpFilter;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<HttpFilter> httpFilter(){
FilterRegistrationBean<HttpFilter> filterRegistrationBean = new FilterRegistrationBean<HttpFilter>();
// 设置filter
filterRegistrationBean.setFilter(new HttpFilter());
// 拦截规则
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
Step3 启动项目 测试
观察启动日志
然后访问 http://localhost:8080/artisan/interceptor/test?name=artisan
方式二 @WebServlet+ @ServletComponentScan注解的方式
Step1 @WebFilter注解开发过滤器
@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器
package com.artisan.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter(filterName = "HttpFilter2", urlPatterns = "/*")
public class HttpFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HttpFilter2 init");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("HttpFilter2 doFilter begin");
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println("HttpFilter2 name:" + request.getParameter("name"));
// 将请求转发给过滤器链上下一个对象。这里的下一个指的是下一个filter,如果没有filter那就是请求的资源。
chain.doFilter(request, response);
System.out.println("HttpFilter2 doFilter end");
}
@Override
public void destroy() {
System.out.println("HttpFilter2 destroy");
Filter.super.destroy();
}
}
Step2 启动类增加@ServletComponentScan注解
在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册
Step3 启动测试
观察启动日志
访问 http://localhost:8080/artisan/interceptor/test?name=artisan
注意下filter的执行在interceptor之前
小结
Filter的执行顺序在Interceptor之前 。 拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,Interceptor都能做,而且可以在请求前,请求后执行,比较灵活。