zoukankan      html  css  js  c++  java
  • 5、Spring Boot 2.x 启动原理解析

    1.5 Spring Boot 启动原理解析

    前言

    前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏。所以这次就跟你们一起一步步揭开SpringBoot的神秘面纱,让它不在神秘。

    1.5.1 SpringBootApplication背后的秘密

    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
    		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    ……
    }
    
    

    虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

    • @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
    • @EnableAutoConfiguration
    • @ComponentScan
    1.5.1.1 @Configuration注解

    这里的@Configuration对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。

    举几个简单例子回顾下,XML跟config配置方式的区别:

    1.5.1.1.1 表达形式层面
    • 基于XML配置的方式是这样
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
           default-lazy-init="true">
        <!--bean定义-->
    </beans>
    
    • JavaConfig的配置方式是这样
    @Configuration
    public class MockConfiguration{
        //bean定义
    }
    

    任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

    1.5.1.1.2 注册bean定义层面
    • ,基于XML的配置形式是这样:
    <bean id="mockService" class="..MockServiceImpl">
        ...
    </bean>
    
    • 而基于JavaConfig的配置形式是这样的:
    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl();
        }
    }
    

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

    1.5.1.1.3 表达依赖注入关系层面
    • 为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:
    <bean id="mockService" class="..MockServiceImpl">
        <propery name ="dependencyService" ref="dependencyService" />
    </bean>
    
    <bean id="dependencyService" class="DependencyServiceImpl"></bean>
    
    • 而基于JavaConfig的配置形式是这样的:
    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl(dependencyService());
        }
        
        @Bean
        public DependencyService dependencyService(){
            return new DependencyServiceImpl();
        }
    }
    

    如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。

    1.5.1.2 @ComponentScan 注解

    @ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

    我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

    注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

    1.5.1.3 @EnableAutoConfiguration 注解

    详见 1.4 Spring Boot 自动配置原理

    1.5.2 SpringApplication执行流程

    1.5.2.1 SpringApplication.run()源码分析
    public ConfigurableApplicationContext run(String... args) {
            // 时间监控
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		// java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
    		configureHeadlessProperty();
    		// 获取spring.factories中的监听器变量,args为指定的参数数组,默认为当前类SpringApplication
    		//第一步:获取并启动监听器
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    					args);
    			//第二步:构造容器环境
    			ConfigurableEnvironment environment = prepareEnvironment(listeners,
    					applicationArguments);
    			//  设置需要忽略的bean
    			configureIgnoreBeanInfo(environment);
    			//  打印banner
    			Banner printedBanner = printBanner(environment);
    			//第三步:创建容器
    			context = createApplicationContext();
    			//第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
    			exceptionReporters = getSpringFactoriesInstances(
    					SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			//第五步:准备容器
    			prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    			//第六步:刷新容器
    			refreshContext(context);
    		    //第七步:刷新容器后的扩展接口
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass)
    						.logStarted(getApplicationLog(), stopWatch);
    			}
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		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;
    	}
    

    SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

    • 第一步:获取并启动监听器
    • 第二步:构造容器环境
    • 第三步:创建容器
    • 第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
    • 第五步:准备容器
    • 第六步:刷新容器
    • 第七步:刷新容器后的扩展接口
  • 相关阅读:
    面试:div水平垂直居中方案--img自适应
    面试:call、apply、bind原理以及自己手写简易模式
    面试之:判断js类型的方式总结
    git的项目完整操作
    vue3.x版本新建项目相关知识和注意事项
    面试常问平时项目中【Date】的常用操作方法总结
    面试常问平时项目中【Math】的常用操作方法总结
    面试常问平时项目中数组【Array】的常用操作方法总结
    面试常问平时用的对象【Object】的创建方式和常用的对象方法总结
    优化无限列表性能vue-virtual-scroll-list【测试90w条数据】
  • 原文地址:https://www.cnblogs.com/Grand-Jon/p/9986263.html
Copyright © 2011-2022 走看看