spring boot源码学习笔记(一)

注明:以下内容基于spring-boot-1.4.2,starter为spring-boot-starter-web。 

<parent>        
 <groupId>org.springframework.boot</groupId>       
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>1.4.2.RELEASE</version>    
</parent>  
  
<dependencies>        
 <dependency>         
  <groupId>org.springframework.boot</groupId>            
  <artifactId>spring-boot-starter-web</artifactId>        
 </dependency>     
<dependencies>

第一步,从启动过程开始。

@SpringBootApplication
public class Application{	
 public static void main(String[] args){		
  SpringApplication.run(Application.class, args);	
 }
}

上面是官方提供的一个简单的启动类,就从这几行简单的代码开始学习框架吧。
和普通java程序一样,main作为入口函数。在启动类中只有一行,直接调用org.springframework.boot.SpringApplication类中的静态run方法,传入启动类的Class对象与启动时所带的参数【可选】;通过该静态方法,实例化SpringApplication,同时调用实例run方法。

* from org.springframework.boot.SpringApplication
/**
 * 使用默认设置从指定的source启动一个Spring应用.
 * @param source 加载来源
 * @param args 启动参数,通常来源与java的main方法参数
 * @return 返回ApplicationContext对象
 */
public static ConfigurableApplicationContext run(Object source, String... args) {
        return run(new Object[] { source }, args);
}

/**
 * 使用默认设置从指定的1到多个source启动一个Spring应用.
 * @param source 加载来源
 * @param args 启动参数,通常来源与java的main方法参数
 * @return 返回ApplicationContext对象
 */
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
}

在SpringApplication的构造函数中,调用私有的initialize方法进行变量的初始化。

* from org.springframework.boot.SpringApplication
    /**
     * 创建一个新的实例。应用上下文会从指定来源加载bean。实例在调用run方法前被定制化。
     * @param sources bean来源
     */
    public SpringApplication(Object... sources) {
        initialize(sources);
    }

private void initialize(Object[] sources) {
        // sources初始化,类型为LinkedHashSet,此处填充了启动类的Class对象
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        //webEnvironment,类型为boolean
        this.webEnvironment = deduceWebEnvironment();
        //设置初始化器
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //设置监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //mainApplicationClass,类型为Class
        this.mainApplicationClass = deduceMainApplicationClass();
 }

初始化内容包括4项,分别为sources,webEnvironment,初始化器,监听器和mainApplicationClass,除了sources为直接填充外,其它几项都是通过调用方法完成初始化。接下来我们逐一来看。


webEnvironment初始化
调用私有方法deduceWebEnvironment,在该方法中会检查项目中是否引入了web工程所必要的类并且可以被加载,包括javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext,在ClassUtils.isPresent方法中会尝试找到并且进行类加载,任何一个类不满足要求,都会返回false。因为webEnvironment的含义应该是是否满足web工程运行条件。

* from org.springframework.boot.SpringApplication

    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };



初始化器Initializers
首先通过调用私有的getSpringFactoriesInstances方法获取ApplicationContextInitializer或者其子类的实例集合。

* from org.springframework.boot.SpringApplication
   private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
   }

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        //获取当前线程所运行的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 使用类名作为key,防止重复
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }



获取初始化器的方法步骤比较多,有多层嵌套,我们一层层看。第一步是获取当前线程所运行的类加载器,这个没有疑问。第二步是加载配置文件,获取到初始化器的类名。

* from org.springframework.core.io.support.SpringFactoriesLoader
    /**
     * 使用给定的类加载器加载FACTORIES_RESOURCE_LOCATION中给出的factoryClass全部合格实现类的名称.
     * @param factoryClass 父级接口或者抽象类
     * @param classLoader 加载资源所使用的类加载器,如果为空的时候使用默认加载器
     * @see #loadFactories
     * @throws IllegalArgumentException 加载类名失败时抛出
     */
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }


    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    我们再看看spring.factories中包含的内容,key是org.springframework.context.ApplicationContextInitializer。      

# Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

此时Set<String> names中包含了上述四个类名。紧接这就是实例化。

