SpringBoot2概览-原理篇

1.自动配置


bean加载方式
  • XML方式声明bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">
    <!--声明自定义bean-->
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" scope="singleton"/>
    <!--声明第三方开发bean-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
  • XML+注解方式声明bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">
    <!--指定加载bean的位置-->
	<context:component-scan base-package="com.itheima"/>
</beans>
// 使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean
@Service
public class BookServiceImpl implements BookService {
}

// 使用@Bean定义第三方bean,并将所在类定义为配置类或Bean(因为不可能在源代码中加上@Service等注解完成对于对象的注入)
// 此时在容器会增加名为dbConfig和dataSource的bean
@Component
public class DbConfig {
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}
  • 注解方式声明配置类:
@Configuration  // 包含了@Component
@ComponentScan("com.itheima")  // 替代了xml中指定加载bean位置的配置
public class SpringConfig {
    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}
  • 使用@Import注解导入要注入的bean对应的字节码:此形式可有效的降低源代码与Spring技术的耦合度,在Spring技术底层及诸多框架的整合中大量使用
// 一般用于导入配置类,如@Import(DbConfig.class),且该bean的名称使用的是该类的全路径类名(其他方式名称是类名且首字母小写)
@Import(Dog.class)  
public class SpringConfig {...}

// 被导入的bean无需使用注解声明为bean也能被注入到容器中
// 没有@Component等注解,就不存在Spring框架的痕迹,降低耦合
public class Dog {...}
  • 使用上下文对象在容器初始化完毕后注入bean
public class AppImport {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
            // 此时SpringConfig类也会被加载到容器中成为一个bean,名称为springConfig
            new AnnotationConfigApplicationContext(SpringConfig.class);  
        ctx.register(Cat.class);  // Cat类无需添加@Component等注解
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
        	System.out.println(name);  // 输出cat
        }
    }
}
  • 导入实现ImportSelector接口的类:实现对导入源的编程式处理
// 配置类
@Import(MyImportSelector.class)  
public class SpringConfig {...}

public class MyImportSelector implements ImportSelector {
    // metadata即SpringConfig得元数据,因为SpringConfig使用@Import导入了本类
    public String[] selectImports(AnnotationMetadata metadata) {
        // 如果SpringConfig存在@Import注解,则返回true(实现对加载何种bean得判定)
    	boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Import");
        if(flag){
        	return new String[]{"com.itheima.domain.Dog"};
        }
        return new String[]{"com.itheima.domain.Cat"};
    }
}
  • 导入实现ImportBeanDefinitionRegistrar接口的类:通过BeanDefinition的注册器注册实名bean,实现对容器中bean的裁定(如对现有bean的覆盖,实现在不修改源代码的情况下更换实现)
// 配置类
@Import({BookServiceImpl1.class, MyRegistry.class})  // 和书写顺序无关,MyRegistry都会对其进行覆盖 
public class SpringConfig {...}

@Service("bookService")
public class BookServiceImpl1 implements BookService{
    ...
}

public class MyRegistry implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(
        AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 创建BeanDefinition对象用于后续的registerBeanDefinition
        BeanDefinition beanDefinition = BeanDefinitionBuilder
        .rootBeanDefinition(BookServiceImpl2.class)   // 使用BookServiceImpl2类覆盖BookServiceImpl1类
        .getBeanDefinition();
        
        registry.registerBeanDefinition("bookService", beanDefinition);
    }
}

// 测试
public class AppImport {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);  
        // 使用@Import(BookServiceImpl1.class),则这里加载是BookServiceImpl1类的bean
        // 使用@Import({BookServiceImpl1.class, MyRegistry.class}),则这里加载是BookServiceImpl2类的bean
        BookService bookservice = ctx.getBean("bookService", BookService.class);
    }
}
  • 导入实现了BeanDefinitionRegistryPostProcessor接口的类:通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定
// 配置类
// 假设MyRegistry1定义的bean为BookServiceImpl1.class,MyRegistry2定义的bean为BookServiceImpl2.class
// @Import({MyRegistry1.class, MyRegistry2.class})  // 这里和书写顺序有关,MyRegistry2会对前面的进行覆盖

