zoukankan      html  css  js  c++  java
  • SpringBoot(一)原理剖析:SpringApplication启动原理

      通常搭建一个基于spring的web应用,我们需要做以下工作:

    1. pom文件中引入相关jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar ...
    2. 配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 ...
    3. 配置数据库连接、配置spring事务
    4. 配置视图解析器
    5. 开启注解、自动扫描功能
    6. 配置完成后部署tomcat、启动调试
    7. ......

      而用springboot后,一切都变得很简便快速。

    一、springboot的启动类入口

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

     

    二、@SpringBootApplication注解分析

      SpringBootApplication注解如下:

     1 @Target(ElementType.TYPE)  //注解的适用范围,其中Type用于描述类、接口或Enum声明 
     2 @Retention(RetentionPolicy.RUNTIME)  //注解的生命周期,保留到Class文件中
     3 @Documented  //表明这个注解应该被javadoc记录
     4 @Inherited   //子类可以继承该注解
     5 @SpringBootConfiguration  //继承了Configuration,表示当前是注解类
     6 @EnableAutoConfiguration  //开启SpringBoot的注解功能,借助@import的支持,收集和注册依赖包中的bean定义
     7 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
     8         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //自动扫描并加载符合条件的组件
     9 public @interface SpringBootApplication {
    10 
    11     /**
    12      * Exclude specific auto-configuration classes such that they will never be applied.
    13      * @return the classes to exclude
    14      */
    15     @AliasFor(annotation = EnableAutoConfiguration.class)
    16     Class<?>[] exclude() default {};
    17 
    18     /**
    19      * Exclude specific auto-configuration class names such that they will never be
    20      * applied.
    21      */
    22     @AliasFor(annotation = EnableAutoConfiguration.class)
    23     String[] excludeName() default {};
    24 
    25     /**
    26      * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
    27      * for a type-safe alternative to String-based package names.
    28      */
    29     @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    30     String[] scanBasePackages() default {};
    31 
    32     /**
    33      * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
    34      * scan for annotated components. The package of each class specified will be scanned.
    35      */
    36     @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    37     Class<?>[] scanBasePackageClasses() default {};
    38 
    39     /**
    40      * The {@link BeanNameGenerator} class to be used for naming detected components
    41      * within the Spring container.
    42      */
    43     @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    44     Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    45 
    46     /**
    47      * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
    48      * bean lifecycle behavior
    49      */
    50     @AliasFor(annotation = Configuration.class)
    51     boolean proxyBeanMethods() default true;
    52 
    53 }
    SpringBootApplication

      除了普通修饰注解类的原信息,还有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 3个注解。

      2.1.@SpringBootConfiguration

      SpringBootConfiguration注解如下:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    
        /**
         * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
         * bean lifecycle behavior
         */
        @AliasFor(annotation = Configuration.class)
        boolean proxyBeanMethods() default true;
    
    }
    View Code

      可以看到类上有@Configuration注解,说明它本身也是一个配置类。SpringBoot社区推荐使用基于JavaConfig的配置方式来定义Bean,所以这里的启动类标注了@Configuration之后,本身也可以认为是一个Spring Ioc容器的配置类。

      2.1.1 xml配置文件的形式注入bean

    <bean id="mockService" class="..MockServiceImpl">
    ...
    </bean>

      2.1.2 javaconfiguration的配置形式注入bean

      任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl();
        }
    }

       2.2.@ComponentScan

      @ComponentScan注解如下:

      1 @Retention(RetentionPolicy.RUNTIME)
      2 @Target(ElementType.TYPE)
      3 @Documented
      4 @Repeatable(ComponentScans.class)
      5 public @interface ComponentScan {
      6 
      7     /**
      8      * Alias for {@link #basePackages}.
      9      */
     10     @AliasFor("basePackages")
     11     String[] value() default {};
     12 
     13     /**
     14      * Base packages to scan for annotated components.
     15      */
     16     @AliasFor("value")
     17     String[] basePackages() default {};
     18 
     19     /**
     20      * Type-safe alternative to {@link #basePackages} for specifying the packages to scan for annotated components. 
     21      */
     22     Class<?>[] basePackageClasses() default {};
     23 
     24     /**
     25      * The {@link BeanNameGenerator} class to be used for naming detected components within the Spring container.
     26      */
     27     Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
     28 
     29     /**
     30      * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
     31      */
     32     Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
     33 
     34     /**
     35      * Indicates whether proxies should be generated for detected components, which may be necessary when using scopes in a proxy-style fashion.
     36      */
     37     ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
     38 
     39     /**
     40      * Controls the class files eligible for component detection.
     41      */
     42     String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
     43 
     44     /**
     45      * Indicates whether automatic detection of classes annotated with {@code @Component}
     46      * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
     47      */
     48     boolean useDefaultFilters() default true;
     49 
     50     /**
     51      * Specifies which types are eligible for component scanning.
     52      */
     53     Filter[] includeFilters() default {};
     54 
     55     /**
     56      * Specifies which types are not eligible for component scanning.
     57      * @see #resourcePattern
     58      */
     59     Filter[] excludeFilters() default {};
     60 
     61     /**
     62      * Specify whether scanned beans should be registered for lazy initialization.
     63      * @since 4.1
     64      */
     65     boolean lazyInit() default false;
     66 
     67 
     68     /**
     69      * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
     70      * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
     71      */
     72     @Retention(RetentionPolicy.RUNTIME)
     73     @Target({})
     74     @interface Filter {
     75 
     76         /**
     77          * The type of filter to use.
     78          * <p>Default is {@link FilterType#ANNOTATION}.
     79          * @see #classes
     80          * @see #pattern
     81          */
     82         FilterType type() default FilterType.ANNOTATION;
     83 
     84         /**
     85          * Alias for {@link #classes}.
     86          * @see #classes
     87          */
     88         @AliasFor("classes")
     89         Class<?>[] value() default {};
     90 
     91         /**
     92          * The class or classes to use as the filter.
     93          */
     94         @AliasFor("value")
     95         Class<?>[] classes() default {};
     96 
     97         /**
     98          * The pattern (or patterns) to use for the filter, as an alternative
     99          */
    100         String[] pattern() default {};
    101 
    102     }
    ComponentScan

      @ComponentScan注解对应原有XML配置中的元素。@ComponentScan的功能是自动扫描并加载符合条件的组件(如@controller、@Component等),最终将这些Bean的定义加载到Ioc容器中。

      我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。所以通常我们在定义SpringBoot启动类的时候,会把它放到root package下,这样就能扫描到所有需要定义的类。

      2.3.@EnableAutoConfiguration

      EnableAutoConfiguration的作用是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

      @EnableAutoConfiguration注解如下:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
        /**
         * Environment property that can be used to override when auto-configuration is
         * enabled.
         */
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        Class<?>[] exclude() default {};
    
        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        String[] excludeName() default {};
    
    }
    EnableAutoConfiguration

      @EnableAutoConfiguration注解对应原有XML配置中的元素。它之所以能自动根据条件来注册我们需要的Bean实例,主要是由其上的注解@Import导入的。

      2.3.1@AutoConfigurationPackage

      @AutoConfigurationPackage注解如下:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
        /**
         * Base packages that should be registered with {@link AutoConfigurationPackages}.
         */
        String[] basePackages() default {};
    
        /**
         * Type-safe alternative to {@link #basePackages} for specifying the packages to be
         * registered with {@link AutoConfigurationPackages}.
         */
        Class<?>[] basePackageClasses() default {};
    
    }
    AutoConfigurationPackage

       @AutoConfigurationPackage注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理。可以通过 AutoConfigurationPackages 工具类获取自动配置package列表。也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。

      核心方法是:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
            }
    }

      其中【new PackageImports(metadata).getPackageNames().toArray(new String[0])】就是启动类的包路径

      2.3.2@Import(AutoConfigurationImportSelector.class)

      在AutoConfigurationImportSelector中会调用如下方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                    getBeanClassLoader());
            Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
            return configurations;
        }

      SpringFactoriesLoader.loadFactoryNames 方法会加载外部配置文件:

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

      以spring-boot-autoconfigure-**.jar中spring.factories为例,如下图所示:

    3.springboot启动流程

     

       3.1.SpringApplication实例初始化并设置基础信息

    public class SpringApplication {
    
      public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
           return run(new Class<?>[] { primarySource }, args);
      }
    
      public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return new SpringApplication(primarySources).run(args);
      }
    
      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
            this.resourceLoader = resourceLoader;
            Assert.notNull(primarySources, "PrimarySources must not be null");
            this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
        }
    }
    SpringApplication
    • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
    • 推断并设置main方法的定义类

       3.2.执行run方法体逻辑

    public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch(); //计时类,计算SpringBoot应用启动时间
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //回调接口,用于支持SpringApplication启动错误的自定义报告
            configureHeadlessProperty();
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();    //3.2.1
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //3.2.2
                configureIgnoreBeanInfo(environment);
                Banner printedBanner = printBanner(environment); //3.2.3
                context = createApplicationContext(); //3.2.4
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);  //3.2.5
                prepareContext(context, environment, listeners, applicationArguments, printedBanner); //3.2.6
                refreshContext(context); //3.2.7
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context); 
                callRunners(context, applicationArguments); //3.2.8
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    
            try {
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }

      3.2.1遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener,调用starting()方法,通知SpringBoot开始执行;
      3.2.2准备并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的properties文件、propertySources等),然后通知Listeners环境准备好了;

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments) {
            // Create and configure the environment
            ConfigurableEnvironment environment = getOrCreateEnvironment();
            configureEnvironment(environment, applicationArguments.getSourceArgs());
            ConfigurationPropertySources.attach(environment);
            listeners.environmentPrepared(environment);
            bindToSpringApplication(environment);
            if (!this.isCustomEnvironment) {
                environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                        deduceEnvironmentClass());
            }
            ConfigurationPropertySources.attach(environment);
            return environment;
        }
      
    View Code

      3.2.3判断bannerMode,是OFF则不打印,是LOG则输出到日志文件,否则输出到System.out;

    private Banner printBanner(ConfigurableEnvironment environment) {
            if (this.bannerMode == Banner.Mode.OFF) {
                return null;
            }
            ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
                    : new DefaultResourceLoader(null);
            SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
            if (this.bannerMode == Mode.LOG) {
                return bannerPrinter.print(environment, this.mainApplicationClass, logger);
            }
            return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }
       
    View Code

      3.2.4根据SpringApplication构造方法生成的webApplicationType变量创建一个ApplicationContext,默认生成AnnotationConfigApplicationContext。

    protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                    }
                }
                catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                            "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
                }
            }
            return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
        }
     
    View Code

      3.2.5从spring.factories中读取出类型为 org.springframework.boot.SpringBootExceptionReporter 对应的类。然后创建类的实例

    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);

      3.2.6遍历调用所有SpringApplicationRunListener的contextPrepared()方法,同时将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext,然后遍历调用所有SpringApplicationRunListener的contextLoaded()方法

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
            context.setEnvironment(environment);
            postProcessApplicationContext(context);
            applyInitializers(context);
            listeners.contextPrepared(context);
            if (this.logStartupInfo) {
                logStartupInfo(context.getParent() == null);
                logStartupProfileInfo(context);
            }
            // Add boot specific singleton beans
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
            if (printedBanner != null) {
                beanFactory.registerSingleton("springBootBanner", printedBanner);
            }
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) beanFactory)
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
            if (this.lazyInitialization) {
                context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
            }
            // Load the sources
            Set<Object> sources = getAllSources();
            Assert.notEmpty(sources, "Sources must not be empty");
            load(context, sources.toArray(new Object[0]));
            listeners.contextLoaded(context);
        }
    View Code

      3.2.7注册ShutdownHook以便在JVM停止时优雅退出。调用ApplicationContext的refresh()方法,初始化DefaultListableBeanFactory工厂类,完成IoC容器可用的最后一道工序.

    private void refreshContext(ConfigurableApplicationContext context) {
            if (this.registerShutdownHook) {
                try {
                    context.registerShutdownHook();
                }
                catch (AccessControlException ex) {
                    // Not allowed in some environments.
                }
            }
            refresh((ApplicationContext) context);
        }
    View Code

      3.2.8 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
            List<Object> runners = new ArrayList<>();
            runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
            runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
            AnnotationAwareOrderComparator.sort(runners);
            for (Object runner : new LinkedHashSet<>(runners)) {
                if (runner instanceof ApplicationRunner) {
                    callRunner((ApplicationRunner) runner, args);
                }
                if (runner instanceof CommandLineRunner) {
                    callRunner((CommandLineRunner) runner, args);
                }
            }
        }
    View Code
  • 相关阅读:
    MVC WebApi的两种访问方法
    MVC CRUD 的两种方法
    MVC EF 导航属性
    MVC EF两种查询方法
    MVC WebApi
    POJ 1511 Invitation Cards ( 双向单源最短路 || 最小来回花费 )
    POJ 2502 Subway ( 最短路 && 最短路建图 )
    POJ 3660 Cow Contest ( 最短路松弛思想应用 && Floyd求传递闭包 )
    POJ 1502 MPI MaeIstrom ( 裸最短路 || atoi系统函数 )
    POJ 3259 Wormholes ( SPFA判断负环 && 思维 )
  • 原文地址:https://www.cnblogs.com/ryjJava/p/14425462.html
Copyright © 2011-2022 走看看