美文网首页
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