// MyPostProcessor决定定义的是哪个bean,和它书写的顺序无关
@Import({MyRegistry1.class, MyRegistry2.class, MyPostProcessor.class})  
public class SpringConfig {...}

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = BeanDefinitionBuilder
        .rootBeanDefinition(BookServiceImpl3.class)
        .getBeanDefinition();
        
        registry.registerBeanDefinition("bookService", beanDefinition);
    }
}

// 测试
public class AppImport {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);  
        // 加载是BookServiceImpl3类的bean,即MyPostProcessor类中定义的
        BookService bookservice = ctx.getBean("bookService", BookService.class);
    }
}

tips:

  • 某些情况下返回的对象和new出来的对象不一样(目的是为了在返回对象前做些前置初始化的工作):
// 初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作
public class BookFactoryBean implements FactoryBean<Book> {
    public Book getObject() throws Exception {
        Book book = new Book();
        // 可以进行book对象相关的初始化工作
        ...
        return book;
    }
    public Class<?> getObjectType() {
    	return Book.class;
    }
}

public class SpringConfig {
    @Bean
    public BookFactoryBean book(){
        // new的是BookFactoryBean类,但实际返回Book类的对象,即注入的是ID=book的bean
    	return new BookFactoryBean();
    }
}
  • 可以使用Java代码同时加载配置类并加载配置文件,一般用于系统迁移:
@Configuration
@ComponentScan("com.itheima")

@ImportResource("applicationContext-config.xml")
public class SpringConfig {}
  • 使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的(即配置类会使用CGLIB代理,在同一个配置文件中调用其它被@Bean注解标注的方法获取对象时会直接从IOC容器中获取):
@Configuration(proxyBeanMethods = false)  // 此时config对象是原始对象
// @Configuration(proxyBeanMethods = true)  // 默认为true,此时config对象是代理对象
public class SpringConfig {
    @Bean
    public Book book(){
    	System.out.println("book init ...");
    	return new Book();
    }
}

// 测试
public class AppObject {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        SpringConfig config = ctx.getBean("SpringConfig", SpringConfig.class);
        // 如果proxyBeanMethods = true,则下面两个语句得到的是同一个对象(即从容器中直接获取创建好的对象)
        // 如果proxyBeanMethods = false,则下面两个语句得到的不是同一个对象(即每次重新创建)
        config.book();
        config.book();
    }
}
  • 下面代码中DbConfig类和DruidDataSource类都会被加载到容器中,名称分别为dbConfigdataSource
@Component
public class DbConfig {
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

bean的加载控制

bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标,具体有以下方式:

  • 根据任意条件确认是否加载beanbean加载方式中的方式6-9皆可设定条件
// 对应bean加载方式中的方式6
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        try {
        	Class<?> clazz = Class.forName("com.itheima.bean.Mouse");
            if(clazz != null) {
                return new String[]{"com.itheima.bean.Cat"};
            }
        } catch (ClassNotFoundException e) {
        	return new String[0];
        }
        return null;
    }
}
  • 使用@Conditional注解的派生注解设置各种组合条件控制bean的加载

    • 匹配指定类:
    public class SpringConfig {
        @Bean
        @ConditionalOnClass(Mouse.class)  // 如果环境中存在Mouse类,则创建Cat类的bean
        public Cat tom(){
        	return new Cat();
        }
    }
    
    • 未匹配指定类:
    public class SpringConfig {
        @Bean
        @ConditionalOnMissingClass("com.itheima.bean.Wolf")  // 如果环境中不存在Mouse类,则创建Cat类的bean
        public Cat tom(){
        	return new Cat();
    	}
    }
    
    • 匹配指定类型的bean
    public class SpringConfig {
        @Bean
        @ConditionalOnBean(Mouse.class)  // 如果容器中存在Mouse类的bean,则创建Cat类的bean
        public Cat tom(){
        	return new Cat();
        }
    }
    
    • 匹配指定名称的bean
    public class SpringConfig {
        @Bean
        @ConditionalOnBean(name="jerry")  // 如果容器中存在名为jerry的bean,则创建Cat类的bean
        public Cat tom(){
        	return new Cat();
        }
    }
    
    • 匹配指定环境:
    public class SpringConfig {
        @Bean
        @ConditionalOnNotWebApplication  // 如果当前项目不是Web环境项目,则创建Cat类的bean
        public Cat tom(){
        	return new Cat();
        }
    }
    

tips:

  • @Conditional注解除了使用在方法上,同样可以在类上使用
  • @Conditional注解可同时存在多个

bean依赖的属性配置
  • 使用@EnableConfigurationProperties注解设定使用属性类时加载bean
cartoon:
  cat:
    name: "图多盖洛"
// 该类用于读取yaml配置文件信息
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
    private Cat cat;
}
@Component
@Data
@EnableConfigurationProperties(CartoonProperties.class)  // 这样CartoonProperties类就可以不用声明为bean
public class CartoonCatAndMouse {
    private CartoonProperties cartoonProperties;
    priavte Cat cat;
    
    public CartoonCatAndMouse(CartoonProperties cartoonProperties){
        this.cartoonProperties = cartoonProperties;
        cat = new Cat();
        // 为了避免yaml配置文件没有设置cat对象相应的属性值时报空指针异常,需要进行一系列的判断
        cat.setName(cartoonProperties.getCat()!=null &&
					StringUtils.hasText(cartoonProperties.getCat().getName()) ? 
                    cartoonProperties.getCat().getName():"tom");
    }
}

tips:

  • Spring4.3之后,构造器注入支持非显示注入方式(即不需要在构造器方法上写@Autowired)

自动配置原理
  • 思想:
    • 收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表——>(技术集A)
    • 收集常用技术(技术集A)的使用参数,整理开发过程中每个技术的常用设置列表——>(设置集B)
    • 初始化SpringBoot基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
    • 将技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载
    • 将技术集A中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对)
    • 将设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量
    • 开放设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置(即在yaml配置文件中修改配置)
  • 源码分析:带问号的注解需要重点分析
// 下面为启动类中@SpringBootApplication注解的组成部分
//@SpringBootConfiguration      √
//    @Configuration            √
//        @Component            √
//    @Indexed                  √
//@EnableAutoConfiguration      √
//    @AutoConfigurationPackage     √
//        @Import(AutoConfigurationPackages.Registrar.class)    ?
//    @Import(AutoConfigurationImportSelector.class)            ?
//@ComponentScan(excludeFilters = { √
//        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
//        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
//    })  
// 1.@Import(AutoConfigurationPackages.Registrar.class): 
// 目的:设置当前配置所在的包作为扫描包,后续要针对当前的包进行扫描
// 2.@Import(AutoConfigurationImportSelector.class):
// 分为DeferredImportSelector接口、以Aware结尾的接口和Ordered接口解析:
// 	①以Aware结尾的接口: 想使用ResourceLoader、BeanClassLoader等bean对象就需要实现这些接口
// 	②Ordered: 接口中的getOrder方法定义了实现该接口的类在Spring中的加载顺序(比如要加载第10个bean需要先加载第5个bean,不定义加载顺序的话就回导致加载不上第10个bean)
//  ③DeferredImportSelector接口: 将META-INF/spring.factories中定义的初始设置进行默认加载(即加载技术集A,都是以AutoConfiguration结尾)
public class AutoConfigurationImportSelector implements 
    DeferredImportSelector, 
	BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, 
	Ordered 
    {
        ...
    }

tips:

  • RedisAutoConfiguration类为例展示技术集A:
@Configuration(proxyBeanMethods = false)
// 设置成按条件加载,由开发者决定是否使用该技术(即是否导入了相应的包)
@ConditionalOnClass({RedisOperations.class})  
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
	...
    @Bean
    @ConditionalOnMissingBean(name = {"redisTemplate"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    ...
}
  • 设置集B作为默认配置加载是通过像RedisPropertiesLettuceConnectionConfiguration这样的类:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private int database = 0;
    private String url;
	...
}

变更自动配置
  • 自定义自动配置:创建resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.itheima.bean.CartoonCatAndMouse
  • 控制SpringBoot内置自动配置类加载:
# 方法1: 在yaml配置文件中设置
spring:
  autoconfigure:
	exclude: 
	  - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
	  - org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
// 方法2: 在启动类上设置
// @EnableAutoConfiguration注解中有excludeNam和exclude,而该注解构成了@SpringBootApplication注解
@SpringBootApplication(excludeName = "xxx",exclude = {xxx})
public class DemoApplication {
    ...
}
<!--方法3: 在pom.xml中进行设置-->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- web起步依赖环境中,排除Tomcati起步依赖,匹配自动配置条件 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
    	</exclusions>
	</dependency>
    
    ...
</dependencies>

2.自定义starter

假设要记录系统访客独立IP访问次数

  • 每次访问网站行为均进行统计
  • 后台每10秒输出一次监控信息(IP+访问次数)

需求分析
  • 数据记录位置:Map / Redis

  • 功能触发位置:每次web请求(拦截器)

    • 步骤一:主动调用,仅统计单一操作访问次数(例如查询)
    • 步骤二:开发拦截器
  • 业务参数(配置项)

    • 输出频度,默认10秒

    • 数据特征:累计数据 / 阶段数据,默认累计数据

    • 输出格式:详细模式 / 极简模式

  • 校验环境,设置加载条件


自定义starter
  • 业务功能开发:
public class IpCountService {

    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();

    @Autowired
    // 当前的request对象的注入工作由使用当前starter的工程提供自动装配
    private HttpServletRequest httpServletRequest;
    
	// 每次调用当前操作,就记录当前访问的IP,然后累加访问次数
    public void count(){
        // 1.获取当前操作的IP地址
        String ip = httpServletRequest.getRemoteAddr();
        // 2.根据IP地址从Map取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null){
            ipCountMap.put(ip, 1);
        }else{
            ipCountMap.put(ip,count + 1);
        }
    }

    @Autowired
    private IpProperties ipProperties;

    // 定时任务
    // @Scheduled(cron = "0/${tools.ip.cycle:5} * * * * ?")  // 读取yaml配置文件定义的属性,没读到默认为5
    @Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")  // 读取IpProperties类中的属性,这里的ipProperties指的是@Component("ipProperties")中的"ipProperties"
    public void print(){
        if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){
            System.out.println("         IP访问监控");
            System.out.println("+-----ip-address-----+--num--+");
            for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(String.format("|%18s  |%5d  |",key,value));
            }
            System.out.println("+--------------------+-------+");
        }else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
            System.out.println("     IP访问监控");
            System.out.println("+-----ip-address-----+");
            for (String key: ipCountMap.keySet()) {
                System.out.println(String.format("|%18s  |",key));
            }
            System.out.println("+--------------------+");
        }

        if(ipProperties.getCycleReset()){
            ipCountMap.clear();
        }
    }
}
  • 定义属性类:
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    /**
     * 日志显示周期
     */
    private Long cycle = 5L;

    /**
     * 是否周期内重置数据
     */
    private Boolean cycleReset = false;

    /**
     * 日志输出模式  detail:详细模式  simple:极简模式
     */
    private String model = LogModel.DETAIL.value;

    public enum LogModel{
        DETAIL("detail"),
        SIMPLE("simple");
        private String value;
        LogModel(String value) {
            this.value = value;
        }
        public String getValue() {
            return value;
        }
    }

    // 每个属性的get和set方法
    ...
}

  • 自动配置类:
// @EnableConfigurationProperties(IpProperties.class)  // 属性创建bean的方式,此时IpProperties无需定义为bean
@EnableScheduling  // 开启定时任务
@Import(IpProperties.class)  // 手动控制的方式,但此时IpProperties需要使用@Component定义为bean
public class IpAutoConfiguration {

    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}
  • 创建resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
	com.itheima.ip.autoconfigure.IpAutoConfiguration
  • 配置和定义拦截器:
// 配置拦截器
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ipCountInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public IpCountInterceptor ipCountInterceptor(){
        return new IpCountInterceptor();
    }

}

// 定义拦截器
public class IpCountInterceptor implements HandlerInterceptor {

    @Autowired
    private IpCountService ipCountService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ipCountService.count();
        return true;
    }
}

  • 辅助功能开发:完成yml提示功能的开发

    • 导入配置处理器坐标:生成spring-configuration-metadata.json文件后注释该依赖,要不然提示会出现两次
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    
    • 进行自定义提示功能开发:创建resources/META-INF/spring-configuration-metadata.json(该文件在导入上面依赖并重新install后会在target目录中自动生成,将生成后的文件移到resources/META-INF/即可)
    {
      "groups": [
        {
          "name": "tools.ip",
          "type": "cn.itcast.properties.IpProperties",
          "sourceType": "cn.itcast.properties.IpProperties"
        }
      ],
      "properties": [
        {
          "name": "tools.ip.cycle",
          "type": "java.lang.Long",
          "description": "日志显示周期",
          "sourceType": "cn.itcast.properties.IpProperties",
          "defaultValue": 5
        },
        {
          "name": "tools.ip.cycle-reset",
          "type": "java.lang.Boolean",
          "description": "是否周期内重置数据",
          "sourceType": "cn.itcast.properties.IpProperties",
          "defaultValue": false
        },
        {
          "name": "tools.ip.model",
          "type": "java.lang.String",
          "description": "日志输出模式  detail:详细模式  simple:极简模式",
          "sourceType": "cn.itcast.properties.IpProperties"
        }
      ],
      "hints": [
        {
          "name": "tools.ip.model",
          "values": [
            {
              "value": "detail",
              "description": "详细模式."
            },
            {
              "value": "simple",
              "description": "极简模式."
            }
          ]
        }
      ]
    }
    
  • 配置文件定义属性值:

tools:
  ip:
    cycle: 10
    cycleReset: false
    model: "detail"
  • 模拟调用:在需要使用该自定义starter的项目中导入安装好的starter,然后使用
@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private IpCountService ipCountService;
    
    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){
        // ip访问统计
        ipCountService.count();
        IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
        if( currentPage > page.getPages()){
        page = bookService.getPage((int)page.getPages(), pageSize,book);
        }
        return new R(true, page);
    }
}

tips:

  • 在使用自己开发的包前先cleaninstall安装到maven仓库,确保资源更新
  • 可以选择将功能类(即AutoConfiguration)和starter放在一个包中,也可以将它们分开
  • 系统内置包的命名为spring_boot_starter_xxx,第三方包命名为xxx_spring_boot_starter
  • @EnableConfigurationProperties(xxx.class)@Import(xxx.class)两种方式生成的bean名称不同,后者根据的是xxx类在@Component中定义的bean名称,所以常使用该方式

3.核心原理


SpringBoot启动流程

本质即初始化数据,然后创建容器:

  • 初始化各种属性,加载成对象

    • 读取环境属性(Environment)

    • 系统配置(spring.factories)

    • 参数(Argumentsapplication.yaml)

  • 创建Spring容器对象ApplicationContext,加载各种配置

  • 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求

  • 容器初始化过程中追加各种功能,如统计时间、输出日志等

源码分析过程:以下面代码为入口,分析文本括号中的数字表示语句对应当前文件的行数

@SpringBootApplication  // 该注解前面已分析
public class SpringbootStartupApplication {
    public static void main(String[] args) {
        args = new String[]{"test.value=test.value1"};
        ConfigurableApplicationContext run = SpringApplication.
                                             run(SpringbootStartupApplication.class, args);
    }
}
SpringbootStartupApplication【10】->SpringApplication.run(SpringbootStartupApplication.class, args);
    SpringApplication【1332】->return run(new Class<?>[] { primarySource }, args);
    	# 1.加载各种配置信息,初始化各种配置对象-执行new SpringApplication(primarySources)
        SpringApplication【1343】->return new SpringApplication(primarySources).run(args);
            SpringApplication【1343】->SpringApplication(primarySources)
                SpringApplication【266】->this(null, primarySources);
                    SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
                    	# 初始化资源加载器
                        SpringApplication【281】->this.resourceLoader = resourceLoader;
                        # 初始化配置类的类名信息(格式转换)
                        SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                        # 确认当前容器加载的类型
                        SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath();
                        # 获取系统配置引导信息(看spring.factories中是否有对应属性和值)
                        SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
                        # 获取ApplicationContextInitializer.class对应的实例(看spring.factories中是否有对应属性和值)
                        SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
                        # 初始化监听器,对初始化过程及运行过程进行干预
                        SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                        # 初始化了引导类类名信息(备用)
                        SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass();
            # 2.初始化容器,得到ApplicationContext对象-执行run(args)        
            SpringApplication【1343】->new SpringApplication(primarySources).run(args)
                # 设置计时器
                SpringApplication【323】->StopWatch stopWatch = new StopWatch();
                # 计时开始
                SpringApplication【324】->stopWatch.start();
                # 系统引导信息对应的上下文对象
                SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext();
                # 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(如代码中有System.out,因为服务器没有显示器会报错,所以要模拟显示器等外设避免报错),底层就是设置java.awt.headless=true
                SpringApplication【327】->configureHeadlessProperty();
                # 获取当前注册的所有监听器
                SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args);
                # 监听器执行了对应的操作步骤
                SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass);
                # 获取参数
                SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                # 将前期读取的数据加载成了一个环境对象,用来描述信息
                SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                # 做了一个配置(备用)
                SpringApplication【333】->configureIgnoreBeanInfo(environment);
                # 初始化logo
                SpringApplication【334】->Banner printedBanner = printBanner(environment);
                # ***创建容器对象,根据前期配置的容器类型进行判定并创建***
                SpringApplication【335】->context = createApplicationContext();
                # 设置启动模式
                SpringApplication【363】->context.setApplicationStartup(this.applicationStartup);
                # 对容器进行设置,参数来源于前期的设定
                SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                # 刷新容器环境
                SpringApplication【338】->refreshContext(context);
                # 刷新完毕后做后处理
                SpringApplication【339】->afterRefresh(context, applicationArguments);
                # 计时结束
                SpringApplication【340】->stopWatch.stop();
                # 判定是否记录启动时间的日志
                SpringApplication【341】->if (this.logStartupInfo) {
                # 创建日志对应的对象,输出日志信息(包含启动时间)
                SpringApplication【342】->    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                # 监听器执行了对应的操作步骤
                SpringApplication【344】->listeners.started(context);
                SpringApplication【345】->callRunners(context, applicationArguments);
                # 监听器执行了对应的操作步骤
                SpringApplication【353】->listeners.running(context);     

tips:

  • 如果导入了spring-boot-starter-Web的依赖包,则创建的容器对象也会变化

监听器
  • 类型:

    • 在应用运行但未进行任何处理时,将发送ApplicationStartingEvent
    • Environment被使用,且上下文创建之前,将发送ApplicationEnvironmentPreparedEvent
    • 在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent
    • 在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent
    • 在应用程序和命令行运行器被调用之后,将发出ApplicationReadyEvent,用于通知应用已经准备处理请求
    • 启动时发生异常,将发送ApplicationFailedEvent
  • 使用:

// 如果不定义监听的事件(即未定义泛型),则会在每个事件都运行
// 这里定义泛型ApplicationStartingEvent,则会在应用运行但未进行任何处理时运行代码
public class MyListener implements ApplicationListener<ApplicationStartingEvent>{
    @Override
    public void onApplicationEvent(ApplicationStartingEvent event){
        // 具体代码
        ...
    }
}

资料

视频参考黑马程序员SpringBoot2全套视频教程 P143-P174


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值