在很多项目中通常需要用到filter来实现用户身份识别,并将识别出来的用户信息,保存到ThreadLocal对应的上下文,这样在后续的请求链路中,在任何地方都可以直接获取当前的登录用户了。
来看一下Java WEB三大组件之一的过滤器Filter,是如何在技术派中发挥作用的
使用场景
实现类路径:com/github/paicoding/forum/web/hook/filter/ReqRecordFilter.java
1.Filter基础知识点
来了解一下Filter的基础把。
Filter称为过滤器,主要用来拦截http请求,来做一些其他的事情
流程说明
一个http请求过来之后
首先进入filter,执行相关的业务逻辑
若判定通行,则进入Servlet逻辑,Servlet执行完毕后,又返回Filter,最后在返回给请求方
判定失败,直接返回,不需要将请求发给Servlet
应用场景
通过上面的流程,可以推算下具体的使用场景:
在filter层,来获取用户的身份
可以考虑在filter层做一些常规的校验(如参数校验。referer校验、权限控制等)
可以在filter层做运维、安全防护相关的工作(如全链路打点,可以在filter层分配一个traceId;也可以在这一层做限流等)
实现说明:
filter的基本使用比较简单,实现Filter接口即可,如;
@Slf4j
@WebFilter(urlPatterns = "/*", filterName = "reqRecordFilter", asyncSupported = true)
public class ReqRecordFilter implements Filter {
private static Logger REQ_LOG = LoggerFactory.getLogger("req");
/**
* 返回给前端的traceId,用于日志追踪
*/
private static final String GLOBAL_TRACE_ID_HEADER = "g-trace-id";
@Autowired
private GlobalInitService globalInitService;
@Autowired
private StatisticsSettingService statisticsSettingService;
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
long start = System.currentTimeMillis();
HttpServletRequest request = null;
StopWatch stopWatch = new StopWatch("请求耗时");
try {
stopWatch.start("请求参数构建");
request = this.initReqInfo((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
stopWatch.stop();
stopWatch.start("cors");
CrossUtil.buildCors(request, (HttpServletResponse) servletResponse);
stopWatch.stop();
stopWatch.start("业务执行");
filterChain.doFilter(request, servletResponse);
stopWatch.stop();
} finally {
stopWatch.start("输出请求日志");
buildRequestLog(ReqInfoContext.getReqInfo(), request, System.currentTimeMillis() - start);
// 一个链路请求完毕,清空MDC相关的变量(如GlobalTraceId,用户信息)
MdcUtil.clear();
ReqInfoContext.clear();
stopWatch.stop();
if (!isStaticURI(request) && !EnvUtil.isPro()) {
log.info("{} - cost:\n{}", request.getRequestURI(), stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
}
}
}
@Override
public void destroy() {
}
上面有三个方法:
init: 初始化执行
destory: 销毁时执行
dofilter: 重点关注这个,filter规则命中请求,都会走进来
三个参数,注意第三个FilterChain,这里是经典的责任链模式
执行filterChain.doFilter(request, servletResponse); 表示会继续将请求执行下去;若不执行这一句,表示这一次的http请求到此为止,后面免得不走下去了。
3.filter注册
过滤注册到Spring容器有多种使用姿势,出上面使用的@WebFilter之外还有其他的使用姿势。
3.1 WebFilter注解
使用WebFilter注解,标注到实现自己额的过滤器上,有几个参数需要注意,其中urlPatterns最为常用,表示这个filter使用与那些url请求(默认场景下全部都被拦截)
属性名 | 类型 | 描述 |
filterName | String | 指定过滤器的name属性,等价于<filter-name> |
value | String[] | 该属性等价于urlPatterns属性。但是两者不应该同时使用。 |
urlPatterns | String[] | 指定一组过滤器的URL匹配模式。等价于<url-pattern>标签 |
servletNames | String[] | 指定过滤器将应用于哪些Servlet。取值是@WebServlet中的name属性的取值,或者是web.xml中<servlet-name>的取值 |
dispatcherTypes | DispatcherTypes | 指定过滤器的转发模式。具体取值包括: ASYNC、ERROR、FORWARD、INCLUDE、REQUEST |
initParams | WebInitParm[] | 指定一组过滤器初始化参数。等价于<init-param>标签 |
asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于<async-supported>标签 |
description | String | 该过滤器的描述信息,等价于<description> |
displayName | String | 该过滤器的显示名,通常配合工具使用,等价于<display-name> |
使用这个注解时,请注意,需要在启动类/配置类添加@ServletComponentScan注解来启用
如:
3.2 FilterRegistrationBean
上面这一种比较简单,但是再指定Filter的优先级的时候比较麻烦,不如下面这种方式简单
4.实例说明
接下来我们看一下,filter在技术派中的具体表现,干了那三件事:
身份识别 ,并保存身份到ReqInfoContext上下文中:
记录请求记录:
添加跨域支持: