spring boot源码学习笔记(一)

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

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

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

注明:以下内容基于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 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方法,各自负责不同的模块初始化逻辑。

编号 作用
编号 作用
1 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer 检查常见的配置错误
2 org.springframework.boot.context.ContextIdApplicationContextInitializer 给ApplicationContext设置一个ID
3 org.springframework.boot.context.config.DelegatingApplicationContextInitializer 委托context.initializer.classes环境变量指定的初始化器(通过类名)进行初始化工作
4 org.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也是上述监听器共同的实现接口。不同的监听器职责不同,详细的职责还需要再进一步深入挖掘。

编号 作用

设置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的实例化。


来源:[]()

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

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