【记录版】SpringBoot下Filter注册源码解读

SpringBoot + TomcatEmbeddedContext + Servlet + ApplicationFilterChain + Filter

背景: 在之前博客中有说明SpringBoot内嵌Web容器后,Filter及Servlet解析与注册流程的变化。将Filter实例封装成FilterRegistrationBean实例并添加到ServletContext后,到实际使用Filter完成过滤功能之前,其实中间还有一些管理流程。本文将在此背景下,继续追加描述此块逻辑。

博客内容精选:
1、Servlet请求体重复读&修改新姿势
2、根据请求获取后端接口详情
3、SpringBoot下Filter自动适配
4、Filter链式执行设计解读

一、首
先Filter相关定义经解析过存储在TomcatEmbeddedContext对象中,有三个重要变量:
1、private Map<String, FilterDef> filterDefs // 存储Filter定义元数据,如filterClass、name等
2、private final StandardContext.ContextFilterMaps filterMaps // FilterMap-匹配元数据,如String[] urlPatterns
3、private Map<String, ApplicationFilterConfig> filterConfigs // 由filterDefs可封装成ApplicationFilterConfig,核心为Filter实例

ApplicationFilterConfig(Context context, FilterDef filterDef) throws Exception{
    this.context = context;
    this.filterDef = filterDef;
    if (filterDef.getFilter() == null) {
        this.getFilter();
    } else {
        this.filter = filterDef.getFilter();
        context.getInstanceManager().newInstance(this.filter);
        this.initFilter();
    }
}

我们都知道Filter要发布到Servlet容器,先以FilterRegistrationBean的形式封装,因注册Bean底层接口包含ServletContextInitializer,其在onStartup(ServletContext servletContext)方法执行下注册到ServletContext中去,以下为注册代码:

// 执行入口
public final void onStartup(ServletContext servletContext) throws ServletException {
    ..........省略.............
    this.register(description, servletContext);
}
// 注册入口
protected final void register(String description, ServletContext servletContext) {
	// 将Filter实例加入Context
    D registration = this.addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
    } else {
    	// 配置Filter匹配元数据
        this.configure(registration);
    }
}
// Filter实例对象注册
protected Dynamic addRegistration(String description, ServletContext servletContext) {
    Filter filter = this.getFilter();
	// 此处可将Filter实例添加到FilterDef =》filterDef.setFilter(filter)
	// 返回ApplicationFilterRegistration(filterDef, this.context)对象
    return servletContext.addFilter(this.getOrDeduceName(filter), filter);
}
// URL等匹配元数据注册
protected void configure(Dynamic registration) {
    super.configure(registration);
    EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
    ..........省略..........
	// 重点
    registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
}

public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {
    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName(this.filterDef.getFilterName());
    ..........
    if (urlPatterns != null) {
        for(int var7 = 0; var7 < urlPatterns.length; ++var7) {
            String urlPattern = urlPatterns[var7];
            filterMap.addURLPattern(urlPattern);
        }
        if (isMatchAfter) {
			// 此处即为添加匹配元数据到Servlet上下文中
            this.context.addFilterMap(filterMap);
        } else {
            this.context.addFilterMapBefore(filterMap);
        }
    }
}

二、ApplicationFilterFactory使用createFilterChain工厂方法创建ApplicationFilterChain链
createFilterChain有两大核心逻辑
1)创建ApplicationFilterChain实例

ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
    Request req = (Request)request;
    if (Globals.IS_SECURITY_ENABLED) {
        filterChain = new ApplicationFilterChain();
    } else {
        filterChain = (ApplicationFilterChain)req.getFilterChain();
        if (filterChain == null) {
        	// 核心逻辑,直接创建Chain实例,且构造器无其他初始化逻辑
            filterChain = new ApplicationFilterChain();
            // chain对象复用,注意这里req对象是org.apache.catalina.connector.Request
            // chain内容不复用,filterChain使用完毕会调用release方法释放资源
            req.setFilterChain(filterChain);
        }
    }
} else {
    filterChain = new ApplicationFilterChain();
}

2)过滤器链初始化【极简版】

