欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

76-java-springboot(2)-启动原理分析及自定义starter

程序员文章站 2022-05-17 08:55:56
...

本文主要分析springboot启动原理分析及自定义starter

一.springboot原理三问

1.首先我们springBoot加载自己编写的bean是怎么加载的?

思考: 加载bean到容器,无外乎,递归扫描基础包,找到相应的注解修饰的类,然后放入容器.所以我们需要指明基础包.

实现: 通过这个注解@AutoConfigurationPackage 获得当前主启动类的包,并且以这个包为基础包

2.其次我们导入其他starter项目的时候,他怎么帮我注入bean到容器的?

思考1: 上面的方式只能将我们自己项目中的类放入容器,怎么将自动将引入的依赖的某些类放入容器呢?

  • 修改其他jar包,也将他们的类加上相应的注解?----这个不可能

  • 增加一个适配的中间依赖,在依赖中专门规定一个文件,来指定我们需要注入什么类到spring的容器,好像可以

实现1: 统一扫描所有的jar包中的META-INF/spring.factories文件,读取org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,将值的所有类注入容器
aaa@qq.com(AutoConfigurationImportSelector.class) 导入AutoConfigurationImportSelector这个类,而这个类的方法就定义我们扫描所有jar包的指定文件,并实例化对象到容器

思考2: 我们有些类不能直接就实例化到spring容器中,不然可能会报错,但是我们又想自动注入?
实现2: 于是我们以@Conditional注解为基础扩展了其他的注解,可以实现很多中条件下,我们才让本配置类生效.
于是产生了各种的@ConditionalOnxxx注解,用于控制本配置类的生效

3.他帮我们注入bean到容器,我们有是怎么通过配置文件修改那些bean的属性的?

思考: 上面已经帮我们实现了自动注入,但是我们还不能修改对应的bean的属性?
于是我们想到了,在引入相关类的时候,我们可以定义一个专门的properties类来读取默认配置文件中的值,可以利用注解@ConfigurationProperties(prefix = “spring.batch”)和注解
实现:

  1. 我们在实例化XXXAutoConfiguration类的时候可以指定读取配置文件中的值aaa@qq.com(BatchProperties.class)启动读取配置文件的值
  2. BatchProperties.class用@ConfigurationProperties注解标注,读取配置文件的值.
  3. XXXAutoConfiguration类中,注入BatchProperties,并将其属性值,配到我们注入容器中的bean对象的属性中去.

二.springboot启动原理剖析

1.注解@SpringBootApplication

76-java-springboot(2)-启动原理分析及自定义starter

aaa@qq.com

@Configuration 表示配置类,注入spring容器
76-java-springboot(2)-启动原理分析及自定义starter

aaa@qq.com

76-java-springboot(2)-启动原理分析及自定义starter
(1) @AutoConfigurationPackage
原理是这个注解@Import(AutoConfigurationPackages.Registrar.class)

76-java-springboot(2)-启动原理分析及自定义starter

  • @Import()
    #注解用于向容器中注入bean实例

  • AutoConfigurationPackages.Registrar.class
    #这个类中的有个方法是用于:获取当前启动类的包,并且将该包作为基础扫描包,将该包下配置相应注解的类全部注入容器
    76-java-springboot(2)-启动原理分析及自定义starter

(2) @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector.class

  • 这个类有个process方法:这个方法会调用SpringFactoriesLoader.loadFactoryNames()方法,而这个方法会扫描所有jar包将
    类路径下 META-INF/spring.factories
    里面配置的所有EnableAutoConfiguration的值加入到了容器中;

  • 将 类路径下 META-INF/spring.factories
    里面配置的所有EnableAutoConfiguration的值加入到了容器中;这里的类的名字都是xxxAutoConfiguration

AutoConfigurationImportSelector.getCandidateConfigurations 加载所有需要注入容器的configuration类
76-java-springboot(2)-启动原理分析及自定义starter
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
76-java-springboot(2)-启动原理分析及自定义starter
public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;
76-java-springboot(2)-启动原理分析及自定义starter
76-java-springboot(2)-启动原理分析及自定义starter

aaa@qq.com(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

#配置组件扫描时排除的拦截器类型

2. SpringApplication.run(本类名.class, args);

这个是springboot项目的入口.
(1)启动流程:
流程答题分为两步:

  • new 一个springapplication对象
  • 执行run方法

76-java-springboot(2)-启动原理分析及自定义starter

new一个SpringApplication对象

private void initialize(Object[] sources) { 
	//保存主配置类 
	if (sources != null && sources.length > 0) { 
		this.sources.addAll(Arrays.asList(sources)); 
		}
	//判断当前是否一个web应用 
	this.webEnvironment = deduceWebEnvironment(); 
	//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起 来 
	setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
	 //从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener 
	 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
	 //从多个配置类中找到有main方法的主配置类 
	 this.mainApplicationClass = deduceMainApplicationClass(); 
	 }

执行run方法

public ConfigurableApplicationContext run(String... args) { 
	StopWatch stopWatch = new StopWatch(); 
	stopWatch.start();
	ConfigurableApplicationContext context = null; 
	FailureAnalyzers analyzers = null; 
	configureHeadlessProperty(); 
	
	//获取SpringApplicationRunListeners;从类路径下META‐INF/spring.factories 
	SpringApplicationRunListeners listeners = getRunListeners(args); 
	//回调所有的获取SpringApplicationRunListener.starting()方法 
	listeners.starting(); 
	try {
		//封装命令行参数 
		ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); 
		//准备环境 
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 
		//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准 备完成
		Banner printedBanner = printBanner(environment);
	 	//创建ApplicationContext;决定创建web的ioc还是普通的ioc 
	 	context = createApplicationContext(); 
	 	analyzers = new FailureAnalyzers(context); 
	 	//准备上下文环境;将environment保存到ioc中;而且applyInitializers(); 
		//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法 
		//回调所有的SpringApplicationRunListener的contextPrepared(); 
		prepareContext(context, environment, listeners, applicationArguments, printedBanner); 
	 	//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded(); 
	 	//s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版 
	 	//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
	  	refreshContext(context); 
	 	//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 
	 	//ApplicationRunner先回调,CommandLineRunner再回调 
		afterRefresh(context, applicationArguments); 
	 	//所有的SpringApplicationRunListener回调finished方法 
	 	listeners.finished(context, null); 
	 	stopWatch.stop(); 
	 	if (this.logStartupInfo) {
	 	 	new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); 
	 	 }
	 	 //整个SpringBoot应用启动完成以后返回启动的ioc容器; 
	 	return context; 
	 }catch (Throwable ex) {
		 handleRunFailure(context, listeners, analyzers, ex);
		 throw new IllegalStateException(ex); 
	} 
}

