Spring 源码解析 -- SpringWeb 过滤器 Filter 解析
简介
在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如 Filter。本篇探索 Filter 初始化、请求处理等相关代码。
前言
说先简单的定义相关的测试代码:
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan
@SpringBootApplication
public class SpringExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringExampleApplication.class, args);
}
}
Controller 相关代码:
import com.example.springexample.vo.User;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloWorld {
@GetMapping("/")
public String helloWorld(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name) {
return "Hello world:" + id;
}
@GetMapping("/test1")
public String helloWorld1(@RequestParam(value = "id") Integer id) {
return "Hello world:" + id;
}
@PostMapping("/test2")
public String helloWorld2(@RequestBody User user) {
return "Hello world:" + user.toString();
}
}
Filter 相关代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Slf4j
@Order(1)
@WebFilter(filterName = "MyFilter1", urlPatterns = "/test1")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("My filter log 1");
chain.doFilter(request, response);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Slf4j
@Order(2)
@WebFilter(filterName = "MyFilter2", urlPatterns = "/test2")
public class MyFilter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("My filter log 2");
chain.doFilter(request, response);
}
}
核心的代码如上,相关的请求 Filter 处理如下:
/ : 两个 Filter 都不触发
/test1 : 触发 MyFilter1
/test2 : 触发 MyFilter2
符合我们的使用预期,接下来我们到源码中探索:
1.Filter 是如何初始化的
2.Filter 是如何对应相关的 URL 请求的
源码解析
探索时是直接在 MyFilter 类打上断点,一步步探索堆栈,得到的相关源码如下
Filter 初始化
首先是找到 Filter 相关类获取并遍历的相关代码
下面的函数中,遍历获得了系统内置的和我们自己定义的 Filter(如何获取的细节先不深究,在这里能得到所有的 Filter)
# ServletWebServerApplicationContext.class
private void selfInitialize(ServletContext servletContext) throws ServletException {
this.prepareWebApplicationContext(servletContext);
this.registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
// 得到了所有的Filter,遍历处理
Iterator var2 = this.getServletContextInitializerBeans().iterator();
while(var2.hasNext()) {
ServletContextInitializer beans = (ServletContextInitializer)var2.next();
beans.onStartup(servletContext);
}
}
接下来来到添加注册 Filter 相关的代码部分
# AbstractFilterRegistrationBean.class
protected void configure(Dynamic registration) {
super.configure(registration);
EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
T filter = this.getFilter();
if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter", filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
dispatcherTypes = EnumSet.allOf(DispatcherType.class);
} else {
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
}
Set<String> servletNames = new LinkedHashSet();
Iterator var4 = this.servletRegistrationBeans.iterator();
// 这部分代码作用尚不明确,留待以后探索
while(var4.hasNext()) {
ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next();
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
// 系统默认的都走的这部分处理,拦截路径默认都是:/**
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
} else {
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames));
}
// 我们自定义的都都了这里,拦截路径就是我们配置的:/test1,/test2
if (!this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
}
}
}
在上面的代码中,我们看到了拦截路径的配置有两个方式:
servletNames
urlPatterns
接着跟下去,下降就是将 Filter 添加
# ApplicationFilterRegistration.java
public void addMappingForUrlPatterns(
EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
String... urlPatterns) {
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filterDef.getFilterName());
if (dispatcherTypes != null) {
for (DispatcherType dispatcherType : dispatcherTypes) {
filterMap.setDispatcher(dispatcherType.name());
}
}
// Filter添加的相关代码
if (urlPatterns != null) {
// % decoded (if necessary) using UTF-8
for (String urlPattern : urlPatterns) {
filterMap.addURLPattern(urlPattern);
}
if (isMatchAfter) {
context.addFilterMap(filterMap);
} else {
context.addFilterMapBefore(filterMap);
}
}
// else error?
}
上面的代码中,Filter 添加有两处代码:一个是添加到 Map 中,一个是 context 中,后者好像还是有其他道道,后面继续研究看看
到这里,Filter 就初始化完成了,下面看看使用方面的代码
Filter 匹配添加
在日常开发中,Filter 我们都会配置相关的匹配路径,不是所有的请求都进行过滤,那这块的匹配是怎么的?接下来就发起请求,探索 Filter 的匹配添加
下面的代码是核心的 Filter 匹配处理,但前面的触发调用目前暂时还没有梳理清楚,Wrapper 好像挺关键的,暂时忽略它,先看 Filter 匹配处理相关的
# ApplicationFilterFactory.java
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
......
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
// 获取Filter,得到上面初始化的Filter
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0)) {
return filterChain;
}
// Acquire the information we will need to match filter mappings
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
// 请求的路径
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// 在这里就进行匹配了
// Add the relevant path-mapped filters to this filter chain
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMap, requestPath)) {
continue;
}
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName)) {
continue;
}
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
// 上面的触发调用后,就来到了下面的将Filter添加到列表中的相关diam
# ApplicationFilterChain.java
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters) {
if(filter==filterConfig) {
return;
}
}
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
上面的就是核心的 Filter 匹配添加的核心代码,值得注意的点有下面几个:
会被匹配添加两次
匹配有下面三种方式:
matchDispatcher(filterMap, dispatcher)
matchFiltersURL(filterMap, requestPath)
matchFiltersServlet(filterMap, servletName)
这里就有下面两点疑问了:
为啥需要将两次匹配分开:是为了前后 Filter 区分?
两次 Filter 循环匹配,好像就是匹配路径 requestPath 和匹配 ServletName 的区别,两者有何不同,为啥需要分开?
关于上面的疑问目前我也没找到确定的线索,后面的探索中,应该能把它补上
Filter 触发
经过上面的 Filter 匹配,请求的 Filter 就初始化好了,下面就进入到处理调用环节
# ApplicationFilterChain.java
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
(java.security.PrivilegedExceptionAction<Void>) () -> {
internalDoFilter(req,res);
return null;
}
);
} catch( PrivilegedActionException pe) {
......
}
} else {
// 调用触发
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 获取当前Filter
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
// 调用触发
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
......
}
return;
}
// 下面的代码有疑似结束Filter,触发请求函数处理相关的代码。先打个标记,后面探索
// We fell off the end of the chain -- call the servlet instance
......
}
上面就是 Filter 调用触发的核心代码了,链式触发调用,在 SpringCloudGateway 和 Netty 中都有类型的相关代码,看着这种代码模式很经典啊,但细节就后面研究了
总结
经过上面的代码分析,Filter 的基本核心代码已经被我们找到了:
1.Filter 的初始化:在应用程序启动时,进行 Filter 的初始化
2.Filter 的匹配添加:对于不用的请求路径,会匹配生成不同的 Filter 链路
其中有用缓存吗:经过实验,每次请求都会进行匹配
3.链式调用处理
其中还有很多疑问点,还有 Order 排序相关的好像没有找到,感兴趣的可以自行查找下
参考链接
版权声明: 本文为 InfoQ 作者【萧】的原创文章。
原文链接:【http://xie.infoq.cn/article/e9d69165829bdcb8737e98ccd】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
萧
还未添加个人签名 2018.09.09 加入
代码是门手艺活,也是门艺术活
评论