Spring源码学习系列第一篇

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

相信对于从事java开发的工程师,没有人不晓得Spring,它替代了EJB,成为当今最流行的开发框架,特别是在互联网,特别是移动互联网当道的今天,模块化的微服务更是盛行,springBoot,spring cloud日渐成为新宠。

从事软件开发数年,一直使用Spring框架,但是一直没有机会学习其源码,趁着工作闲暇之余,了解学习Spring源码,学习这些伟大的产品的设计方案和实现原理,当然阅读源码是一项比较费力的事情,记得Spring技术内幕的作者在序论中谈到这点,他说用了半年时间去详细的学习源码。今天就开始源码分析的第一篇:

spring最为大家熟悉也是使用最多的就是他的IOC容器和AOP切面编程,对象间相互关系的维护交由spring来处理,大大减轻了工程师们的工作。对AOP使用最广泛的就是spring提供的声明式事务。

今天开篇就从spring的IOC容器开始说起,BeanFactory,spring提供的最基础的容器接口,开发过程中使用的ApplicationContext上下文就是从beanFactory扩展出来的,并实现了其他相关的接口,来提供ioc容器的高级功能,比如国际化,资源加载等

首先看一张关于spring提供的BeanFactory接口的类图,初略的了解下相关的类和接口:

20191017100276\_1.png

是不是有种眼花缭乱的感觉,不管你是不是,反正我看源码时,颇有种凌乱的感脚,其实,我们将这个类图整理分类成两条线,一条是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… 生成默认的idwac.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>> initializerClasses = determineContextInitializerClasses(servletContext); if (initializerClasses.isEmpty()) { // no ApplicationContextInitializers have been declared -> nothing to do return; } Class<?> contextClass = applicationContext.getClass(); ArrayList<ApplicationContextInitializer> initializerInstances = new ArrayList<ApplicationContextInitializer>(); //如果有配置定制的类,则循环取,并加入到集合在中, for (Class<ApplicationContextInitializer> 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>contextInitializerClasses</param-name> <param-value>freestyle.ContextInitializerImp</param-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容器实现的核心,那就下次一起学习吧!


来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring源码学习系列第一篇

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