相信对于从事java开发的工程师,没有人不晓得Spring,它替代了EJB,成为当今最流行的开发框架,特别是在互联网,特别是移动互联网当道的今天,模块化的微服务更是盛行,springBoot,spring cloud日渐成为新宠。
从事软件开发数年,一直使用Spring框架,但是一直没有机会学习其源码,趁着工作闲暇之余,了解学习Spring源码,学习这些伟大的产品的设计方案和实现原理,当然阅读源码是一项比较费力的事情,记得Spring技术内幕的作者在序论中谈到这点,他说用了半年时间去详细的学习源码。今天就开始源码分析的第一篇:
spring最为大家熟悉也是使用最多的就是他的IOC容器和AOP切面编程,对象间相互关系的维护交由spring来处理,大大减轻了工程师们的工作。对AOP使用最广泛的就是spring提供的声明式事务。
今天开篇就从spring的IOC容器开始说起,BeanFactory,spring提供的最基础的容器接口,开发过程中使用的ApplicationContext上下文就是从beanFactory扩展出来的,并实现了其他相关的接口,来提供ioc容器的高级功能,比如国际化,资源加载等
首先看一张关于spring提供的BeanFactory接口的类图,初略的了解下相关的类和接口:
是不是有种眼花缭乱的感觉,不管你是不是,反正我看源码时,颇有种凌乱的感脚,其实,我们将这个类图整理分类成两条线,一条是BeanFactory,一条是ApplicationContext,BeanFactory线是不是已将整个类图的右上瓜分掉了,Application接口左边的几个接口为它提供了高级的功能,往下就是它的子类或子接口了,至于每个类或者接口都是干什么用的,暂时先不必理会,在分析源码的时候,会介绍的。
那么既然学习源码,总要有个入口把,从那开始学习那,就从ContextLoaderListener类开始,因为他是启动spring容器的关键,我们一般使用spring,都会在web.xml中去配置它。
首先看看此类的声明
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
因为实现了ServletContextListner,所以当web容器启动时候,会回掉监听器的contextInitialized(ServletContextEvent event)
查看ContextLoaderListener中此方法的实现,发现它调用了 initWebApplicationContext(event.getServletContext());
此方法是在基类ContextLoader中实现的,相关部分源码:
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
//创建web上下文,实现在此方法中
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {//根据配置获取创建的上下文的双亲上下文,从没用使用过,非重点,不深入
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置刷新根容器,这是spring能够启动的关键,下文分析
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将创建好的上下文保存在ServletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
关键看如何创建的根上下文:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class> contextClass = determineContextClass(sc); //决定用哪种容器
//选择好容器类型后判断是否是 ConfigurableWebApplicationContext同类或子类,不是则抛异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); }
//实例化根容器交由工具类BeanUtils实现, return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
determineContextClass方法是决定使用哪种容器类,看看是如何实现的:
protected Class> determineContextClass(ServletContext servletContext) {
//首先查询中有没有配置contextClass参数,如果 配 置,则用配置的,若没有配置,则从默认策略中取
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } }
else {
//默认策略由spring提供的ContextLoader.properties配置,默认只配置一个XmlWebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {//加载类文件
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } }
}
开始刷新容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value
// -> assign a more useful id based on available information
//初始化参数设置了contextId的话,则将容器的id设置为配置的
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... 生成默认的id wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); }
}
//保存servlet上下文保存到root容器中
wac.setServletContext(sc);
//获取配置的spring配置文件路径参数contextConfigLocation
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//定制ContextLoader创建的root容器,一般发生在refresh前,配置contextConfigLocation后
customizeContext(sc, wac);
//开始刷新容器,springIOC容器最重要的开始
wac.refresh(); }
customizeContext(sc,wac)方法是在容器创建完毕,允许我们对根容器作一些更改,一般项目上也不常用,这里还是可以看一下的,因为它
还是有点作用的
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
//对容器的定制功能都封装在determinContextInitiallizer方法中,下文分析如何定制
List>> initializerClasses = determineContextInitializerClasses(servletContext); if (initializerClasses.isEmpty()) { // no ApplicationContextInitializers have been declared -> nothing to do return; } Class> contextClass = applicationContext.getClass(); ArrayList> initializerInstances = new ArrayList>(); //如果有配置定制的类,则循环取,并加入到集合在中, for (Class> initializerClass : initializerClasses) { Class> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null) { Assert.isAssignable(initializerContextClass, contextClass, String.format( "Could not add context initializer [%s] as its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(), contextClass.getName())); } initializerInstances.add(BeanUtils.instantiateClass(initializerClass)); } ConfigurableEnvironment env = applicationContext.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(servletContext, null); } AnnotationAwareOrderComparator.sort(initializerInstances);
//调用ApplicationContextInitializer中德initialize方法,我们的定制功能都在这方法实现 for (ApplicationContextInitializer initializer : initializerInstances) { initializer.initialize(applicationContext); } }
从上述源码可以看出配置的必须是ApplicationContextInitializer的实现类,配置参数是contextInitializerClasses
多个可以以","分割
例子:
web.xml中配置
<context-param> <param-name>contextInitializerClassesparam-name> <param-value>freestyle.ContextInitializerImpparam-value>
context-param>
/** * Created by 要 on 2017/2/21. */ public class ContextInitializerImp implements ApplicationContextInitializer { @Override
public void initialize(XmlWebApplicationContext applicationContext) {
//可以更新根容器的全局属性
applicationContext.setAllowBeanDefinitionOverriding(false);
// initPropertySources在定制容器时执行过,是没有必要执行的
StandardServletEnvironment environment =
(StandardServletEnvironment) applicationContext.getEnvironment();
environment.initPropertySources(applicationContext.getServletContext(),
applicationContext.getServletConfig());
//设置活动的profile,这个一般用在配置多环境(开发,测试),其他没有配置为活动的则spring不会去加载
environment.setActiveProfiles("dev");
} }
根容器创建完毕,其实没有介绍太多东西,这篇中介绍的部分,一般开发中也很少用到,如定制根容器
wac.refresh()才是springIOC容器实现的核心,那就下次一起学习吧!