美文网首页
spring-循环依赖

spring-循环依赖

作者: 16325 | 来源:发表于2020-04-15 14:17 被阅读0次

spring依赖

在项目中有个springContextHolder,实现applicationContextAware。
这次需求增加了一个filter,用的servlet3.0的注解webfilter。然后再init方法中用这个holder的静态方法获取了一个spring的service。
开发完,测试没问题能拿到。但是原理不是很清楚。

谁先加载

spring的加载使用的编码方式,实现了WebApplicationInitializer,最终是实现了ServletContainerInitializer接口。

ServletContainerInitializer可以编码定义web.xml 中的内容。
这里有一个疑问:web.xml,注解,ServletContainerInitializer中编码增加的listener,filter等。他们的顺序如何控制?

上边的问题没有查到资料。但是肯定一点就是,servlet三大将肯定是按照listener-fileter-servlet这个顺序来加载的。也就是说我新增的WebFilter注解定义的过滤器,肯定是在spring加载完配置之后才init,所以这里肯定可以去到spring的applicatonContext

Filter的顺序

  • web.xml是按照mapping的顺序。
  • Webfilter注解,是按照类名排序。
  • ServletContainerInitializer中增加的,不知道。。

ServletContainerInitializer 的调用时机

以Tomcat举例, 以下逻辑总结于Tomcat7.x

  • 解析web.xml
  • 往ServletContext实例中注入<context-param> 参数
  • 回调Servlet3.0的ServletContainerInitializers接口实现类
  • 触发 Listener 事件(beforeContextInitialized, afterContextInitialized); 这里只会触发 ServletContextListener 类型的
  • 初始化 Filter, 调用其init方法
  • 加载 启动时即加载的servlet
  • 在servlet3.0之前都是采用ServletContextListener这种机制来初始化spring上下文。之后可以使用ServletContainerInitializer 用编码的方式加载spring的配置文件。
    通过上边可以看到,先在ServletContainerInitializers接口注册了listener,然后下一步就会触发listener的操作。对应spring来说,这时候已经开始加载配置文件了,后续在加载filter的时候,spring已经初始化完成
public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

ServletContext提供了各种各样的方法,可以完全替代所有web.xml中的配置。

spring配置(原始)

spring的配置,一般在项目中包括两部分。一是核心功能也就是ioc和aop,还有就是mvc,当然如果使用struts的话就没有这部分配置了。

先看看原始的,用xml的方式。核心配置
包括各种bean的定义,注入等。

  • 方式一:org.springframework.web.context.ContextLoaderServlet
    在Spring3.0之前或许还有使用ContextLoaderServlet来配置listener的,不过3.0版本之后已经删除了,目前只有通过ContextLoaderListener的方式来配置。
  • 方式二: org.springframework.web.context.ContextLoaderListener
    正常的配置方式,参考上一点的顺序,在filter之前,就会得到实际初始化。
<!--spring监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--spring配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
classpath:applicationContext_pub.xml,classpath:applicationContext-cxf-rs.xml,
        </param-value>
    </context-param>

springMvc
包括消息转换,controller的异常处理,当然也可以定义正常的bean

  • 注册DispatcherServlet
<!--配置springMVC-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-config.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  • 注意spring载入的WebApplicationContext是隶属于此Servlet的(所以spring可以配置多个分别拥有各自环境的DispatcherServlet),因此其他servlet无法获取到该Context。

spring配置(编码)

servlet3.0之后可以使用ServletContainerInitializer来编码定义了。这个接口会在容器初始化得到执行。然后可以注册listener,servlet等。

其实就是把web.xml中的配置用编码实现,spring的配置文件可以不动

 // root context
        XmlWebApplicationContext rootContext = new XmlWebApplicationContext();

        rootContext.setConfigLocations(resolveConfigPath("*-context.xml"), resolveConfigPath("*-context-cas.xml"));
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // web dispatcher servlet
        XmlWebApplicationContext web = new XmlWebApplicationContext();
        web.setParent(rootContext);
        web.setConfigLocation(resolveConfigPath("*-servlet.xml"));
        ServletRegistration.Dynamic app = servletContext.addServlet("appServlet", new DispatcherServlet(web));
        app.setLoadOnStartup(1);
        app.addMapping("/");

以上代码,其实就是注册了ContextLoaderListener和DispatcherServlet,和上一点在web.xml中的配置并无两样。

spring配置(全编码)

如果spring的配置文件不想写了,全都采用注解的方式呢?还是使用ServletContainerInitializer编码的方式 。但是不加载配置文件了。

 // root context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

        rootContext.register(RootConfig.class);
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // web dispatcher servlet
        AnnotationConfigWebApplicationContext web = new AnnotationConfigWebApplicationContext();
        web.setParent(rootContext);
        rootContext.register(WebConfig.class);
        ServletRegistration.Dynamic app = servletContext.addServlet("appServlet", new DispatcherServlet(web));
        app.setLoadOnStartup(1);
        app.addMapping("/");

RootConfig和WebConfig就对应spring的核心配置xml和mvc配置xml。把xml转成java代码的方式,各种注解套上就可以了。

spring的循环依赖

上边好像有点跑题了啊,写这篇主要是为了说spring循环依赖的问题。
a依赖b,b依赖a。不论是xml配置文件中或者是编码方式。这种方式spring的怎么解决的呢?

  • 如果是构造函数依赖,报错!
  • 如果是prototpye的bean的循环依赖,报错!
  • 默认的singleton的bean的循环依赖,正常启动。
    原理就是spring对bean有三级存储,第一级的bean只是调用了构造函数,属性并未注入。这样a就可以先临时注入得到这个未初始化完成的b。然后继续形成完整的a。 b初始化的时候就能拿到完整的a了。b也就完整了。
    说白了,都是因为引用传递,才可行。
    最终初始化完成的bean放入第三级别存储,里边都是完整的,singleton的bean了,protype的bean是没有这三个级别存储的。每次都是重新实例化出来的。所以循环依赖会报错。

相关文章

网友评论

      本文标题:spring-循环依赖

      本文链接:https://www.haomeiwen.com/subject/srgqvhtx.html