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

Spring 系列目录

Spring读源码系列之AOP--05---aop常用工具类学习

Spring源码系列 —— 构造和初始化上下文

spring boot实战(第一篇)第一个案例

Spring源码学习笔记