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

本文主要分析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的值,将值的所有类注入容器
1.@Import(AutoConfigurationImportSelector.class) 导入AutoConfigurationImportSelector这个类,而这个类的方法就定义我们扫描所有jar包的指定文件,并实例化对象到容器

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

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

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

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

二.springboot启动原理剖析

1.注解@SpringBootApplication

在这里插入图片描述

1.1.@SpringBootConfiguration

@Configuration 表示配置类,注入spring容器
在这里插入图片描述

1.2.@EnableAutoConfiguration

在这里插入图片描述
(1) @AutoConfigurationPackage
原理是这个注解@Import(AutoConfigurationPackages.Registrar.class)

在这里插入图片描述

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

  • AutoConfigurationPackages.Registrar.class
    #这个类中的有个方法是用于:获取当前启动类的包,并且将该包作为基础扫描包,将该包下配置相应注解的类全部注入容器
    在这里插入图片描述

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

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

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

AutoConfigurationImportSelector.getCandidateConfigurations 加载所有需要注入容器的configuration类
在这里插入图片描述
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
在这里插入图片描述
public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;
在这里插入图片描述
在这里插入图片描述
xxxAutoConfiguration类

例如:WebMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = "";

	private static final String[] SERVLET_LOCATIONS = { "/" };

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

分析该类,主要有三类注解:

  • @Configuration #容器注入的注解
    使类注入容器
  • @ConditionalOnWebApplication(type = Type.SERVLET) #注入条件控制注解
    当什么情况下,才将本类注入容器
  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) #注入顺序控制的注解
    在什么之后注入容器

大致的条件控制注解都是@Conditional的派生注解.
在这里插入图片描述

1.3.@ComponentScan(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方法

在这里插入图片描述

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中

在这里插入图片描述
只需要将以下放在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官方编写的启动环境项的命名规则.

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

1.2 设计概述

在这里插入图片描述
实现步骤:

新建一个空包,并且在该空包中新建一个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包
在这里插入图片描述

(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文件中
在这里插入图片描述
在此包中新建一个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>

项目结构
在这里插入图片描述
(6) install安装
先安装AutoConfigure项目

再安装starter项目
在这里插入图片描述
(7) 测试
重新,新建一个测试项目引入starter依赖
pom文件
在这里插入图片描述
application.yml
在这里插入图片描述
Controller

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值