spring-session源码解读-1

简介: ServletContainerInitializerServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架。例如Spring,我们使用它的web功能时,需要在we

ServletContainerInitializer

ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架。

例如Spring,我们使用它的web功能时,需要在web.xml中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置。

ServletContainerInitializer原理

容器启动阶段依据java spi获取到所有ServletContainerInitializer的实现类,然后执行ononStartup方法

public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)
        throws ServletException; 
}
  1. 实现类需要通过java spi声明自己是ServletContainerInitializer 的provider。
  2. 可以通过@HandlesTypes注解定义希望处理类型,容器会将当前应用中所有这一类型(继承或者实现)的类放在集合参数1中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数1为空。

在spring-session中的应用

spring-web通过java spi声明了SpringServletContainerInitializer。参考spring-web.jar下的meta-inf/services

以下是抽出SpringServletContainerInitializer的核心逻辑:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                initializers.add((WebApplicationInitializer) waiClass.newInstance());
            }
        }

        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

spring-session只处理WebApplicationInitializer类型,全部实例化后再依次调用WebApplicationInitializer的onStartup接口。

public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;

}

该接口定义了一个onStartup的方法,所以实现类都需要自己实现。spring-session提供了一个抽象的公用基类–org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer,由它实现了接口的onStartup方法

public void onStartup(ServletContext servletContext)
            throws ServletException {
        beforeSessionRepositoryFilter(servletContext);
        if(configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }
        insertSessionRepositoryFilter(servletContext);
        afterSessionRepositoryFilter(servletContext);
    }

这个方法的主要过程:
1. 创建了AnnotationConfigWebApplicationContext对象并且设置了配置类(@Configuration)。对spring的Configuration类不熟悉的,可以参考Spring基于编程的配置定义。其实就相当于是个beans配置。这时候ApplicationContext容器就设置完毕,使用AnnotationConfigWebApplicationContext,并且设置了相应的配置路劲。

  1. 添加ContextLoaderListener,使用过spring-mvc的肯定对这个Listener不陌生,这个Listener是整个spring-web的核心,由它来自动装配spring的核心容器ApplicationContext。第一步中的ApplicationContext作为构造参数传进了该listener。web容器启动时会通知该listener,该listener就会启动ApplicationContext的生命周期。并将bean加载完毕。因为这是spring-web边界内的,所以就不再展开,只要知道大概做了什么事情就行了。

  2. 通过insertSessionRepositoryFilter注册了一个Filter,从名字知道这个Filter叫SessionRepositoryFilter,顾名思义,它肯定是通过一定方式做session持久化的。

private void insertSessionRepositoryFilter(ServletContext servletContext) {
        String filterName = DEFAULT_FILTER_NAME;
        DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);
        String contextAttribute = getWebApplicationContextAttribute();
        if(contextAttribute != null) {
            springSessionRepositoryFilter.setContextAttribute(contextAttribute);
        }
        registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
    }

这个方法主要是动态装载了一个DelegatingFilterProxy,构造函数的入参是个静态变量,对应的值是“springSessionRepositoryFilter”。从“DelegatingFilterProxy”这个名字就大概能猜到这个filter最终会把请求delegate给具体的filter,看下入参大概就可以猜到应该是委派给“springSessionRepositoryFilter”这个filter,并且这个filter应该是由spring容器管理的,在filter生命周期的某个阶段会通过spring的依赖注入进来,并执行相关的拦截请求。

这里有一点会比较容易疑惑,spring为什么要通过一个代理类来做委派,直接注册SpringSessionRepositoryFilter不是更好。

关于这个问题会在下一章”容器对springSessionRepositoryFilter的依赖管理“里详细展开,这里继续沿着处理流往下。

这个类继承自GenericFilterBean,GenericFilterBean是spring抽象出来的一个filter公用接口。先看下GenericFilterBean的init方法:

public final void init(FilterConfig filterConfig) throws ServletException {
        // 伪代码,利用filterConfig里的参数为filter做属性自动注入

        // Let subclasses do whatever initialization they like.
        initFilterBean();
    }

这里只抽出了其中的核心逻辑,最后调用子类的initFilterBean

DelegatingFilterProxy覆盖了父类的initFilterBean方法

protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac);
                }
            }
        }
    }

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

initFilterBean方法通过spring容器来取得具体的filter bean,而WebApplicationContext对象就是上文提到的AnnotationConfigWebApplicationContext对象。ContextLoaderListener在spring容器refresh完后会把对应的容器保存进ServletContext里,默认的key值为WebApplicationContext.class.getName() + “.ROOT”。这里获取spring context的findWebApplicationContext方法就是从ServletContext里通过对应的key取得的。

至此DelegatingFilterProxy初始化完成,里面做实际处理的filter最终通过spring的依赖注入初始化并且注入进来。

目录
相关文章
|
18天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
31 0
|
21天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1月前
|
消息中间件 NoSQL Java
Spring Cloud项目实战Spring Cloud视频教程 含源码
Spring Cloud项目实战Spring Cloud视频教程 含源码
33 1
|
2月前
|
设计模式 Java Spring
【Spring源码】WebSocket做推送动作的底层实例是谁
我们都知道WebSocket可以主动推送消息给用户,那做推送动作的底层实例究竟是谁?我们先整体看下整个模块的组织机构。可以看到handleMessage方法定义了每个消息格式采用不同的消息处理方法,而这些方法该类并**没有实现**,而是留给了子类去实现。
26 1
【Spring源码】WebSocket做推送动作的底层实例是谁
|
2月前
|
存储 设计模式 Java
【Spring源码】Bean采用什么数据结构进行存储
我们再来看看中间新加入的阅读线索4,不知大家忘记了没。我们可以对照图片1的代码组织结构,发现这些没存储在包里的功能类都是比较杂乱的,想必是Spring觉得目前这些功能类还构不成一个包的体系,可能后面规模更大会统一集成起来管理。
29 1
【Spring源码】Bean采用什么数据结构进行存储
|
3天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
12 1
|
4天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
45 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
11天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
16天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
22天前
|
Java Maven Nacos
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
31 0