spring源码之初始化
前面我们探讨了struct的源码及其工作原理。本篇开始,我们将来探讨spring的源码及其工作原理。struct2的核心对象是dispacher,在struct2初始化的时候,初始化一个dispacher对象,当有request请求来的时候,调用dispacher的方法来处理和执行request请求。而spring的核心是ioc容器,如果给我们设计,那spring的初始化是不是都要初始化一个容器对象?然后在运行时就从容器中取出对象?就好比于struct2的container容器,在初始化的的把配置文件中配置的对象放入到容器中,然后在处理action请求的时候,通过@inject依赖注入。同样,spring在初始化的时候也会把配置中的对象放入到容器中(spring把容器称为beanFactory或者是applicationContext,下面会介绍这两个的区别),下面就让我们看看spring是不是这样实现的。
那么spring是从哪里开始的呢?struct2用到了过滤器filter,而spring用到了监听器listener.
1.spring的入口
在web.xml中配置一个监听器listener-ContetLoaderListener.
<!--【代码清单】:在web.xml中配置监听器listener--> <!-- 配置spring初始化监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在tomcat启动的时候,会自动运行配置的监听器ContextLoaderListener的contextInitialized()方法(这是因为ContextLoaderListener继承了ServletContextListener的接口,具体原理参考tomcat的运行机制)。那么,在这个方法中,spring到底干了些什么呢?我们来看看这个方法
2.spring初始化主要工作
//【代码清单】:contextInitialized()方法
publicvoid contextInitialized(ServletContextEvent event) {
//创建一个ContextLoader
this.contextLoader =createContextLoader();
//初始化ioc容器
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
在这个方法中主要是实例化了一个contextLoader,然后通过这个contextLoader初始化一个webApplicationContext,这个webApplicationContext俗称就是spring的ioc容器。这里也论证了我们上面的猜想:spring的初始化,就是初始化一个IOC容器,当然,这个过程中,spring会把配置文件中的对象放入到容器,这里也会涉及到解析配置文件。
2.1.创建ContextLoader
①为什么需要ContextLoader?
spring的ioc容器有很多,不同的应用层面有不同的ioc服务,如接口的有BeanFactoy和ApplicationContext,继承这两个接口类又有很多,如继承applicationContext的有ConfigurableApplicationContext和WebApplicationContext,而实现这些接口的类也有很多,如实现webApplicationContext的有xmlWebApplictationContext,实现ConfigurableApplicationContext的有FileSystemApplicationContext。这些都是针对不用应用层面提供的ioc服务。FileSystemApplicationContext是在文件目录下解析xml 文件,而xmlWebApplictationContext是在xml配置中获取xml路径并解析。
针对这么多的ioc容器,tomcat启动的时候,到底初始化哪个ioc容器?这就需要一个类来对这些IOC容器进行管理,并决定在tomcat启动的时候,决定加载哪个ioc容器,这个类就是ContextLoader。
实际web项目开发是在web.xml中配置applicationContext.xml文件的路径。因此本文以webapplicationContext作主要解析。
<!--代码清单:web.xml中配置 applicationContext.xml路径--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
classpath:表示从类路劲加载配置文件,详细讲解,请看spring的策略管理。
②什么是ContextLoader?
//【代码清单】:ContextLoader
publicclass ContextLoader {
//初始化IOC容器
WebApplicationContext initWebApplicationContext();
//获得当前ioc容器
WebApplicationContext getCurrentWebApplicationContext();
//关闭ioc容器
void closeWebApplicationContext
}
ContextLoader主要提供了初始化ioc容器,获取当前ioc容器,和关闭当前ioc容器的几个方法。通俗地说,contextLoader就是一个管理ioc容器的类。我们可以创建自己的ContextLoader来加载自己想要的ioc容器。
③怎么创建ContextLoader?
//【代码清单】:createContextLoader()
protected ContextLoadercreateContextLoader() {
//实例化一个ContextLoader对象
return new ContextLoader();
}
在createContextLoader()方法中主要是new了一个ContextLoader实例。
2.2.初始化IOC容器
初始化IOC容器主要是调用了ContextLoader. initWebApplicationContext()方法。
//【代码清单】:initWebApplicationContext()
public WebApplicationContextinitWebApplicationContext
(ServletContext servletContext)
throwsIllegalStateException, BeansException {
// 因为webApplicationContext最后要保存到request的*.ROOT常量中,所以这里先判断这个这个变量是否为null,为null则抛出异常。此处代码省略。
try {
// 1.获取根上下文,
ApplicationContextparent = loadParentContext(servletContext);
//2. 创建webApplicationContext,完成初始化后添加到根上下文中
this.context =createWebApplicationContext(servletContext, parent);
//3保存到servletContext.ROOT中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 4.添加到当前线程中
currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
// 此处异常信息,代码省略。
returnthis.context;
}
(1)获取根上下文
//【代码清单】:loadParentContext()
LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
protected ApplicationContext loadParentContext(ServletContextservletContext)throws BeansException {
// 1.从ServletContext获取上面那2个常量值
servletContext.getInitParameter();
// 2.如果parentContextKey的值为空,则返回一个空的applicationContext,否则
if (parentContextKey!= null) {
//3.先判断locatorFactorySelector是否为null,如果是null,则用默认值classpath*:beanRefContext.xml,如果不为null,则根据locatorFactorySelector实例化一个BeanFactoryLocator
BeanFactoryLocatorlocator =
ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
// 4.然后用BeanFactoryLocator. useBeanFactory()方法实例化一个parentContextRef .ApplicationContext是parentContextRef的一个field.
parentContextRef.applicationContext=
new ClassPathXmlApplicationContext(parentContextKey)
this.parentContextRef = locator.useBeanFactory(parentContextKey);
//5. return parentContextRef.applicationContext
parentContext = (ApplicationContext)
this.parentContextRef.getFactory();
}
return parentContext;
}
(2)创建webApplicationContext
//【代码清单】:createWebApplicationContext()
protectedWebApplicationContext createWebApplicationContext(
ServletContextservletContext, ApplicationContext parent) throws BeansException {
//1.判断载入的applicationContext类型,如果没有则默认使用继承WebApplicationContext接口的类。
ClasscontextClass = determineContextClass(servletContext);
//判断contextClass是否是ConfigurableWebApplicationContext.class类型,不是则抛出异常。此处代码省略。ConfigurableWebApplicationContext是一个继承了webApplicationContext和ConfiguteApplicationContext接口的接口。、
//2.根据contextClass实例化一个ConfigurableWebApplicationContext
ConfigurableWebApplicationContextwac =
(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
//3.添加根上下文
wac.setParent(parent);
//4.添加servletContext
wac.setServletContext(servletContext);
//5.获取web.xml中配置的contextConfigLocation参数
wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
//6.定制Context,这是一个什么也不做的方法,用于拓展,可以忽略
customizeContext(servletContext,wac);
//7.解析applicationContext.xml,完成初始化
wac.refresh();
return wac;
}
①判断载入的根applicationContext类型
//【代码清单】:determineContextClass()
protected Class determineContextClass(ServletContextservletContext) throws ApplicationContextException {
//1.获取我们在web.xml中配置的contextClass参数,这个参数可以可以决定我们需要载入的applicationContext类型
StringcontextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName!= null) {
try {
return ClassUtils.forName(contextClassName);
}
}
else {
//2.如果没配置有,则使用继承webApplicationContext接口的Class类
contextClassName= defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName,ContextLoader.class.getClassLoader());
}
}
}
②实例化一个实现ConfigurableWebApplicationContext的类
//【代码清单】:instantiateClass(contextClassName)
publicstatic Object instantiateClass(Class clazz) throws BeanInstantiationException {
try {
//重载instantiateClass方法
return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null);
}
}
在instantiateClass(contextClassName)方法中重载了instantiateClass方法
//【代码清单】:instantiateClass(constructor,args)
publicstatic Object instantiateClass(Constructor ctor, Object[] args) throws BeanInstantiationException {
try {
//setAccessible(true)
ReflectionUtils.makeAccessible(ctor);
//实例化
return ctor.newInstance(args);
}
}
③添加根上下文
//【代码清单】:setParent()
publicvoid setParent(ApplicationContextparent) {
this.parent = parent;
}
④设置servletContext
//【代码清单】:setServletContext()
publicvoid setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
⑤获取配置文件信息
CONFIG_LOCATION_PARAM = "contextConfigLocation"; servletContext.getInitParameter(CONFIG_LOCATION_PARAM)
获的web.xml配置contextConfigLocation的参数
<!--【代码清单】:web.xml的contextConfigLocation配置参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
处理参数
//【代码清单】:处理参数
publicvoid setConfigLocation(String location) {
//String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
//“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”
setConfigLocations(StringUtils.tokenizeToStringArray(location,CONFIG_LOCATION_DELIMITERS));
}
tokenizeToStringArray()方法是处理“classpath:applicationContext*.xml”这个字符串成一个数组,然后调用setConfigLocations(String[]locations)方法。
//【代码清单】:tokenizeToStringArray()
publicstatic String[]tokenizeToStringArray(
Stringstr, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
StringTokenizerst = new StringTokenizer(str, delimiters);
Listtokens = new ArrayList();
while(st.hasMoreTokens()) {
//分割
Stringtoken = st.nextToken();
//前后去掉空格。此处代码省略
if (!ignoreEmptyTokens || token.length() > 0) {
//添加到数组
tokens.add(token);
}
}
//转字符数组
returntoStringArray(tokens);
}
把配置文件信息,保存到容器中
//【代码清单】:setConfigLocations(String[] locations)
//configLocations只是一个字符数组
private String[] configLocations;
publicvoid setConfigLocations(String[] locations) {
if (locations != null) {
this.configLocations = newString[locations.length];
for (int i = 0; i <locations.length; i++) {
//循环遍历,赋给configLocations
this.configLocations[i] =resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
⑥解析applicationContext.xml,完成初始化
AbstractRefreshableWebApplicationContext是实现了
ConfigurableWebApplicationContext接口的类,但是
AbstractRefreshableWebApplicationContext中 并没有refresh()方法。当我们一层一层找它的父类的时候,在父类AbstractApplicationContext中找到了refresh()这个方法。
//【代码清单】:AbstractApplicationContext.refresh()
publicvoid refresh() throws BeansException,IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1.容器启动的预先准备,记录容器启动的时间和标记.
prepareRefresh();
//2.创建BeanFactory,如果已有就销毁,没有就创建.此类实现了对BeanDefinition的装载
ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();
//3.配置BeanFactory标准上下文特性,如类装载器、postProcesser等
prepareBeanFactory(beanFactory);
try {
// 4.在bean被装载后,提供一个修改BeanFactory的入口
postProcessBeanFactory(beanFactory);
// 5.调用postProceessBeanFactory
invokeBeanFactoryPostProcessors(beanFactory);
//6.注册用于拦截bean创建过程中的BeanPostProcessors
registerBeanPostProcessors(beanFactory);
// 7.Initialize message source for this context.
initMessageSource();
// 8.Initialize event multicaster for this context.
initApplicationEventMulticaster();
//9.Initialize other special beans in specific contextsubclasses.
onRefresh();
//10. 注册监听器
registerListeners();
//11.完成容器的初始化,里面的preInstantiateSingletons完成单例对象的创建
finishBeanFactoryInitialization(beanFactory);
// 12.Last step: publish corresponding event.
finishRefresh();
}
}
}
3.总结
综上所述,spring的初始化其实就是创建一个configurateWebApplicationContext对象,然后把这个对象放到servletContext和线程中。而创建这个webApplcationContext的过程,其实就是获取配置文件并解析这个配置文件的过程。上面讨论了spring已经从web.xml中获取了applicationContext.xml配置的文件信息,下篇博文,我们将探讨,refresh()这个方法是如何解析配置文件和完成初始化的。
转载于:https://blog.51cto.com/yoyanda/1717243