// Servlet设置
filterChain.setServlet(servlet); // DispatcherServlet实例
StandardContext context = (StandardContext)wrapper.getParent(); // TomcatEmbeddedContext
// 获取Servlet上下文中的过滤器匹配元数据
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0) {
	// 分发类型,一般为Request
    DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
	// 请求URI解析,此处忽略   
    String requestPath = xxx;

    String servletName = wrapper.getName(); // 值为dispatcherServlet

    FilterMap filterMap;
    ApplicationFilterConfig filterConfig;
    for(int index = 0; index < filterMaps.length; ++index) {
        filterMap = filterMaps[index];
        // 目前Springboot项目基本只需解析matchFiltersURL方法,根据URL匹配对应Filter
        if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
            filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig != null) {
            	// 过滤器链中数组对象存的是ApplicationFilterConfig实例,而不是Filter实例,因为部分场景需要元数据作为判断条件
                filterChain.addFilter(filterConfig);
            }
        }
    }

	// Servlet匹配Filter当前无用,仅作参考。结构类似,代码忽略
  	..............................
    return filterChain;
}

三、过滤器链执行入口:StandardWrapperValve
在StandardWrapperValve中,由invoke(Request request, Response response)方法内执行过滤器链,StandardWrapperValve前也是通过责任链模式一步步传递过来,也有相似的Valve实例。

过滤器链执行流程很简单,可以简化为以下过程:
// 注:这里传给Filter的其实是RequestFacade对象,包装了底层的Request对象,Response同理
1、filterChain.doFilter(request.getRequest(), response.getResponse())

2、中间过滤器链执行流程,已在其他博客说明

//过滤器链每次执行后,释放资源
3、filterChain.release()

总结:本文将Filter在Servlet上下文的注册流程、FilterChain过滤器链的封装流程基本讲述完成,其他Filter相关知识可参考之前其他博客内容,剩余细节不在展开。

思考:
1、过滤器池化相关概念,实际不是FilterChain的池化,其每次会recycle,而是org.apache.coyote.Request请求对象的池化(注意:此对象不是实现HttpServletRequest的org.apache.catalina.connector.Request)。Request对象较为底层且复杂,在并发量较低时,同一接口间隔请求都是一个Request对象,使用完成也会调用recycle回收,response同理。
2、FilterChain在Request池化基础上,每次不会新建,但是每次请求都会重新生成ApplicationFilterConfig数组。按现在Springboot请求流程,暂不清楚为啥需要recycle,(只是元数据封装,性能影响很小)可能考虑到Filter可能在服务期间被destroy?
3、至于线程安全问题,FilterChain是多例的,Filter是单例的,但是被多个FilterChain封装,也能并发执行,所以不要在Filter中定义共享变量。

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot注册 Filter 有两种方式: 1. 使用 @Bean 注解注册 FilterSpring Boot 项目中,可以通过在一个配置类中定义一个 Filter 的 Bean 来注册 Filter。具体步骤如下: 1)编写 Filter 类 首先,需要编写一个 Filter 类,该类必须实现 javax.servlet.Filter 接口。 例如: ``` public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化方法 } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 过滤方法 } @Override public void destroy() { // 销毁方法 } } ``` 2)在配置类中注册 Filter 然后,在一个配置类中使用 @Bean 注解注册 Filter: ``` @Configuration public class MyConfiguration { @Bean public FilterRegistrationBean<MyFilter> myFilter() { FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new MyFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setName("MyFilter"); registrationBean.setOrder(1); return registrationBean; } } ``` 在上面的代码中,我们使用 FilterRegistrationBean 类来注册 Filter,并设置 Filter 的相关属性,比如 URL 模式、名称、执行顺序等。 2. 使用 @WebFilter 注解注册 Filter 另一种方式是使用 @WebFilter 注解来注册 Filter,该注解需要在 Filter 类上使用。具体步骤如下: 1)编写 Filter 类 同样需要编写一个实现 javax.servlet.Filter 接口的 Filter 类。 例如: ``` @WebFilter(urlPatterns = "/*", filterName = "MyFilter") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化方法 } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 过滤方法 } @Override public void destroy() { // 销毁方法 } } ``` 2)启动类添加 @ServletComponentScan 注解 在启动类上添加 @ServletComponentScan 注解,用于启用 Servlet 组件扫描。 例如: ``` @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 在上面的代码中,@ServletComponentScan 注解告诉 Spring Boot 扫描 @WebFilter、@WebServlet、@WebListener 注解,并将其注册Servlet 容器中。 总的来说,使用 @Bean 注解注册 Filter 更加灵活,可以动态地设置 Filter 的属性,而使用 @WebFilter 注解则更加简便,可以一次性完成 Filter注册和属性设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值