FilterChainProxy 探索分析
今天看一个项目中的前端请求接口响应时间,controller方法监控只有2-300ms,但是页面上浏览器返回却占用了将近3s。后来用jprofile工具追踪了一下调用路径,发现方法在到达controller 方法之前执行了两个过滤器的方法,占用了一定的时间。借机分析了一下过滤器的执行过程。
FilterChainProxy 与Spring Security 过滤链相关,具体可查看相关源码
可参考 :
详细可查看这些源码,分析 FilterChainProxy 的具体使用及功能。
本篇内容重点介绍FilterChainProxy
过滤时使用的责任链模式。
doFilterInternal 方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 重点是这个方法
doFilterInternal(request, response, chain);
}
catch (Exception ex) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
Throwable requestRejectedException = this.throwableAnalyzer
.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
if (!(requestRejectedException instanceof RequestRejectedException)) {
throw ex;
}
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,
(RequestRejectedException) requestRejectedException);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
具体实现:
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
// 查看 getFilters逻辑
List<Filter> filters = getFilters(firewallRequest);
if (filters == null || filters.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
return;
}
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
// 创建虚拟的过滤器链
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
// 执行过滤器
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
getFilters:获取所有的过滤器
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
this.filterChains.size()));
}
// 从 DefaultSecurityFilterChain 中,拿到注册好的Filters
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
virtualFilterChain.doFilter:执行过滤器
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
return;
}
this.currentPosition++;
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
this.currentPosition, this.size));
}
nextFilter.doFilter(request, response, this);
}
点睛之笔:nextFilter.doFilter(request, response, this);
这里才是过滤链调度的地方。
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {}
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
default void destroy() {}
}
每个过滤器肯定实现了 Filter接口,其核心就是 doFilter方法。所以当执行到 virtualFilterChain.doFilter时,传入了一个this,那么当前过滤器执行完成以后,又可以回到当前的逻辑中执行下一个Filter。
执行顺序在调用getFilters() 方法的时候就已经拿到了,源码中又可以修改执行顺序的地方,这里不详细描述。
重点总结
参考 virtualFilterChain.doFilter,责任链可以这么用:
重点理解责任链一个设计的逻辑:
1、肯定要有个调度者
2、调度者有一个方法能拿到所有的处理器
3、定义一个接口和方法,所有的处理器实现这个方法,方法中包含一个入参就是调度者
4、调度者中拿到处理器以后,调用这个方法,调用的时候把自身传进去。
具体可根据业务的实际情况设计
原来的设计模式我记得是每一个处理器里面注入下一个处理器,感觉还是有耦合性,不如这个设计好,记录一下以后实践