Spring源码学习系列第一篇
Posted 要千
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码学习系列第一篇相关的知识,希望对你有一定的参考价值。
相信对于从事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) //首先查询<context-param>中有没有配置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<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(servletContext); if (initializerClasses.isEmpty()) // no ApplicationContextInitializers have been declared -> nothing to do return; Class<?> contextClass = applicationContext.getClass(); ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>(); //如果有配置定制的类,则循环取,并加入到集合在中, for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> 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<ConfigurableApplicationContext> initializer : initializerInstances) initializer.initialize(applicationContext); 从上述源码可以看出配置的必须是ApplicationContextInitializer的实现类,配置参数是contextInitializerClasses
多个可以以","分割
例子: web.xml中配置 <context-param> <param-name>contextInitializerClasses</param-name> <param-value>freestyle.ContextInitializerImp</param-value> </context-param> /** * Created by 要 on 2017/2/21. */ public class ContextInitializerImp implements ApplicationContextInitializer<XmlWebApplicationContext> @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容器实现的核心,那就下次一起学习吧!
以上是关于Spring源码学习系列第一篇的主要内容,如果未能解决你的问题,请参考以下文章
Nacos系列第一篇-Nacos之Spring Discovery