SpringBoot实现过滤器Filter的三种方式
过滤器 Filter 是 Web 三大组件之一,也是项目常用到的工具,本文主要介绍一下 Filter的概念以及常见的使用方式。
过滤器Filter
过滤器 Filter 由 Servlet 提供,基于函数回调实现链式对网络请求与响应的拦截与修改。由于基于 Servlet ,其可以对web服务器管理的几乎所有资源进行拦截(JSP、图片文件、HTML 文件、CSS文件等)。
定义一个过滤器,需要实现 javax.servlet.Filter 接口。
Filter 并不是一个 Servlet,它不能直接向客户端生成响应,只是拦截已有的请求,对不需要或不符合的信息资源进行预处理。
过滤器可以定义多个,按照过滤器链顺序调用:
Filter 的生命周期
init(): 初始化Filter 实例,Filter 的生命周期与 Servlet 是相同的,也就是当 Web 容器(tomcat)启动时,调用 init() 方法初始化实例,Filter只会初始化一次。需要设置初始化参数的时候,可以写到init()方法中。
doFilter(): 业务处理,拦截要执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现
destroy() : 销毁实例,关闭容器时调用 destroy() 销毁 Filter 的实例。
过滤器的使用方式
首先要实现 javax.servlet.Filter 接口,之后将 Filter 声明为 Bean 交由 Spring 容器管理。以 SpringBoot 为示例:
方式一:@WebFilter注解
通过 @WebFilter 注解,将类声明为 Bean 过滤器类,在启动类添加注解 @ServletComponentScan ,让 Spring 可以扫描到。此时 可以指定要拦截的url , 但是不能指定过滤器执行顺序
@WebFilter public class WebVisitFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 输出访问 ip */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { //获取访问 ip 地址 HttpServletRequest req = (HttpServletRequest) request; String visitIp = req.getRemoteAddr(); visitIp = "0:0:0:0:0:0:0:1".equals(visitIp) ? "127.0.0.1" : visitIp; // 每次拦截到请求输出访问 ip System.out.println("访问 IP = " + visitIp); chain.doFilter(req, response); } @Override public void destroy() { } } @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
@WebFilter作用
Tomcat 的 servlet 包下的注解,通过 @WebFilter 注解可以将指定类声明为过滤器。
@WebFilter 属性中没有配置顺序的,其执行顺序和 Filter 类名称字符排序有关,如果需要设置执行顺序,可以在命名的时候注意一下。
方式二:@Component注解
使用 @Component 将类声明为 Bean ,配合使用 @Order 注解可以设置过滤器执行顺序。此时过滤器执行顺序排序是有效的, 但是过滤器不能指定拦截的url , 只能默认拦截全部
@Order(1) @Component public class WebVisitFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 输出访问 IP */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { // 业务处理 } @Override public void destroy() { } }
方式三:Java Config 配置类
使用 @Configuration + @Bean 配置类,注解声明Bean,交由 Spring 容器管理。此方式既能拦截Url,也能指定执行顺序
Java Config 的方式可以通过 @Bean 配置顺序或 FilterRegistrationBean.setOrder() 决定 Filter 执行顺序。(在启动类配置拦截器,此时自定义过滤器不加注解,为普通类即可) 可以指定过滤器要拦截的url 和 过滤器执行顺序, 但需要代码方式实现
public class Test1Filter implements Filter { @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletRequest request=(HttpServletRequest)arg0; System.out.println("自定义过滤器filter1触发,拦截url:"+request.getRequestURI()); arg2.doFilter(arg0,arg1); } } public class Test2Filter implements Filter { @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletRequest request=(HttpServletRequest)arg0; System.out.println("自定义过滤器filter2触发,拦截url:"+request.getRequestURI()); arg2.doFilter(arg0,arg1); } }
通过在springboot的configuration中配置不同的FilterRegistrationBean实例,来注册自定义过滤器
这里创建一个configuration类
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.example.demo.filter.Test1Filter; import com.example.demo.filter.Test2Filter; @Configuration public class DemoConfiguration { @Bean public FilterRegistrationBeanRegistTest1(){ //通过FilterRegistrationBean实例设置优先级可以生效 //通过@WebFilter无效 FilterRegistrationBean bean = new FilterRegistrationBean (); bean.setFilter(new Test1Filter());//注册自定义过滤器 bean.setName("flilter1");//过滤器名称 bean.addUrlPatterns("/*");//过滤所有路径 bean.setOrder(1);//优先级,最顶级 return bean; } @Bean public FilterRegistrationBean RegistTest2(){ //通过FilterRegistrationBean实例设置优先级可以生效 //通过@WebFilter无效 FilterRegistrationBean bean = new FilterRegistrationBean (); bean.setFilter(new Test2Filter());//注册自定义过滤器 bean.setName("flilter2");//过滤器名称 bean.addUrlPatterns("/test/*");//过滤所有路径 bean.setOrder(6);//优先级,越低越优先 return bean; } }
其中
1第一个bean拦截所有路径,而第二个只拦截/test/*路径
2第一个bean优先级设置了1,而第二个设置了6,越低越优先,所以过滤器1应该在过滤器2前面拦截
运行springboot,访问/test/*请求如下图,再访问/v请求如下图只拦截了过滤器1,而过滤器2路径不匹配
FilterChain 的作用
过滤器链是一种责任链模式的设计实现,在一个Filter 处理完成业务后,通过 FilterChain 调用过滤器链中的下一个过滤器。
流程如下:
FilterChain 接口定义了 doFilter 方法
public interface FilterChain { public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; }
ApplicationFilterChain类实现了 FilterChain 接口,管理所有的 Filter 的执行与调用
public final class ApplicationFilterChain implements FilterChain { // 数组存储所有的过滤器链 private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 类中实现 doFilter() 方法 调用 调用 internalDoFilter(req,res) 方法 public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // ... //调用 internalDoFilter internalDoFilter(request,response); } }
internalDoFilter(req,res) 方法中实现 Filter 调用的具体的操作,如下:
//取得数组中下一个过滤器实例 ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); // ... //调用下一个过滤器的 doFilter() 方法 filter.doFilter(request, response, this);
通过这种方式完成整个过滤器链的调用执行。
常见应用场景
登录验证
统一编码处理
敏感字符过滤
过滤器链抛出异常处理方式
在过滤器进行拦截操作时,如发生异常,与业务类相同需要捕获异常进行记录并处理。如果想继续执行业务,可以通过 chain.doFilter(req, response); 对之后的过滤器进行调用。