前言: 看一百本书不如去把一本书弄懂, 实践才能出真知. 话不多说, 上代码吧:
自定义Filter
#### FilterDemo1 ####
package com.huntkey.rx.sceo.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* @description: Servlet 过滤器(功能: 对所有web资源(图片,页面等)拦截, 实现URL过滤,过滤敏感词汇、压缩响应信息等)
*
* 开发步骤:
* 1. 实现servlet(不是druid)中的Filter接口, 实现doFilter方法
* 2. 通过SpringBoot中的 FilterRegistrationBean 进行实例注册
*
* Filter 生命周期
* 创建(init): WEB服务器创建(Spring boot内置的tomcat), 在程序启动时, tomcat服务器创建Filter
* 实例对象(只创建一次), 并调用init方法(只执行一次), 准备拦截
* 运行(doFilter): 1. 在目标调用资源前, 对request/response做预处理
* 2. filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
* 3. 在目标调用资源后, 对response 数据再加工
* 销毁(destroy): WEB服务器销毁, 释放资源
*
* Filter 参数介绍
* filterConfig: { getFilterName: filter名称
* getServletContext: Servlet上下文对象的引用
* getInitParameter: 初始化参数(在xml或其他地方定义的数据 参考: ./file/filterConfig.txt)
* getInitParameterNames: 返回过滤器的所有初始化参数的名字的枚举集合 }
* FilterChain 过滤链: { WEB 根据 Filter在 springboot 中的注册顺序(FilterRegistrationBean 中的order)
* 决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链
* 的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter
* 方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,
* 如果没有,则调用目标资源 }
*
*
* @author: wangml
* @date: 2021/7/21 14:41
*/
public class FilterDemo1 implements Filter {
private static Logger logger = LoggerFactory.getLogger(FilterDemo1.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("-------------WEB服务器初始化init过滤器FilterDemo1-----------");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 将ServletRequest 转为Http请求
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
accessResourceBefore(httpRequest, servletResponse);
filterChain.doFilter(httpRequest, servletResponse);
accessResourceAfter(servletResponse);
}
@Override
public void destroy() {
logger.info("-------------WEB服务器destroy过滤器FilterDemo1-----------");
}
/**
* @description: request/response请求预处理
*
* @author: wangml
* @date: 16:21 2021/7/21
* @param: [request, response]
* @return: void
*/
private void accessResourceBefore(HttpServletRequest request, ServletResponse response) throws UnsupportedEncodingException {
logger.info("-------------FilterDemo1处理前(预处理)-------------");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
}
/**
* @description: response加工
*
* @author: wangml
* @date: 16:21 2021/7/21
* @param: [servletResponse]
* @return: void
*/
private void accessResourceAfter(ServletResponse response) {
logger.info("-------------FilterDemo1处理完成后(加工)-------------");
// 随便写的, 不要深究(自己按功能处理)
response.setCharacterEncoding("UTF-8");
}
}
#### FilterDemo2 ####
package com.huntkey.rx.sceo.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import java.io.IOException;
/**
* @description: 过滤器FilterDemo2
* @author: wangml
* @date: 2021/7/21 16:30
*/
public class FilterDemo2 implements Filter {
private static Logger logger = LoggerFactory.getLogger(FilterDemo2.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("###############WEB服务器初始化init过滤器FilterDemo2#############");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("######### FilterDemo2处理前 #########");
// TODO: 自己定义判断逻辑,比如MD5算法校验jwt是否正确
Boolean flag = true;
if (flag) {
logger.info("######### FilterDemo2放行前 #########");
filterChain.doFilter(servletRequest, servletResponse);
}
logger.info("######### FilterDemo2处理后 #########");
}
@Override
public void destroy() {
logger.info("##################WEB服务器destroy过滤器FilterDemo2##############");
}
}
#### FilterDemo3 ####
package com.huntkey.rx.sceo.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import java.io.IOException;
/**
* @description: 过滤器FilterDemo3
* @author: wangml
* @date: 2021/7/21 16:30
*/
public class FilterDemo3 implements Filter {
private static Logger logger = LoggerFactory.getLogger(FilterDemo3.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("$$$$$$$$$$$$$$ WEB服务器初始化init过滤器 FilterDemo3 $$$$$$$$$$$");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("$$$$$$$$$$$$$$ FilterDemo3处理前 $$$$$$$$$$$$$$");
// TODO: 自己定义判断逻辑,比如MD5算法校验jwt中令牌是否正确
Boolean flag = true;
if (flag) {
logger.info("$$$$$$$$$$$$$$ FilterDemo3放行前 $$$$$$$$$$$$$$");
filterChain.doFilter(servletRequest, servletResponse);
}
logger.info("$$$$$$$$$$$$$$ FilterDemo3处理后 $$$$$$$$$$$$$$");
}
@Override
public void destroy() {
logger.info("$$$$$$$$$$$$$$ WEB服务器destroy过滤器FilterDemo3 $$$$$$$$$$$");
}
}
过滤器注册
package com.huntkey.rx.sceo.filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description: 过滤器注册
* @author: wangml
* @date: 2021/7/21 14:34
*/
@Configuration
public class FilterCite {
@Bean
public FilterRegistrationBean registerFilterDemo1() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new FilterDemo1());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
@Bean
public FilterRegistrationBean<FilterDemo2> registerFilterDemo2() {
FilterRegistrationBean<FilterDemo2> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new FilterDemo2());
registrationBean.addUrlPatterns("/filter/*");
registrationBean.setOrder(10);
return registrationBean;
}
@Bean
public FilterRegistrationBean registerFilterDemo3() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new FilterDemo3());
registrationBean.addUrlPatterns("/filter/*");
registrationBean.setOrder(20);
return registrationBean;
}
}
测试
package com.huntkey.rx.sceo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: 测试filter使用
* @author: wangml
* @date: 2021/7/21 16:51
*/
@RestController
@RequestMapping(value = "/filter")
public class FilterTestController {
private static Logger logger = LoggerFactory.getLogger(FilterTestController.class);
@GetMapping("/load")
public String load() {
logger.info("************控制器方法**********");
return "万古剑道长如夜, 天不生我wml";
}
}
启动项目, 可以查看到如下信息, 说明在web启动完成后, 是会去调用filter中的init方法初始化过滤器
2021-07-22 09:29:38.819 INFO 6120 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-07-22 09:29:38.819 INFO 6120 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1537 ms
2021-07-22 09:29:38.884 INFO 6120 --- [ restartedMain] com.huntkey.rx.sceo.filter.FilterDemo2 : ###############WEB服务器初始化init过滤器FilterDemo2#############
2021-07-22 09:29:38.884 INFO 6120 --- [ restartedMain] com.huntkey.rx.sceo.filter.FilterDemo1 : -------------WEB服务器初始化init过滤器FilterDemo1-----------
2021-07-22 09:29:38.884 INFO 6120 --- [ restartedMain] com.huntkey.rx.sceo.filter.FilterDemo3 : $$$$$$$$$$$$$$ WEB服务器初始化init过滤器 FilterDemo3 $$$$$$$$$$$
浏览器 访问 http://localhost:8080/filter/load 显示 万古剑道长如夜, 天不生我wml, 控制台显示如下
2021-07-22 09:57:21.592 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo1 : -------------FilterDemo1处理前(预处理)-------------
2021-07-22 09:57:21.593 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo2 : ######### FilterDemo2处理前 #########
2021-07-22 09:57:21.593 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo2 : ######### FilterDemo2放行前 #########
2021-07-22 09:57:21.593 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo3 : $$$$$$$$$$$$$$ FilterDemo3处理前 $$$$$$$$$$$$$$
2021-07-22 09:57:21.593 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo3 : $$$$$$$$$$$$$$ FilterDemo3放行前 $$$$$$$$$$$$$$
2021-07-22 09:57:21.596 INFO 6120 --- [nio-8080-exec-5] c.h.r.s.controller.FilterTestController : ************控制器方法**********
2021-07-22 09:57:21.600 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo3 : $$$$$$$$$$$$$$ FilterDemo3处理后 $$$$$$$$$$$$$$
2021-07-22 09:57:21.600 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo2 : ######### FilterDemo2处理后 #########
2021-07-22 09:57:21.600 INFO 6120 --- [nio-8080-exec-5] com.huntkey.rx.sceo.filter.FilterDemo1 : -------------FilterDemo1处理完成后(加工)-------------
三个过滤器都会拦截, 实际上最后只通过 优先级最低的过滤器进行放行, 和我们的要求相符合, 优先级高的过滤器先拦截, 满足要求的话才能继续往下继续
总结
- 过滤器过滤代码的过程相当于一个闭环过程, 参考上面例子说明: FilterDemo1(前, 中, 后), FilterDemo2(前, 中, 后) FilterDemo3(前, 中, 后), 划分节点是 filterChain.doFilter 执行(中), 具体的过程就可表示为 FilterDemo1前 -> FilterDemo2 前 -> FilterDemo3 前 -> FilterDemo3中 -> FilterDemo3后 -> FilterDemo2后 -> FilterDemo1后
- 每次过滤最多只会执行 filterChain.doFilter 一次
- 在定义过滤器的时候最好不要多次在doFilter 前后定义对请求和响应的处理, 否则根本不知道问题出在哪, 最好在 /* 路径下对请求和响应添加相应处理(防止覆盖)