(2)几个重要的事件回调机制

  • 配置在META-INF/spring.factories

    • ApplicationContextInitializer
    • SpringApplicationRunListener
  • 只需要放在ioc容器中

    • ApplicationRunner
    • CommandLineRunner

自定义监听:

  • 1.ApplicationContextInitializer
package com.atguigu.springboot.listener;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
    }
}

  • 2.SpringApplicationRunListener
package com.atguigu.springboot.listener;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {

    //必须有的构造器
    public HelloSpringApplicationRunListener(SpringApplication application, String[] args){

    }

    @Override
    public void starting() {
        System.out.println("SpringApplicationRunListener...starting...");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        Object o = environment.getSystemProperties().get("os.name");
        System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...contextPrepared...");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...contextLoaded...");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }


    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("SpringApplicationRunListener...finished...");
    }
}

  • 3.配置(META-INF/spring.factories);将以上两个自定义类配置到该factories中

76-java-springboot(2)-启动原理分析及自定义starter
只需要将以下放在ioc容器中

  • 4.ApplicationRunner
package com.atguigu.springboot.listener;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class HelloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner...run....");
    }
}

  • 5.CommandLineRunner
package com.atguigu.springboot.listener;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.Arrays;


@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
    }
}

这样我们就可以干预springBoot启动过程了,通过干预这些过程,我们可以做一些自定义配置.

三.自定义starter

1.命名规范及设计概述

1.1 命名规范
  • spring-boot-starter-xxx
    #这是spring官方的编写的启动环境项的命名规则.

  • xxx-spring-boot-starter
    #这个是非spring官方编写的启动环境项的命名规则.

1.2 设计概述

76-java-springboot(2)-启动原理分析及自定义starter
实现步骤:

新建一个空包,并且在该空包中新建一个springboot项目 xxx-spring-boot-autoconfigure,并在该项目中抒写
(0) 修改项目结构
pom文件
#根据实际依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>gl-spring-boot-starter-autoconfiguration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gl-spring-boot-starter-autoconfiguration</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

    </dependencies>

</project>

项目结构
删除启动类/application文件,以及test包
76-java-springboot(2)-启动原理分析及自定义starter

(1) HelloProperties.class 配置文件读取类
作用:读取配置文件中的值

 //读取application文件中的一hello为前缀的属性值
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public HelloProperties setName(String name) {
        this.name = name;
        return this;
    }

    public String getAge() {
        return age;
    }

    public HelloProperties setAge(String age) {
        this.age = age;
        return this;
    }
}

(2) hello.class
作用:这个是业务类,也就是我们真正需要的类.一般是我们其他jar包中的类,所以是自己编写的.

public class Hello {
    private HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public Hello setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
        return this;
    }

    public String sayHello(){
        return "hello:" + helloProperties.getName() + ";年龄:" + helloProperties.getAge();
    }

}

(3) HelloAutoConfiguration.class
作用:这个是真正的将hello(业务类)组装属性,注入容器的类

@ConfigurationProperties
@ConditionalOnWebApplication
//启动helloProperties.class类,并注入容器.
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Autowired
    private HelloProperties helloProperties;

    @Bean
    public Hello getHello(){
        Hello hello = new Hello();
        hello.setHelloProperties(helloProperties);
        return hello;
    }
}

(4) META-INF/spring.factories
将HelloAutoConfiguration编写到factories文件中
76-java-springboot(2)-启动原理分析及自定义starter
在此包中新建一个maven module : xxx-spring-boot-starter
(5)修改项目结构(maven项目)
pom文件
#引入刚刚写的那个项目 xxx-spring-boot-autoconfigure

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>gl-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gl-spring-boot-starter</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>gl-spring-boot-starter-autoconfiguration</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    </dependencies>
</project>

项目结构
76-java-springboot(2)-启动原理分析及自定义starter
(6) install安装
先安装AutoConfigure项目

再安装starter项目
76-java-springboot(2)-启动原理分析及自定义starter
(7) 测试
重新,新建一个测试项目引入starter依赖
pom文件
76-java-springboot(2)-启动原理分析及自定义starter
application.yml
76-java-springboot(2)-启动原理分析及自定义starter
Controller

@RestController
public class HelloController {
    @Autowired //此hello是我们自定义的starter注入容器中的
    private Hello hello;

    @GetMapping("/hello")
    public String hello(){
        String s = hello.sayHello();
        return s;
    }
}

启动容器,访问测试即可.

相关标签: springboot 框架