* from org.springframework.boot.SpringApplication

    private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
        List<T> instances = new ArrayList<T>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass
                        .getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

在私有方法createSpringFactoriesInstances中,使用反射机制对获取的4个类分别进行了实例化,放在ArrayList中并返回对象集合。在这里,这四个初始化器都实现了ApplicationContextInitializer接口,实现了仅有的initialize方法,各自负责不同的模块初始化逻辑。


编号作用
1org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer检查常见的配置错误
2org.springframework.boot.context.ContextIdApplicationContextInitializer给ApplicationContext设置一个ID
3org.springframework.boot.context.config.DelegatingApplicationContextInitializer委托context.initializer.classes环境变量指定的初始化器(通过类名)进行初始化工作
4org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer将内嵌的Web服务器使用的端口给设置到ApplicationContext中

到这里,涉及到的初始化器的实例全部准备就绪,但是初始化工作还没有开展,每个初始化器的具体工作会在执行初始化的时候再详细介绍。在代码中有两个工具类用屡次被使用:
        – 一个是org.springframework.beans.BeanUtils,这个类中包含了一些java bean的静态方法,主要使用反射机制来初始化类,检查属性,拷贝属性等。
        – 还有一个是org.springframework.util.ClassUtils,这个类包含各种Class对象操作,包括获取报名,方法名等等。
     以上工具类主要在spring内部使用,但是很多静态方法都是公共的,在自己的程序中如果有需要应该也可以使用吧。

 在获取了初始化器实例以后,还需要对实例进行排序。

* from org.springframework.core.annotation.AnnotationAwareOrderComparator

public static void sort(List<?> list) {
        if (list.size() > 1) {
            Collections.sort(list, INSTANCE);
        }
    }

使用的比较器就是AnnotationAwareOrderComparator,在上述四个初始化器中,只有DelegatingApplicationContextInitializer实现了Ordered接口,并设置order值为0,其它初始化器没有设置,使用默认的最低优先级,从而区分出在集合中与后续执行的先后顺序,并全部填充到SpringApplication的initializers中去。


设置监听器
获取监听器实例的过程和获取初始化器的过程是相同的,不同的是监听器的配置key在spring.factories中使用的是org.springframework.context.ApplicationListener。

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

同样,这个key也是上述监听器共同的实现接口。不同的监听器职责不同,详细的职责还需要再进一步深入挖掘。

编号作用
1org.springframework.boot.ClearCachesApplicationListener一旦上下文加载完就清理缓存
2org.springframework.boot.builder.ParentContextCloserApplicationListener如果父级context关闭以后关闭子级上下文。该监听器设置了order,处于较低优先级,比默认最低优先级要高一些。
3org.springframework.boot.context.FileEncodingApplicationListener检查系统文件的编码是否和环境期望的文件编码一致,如果不一致,中断启动程序。显示指定优先级为最低。
4org.springframework.boot.context.config.AnsiOutputApplicationListener根据配置文件设置ANSI输出的开关等信息,优先级比ConfigFileApplicationListener低一级。
5org.springframework.boot.context.config.ConfigFileApplicationListener加载环境基本配置信息,默认为 ‘application.properties’ 和/或 ‘application.yml’,优先级比最高优先级稍低一些。
6org.springframework.boot.context.config.DelegatingApplicationListener委托context.listener.classes配置的其它监听器来执行任务,设置order为0,优先级处于中间位置。
7org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener如果相关参数存在,则使用springboot相关的版本进行替代
8org.springframework.boot.logging.ClasspathLoggingApplicationListener记录线程上下文加载类TCCL的classpath到DEBUG级别的,设置order比LoggingApplicationListener的优先级稍低一点。
9org.springframework.boot.logging.LoggingApplicationListener根据配置初始化日志系统,优先级比最高优先级稍低一些。低于ConfigFileApplicationListener。



设置mainApplicationClass
通过RuntimeException的异常实例的错误栈信息来获取main函数所在的类,并返回类的Class对象。比直接使用传进来的sources更保险。

* from org.springframework.boot.SpringApplication
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

自此,才完成SpringApplication的实例化。

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/xac07/article/details/79488049
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