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是没有这三个级别存储的。每次都是重新实例化出来的。所以循环依赖会报错。
网友评论