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

SpringBoot之Starter组件手写和启动原理解析

程序员文章站 2022-04-02 18:16:14
...

定义:

starter是一种服务(或者叫插件)——使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,只需要导入相关maven坐标,由Spring Boot自动通过classpath路径下的类注入需要的Bean,并织入bean。

简而言之:组件化开发思维,提高代码复用性,避免重复造*,开箱即用!!

大概启动原理

Spring Boot的启动类上有一个@SpringBootApplication注解,在@SpringBootApplication中有一个注解@EnableAutoConfiguration,就是开启自动配置。
SpringBoot之Starter组件手写和启动原理解析
而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。
SpringBoot之Starter组件手写和启动原理解析
SpringBoot之Starter组件手写和启动原理解析
SpringBoot之Starter组件手写和启动原理解析
有一个这样的spring.factories文件,这个spring.factories文件也是一组一组的key=value的形式。
通过SPI机制扫描spring.factories这个文件,读取里面的配置信息。

SpringBoot之Starter组件手写和启动原理解析
这个文件其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

主要流程:
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。

主要的几个注解:
@EnableAutoConfiguration
@SpringBootConfiguration -> @Configuration -> @Component
@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage

其实Starter就是一些整合包。
Spring 最初利用“工厂模式”( DI )和“代理模式”( AOP )解耦应用组件。按照这种模式封装了一个 MVC 框架,用开发 web 应用。后来发现每次开发都要搞很多依赖,写很多样板代码,使代码臃肿而麻烦,于是就有了一些整合包( starter ),这套就是 Spring Boot。它和原有的springmvc相比只不过是将原有的配置在xml文件中的内容做了自动化配置而已。

手写Starter实践步骤

1. 新建项目

新建一个普通maven项目,作为一个Starter组件,编写一个测试A项目(后面统称为A项目),根据导入的maven坐标动态加载注入相关Bean。
SpringBoot之Starter组件手写和启动原理解析
2. 加入spring-boot-starter坐标

	<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.2.6.RELEASE</version>
    </dependency>

3. 新建一个接口,添加一个format方法,然后两个类实现接口重写方法

public interface FormatProcess {
    
    <T> String format(T obj);
    
}
public class JsonFormatProcess implements FormatProcess{
    @Override
    public <T> String format(T obj) {
        return "JSON format: " + JSON.toJSONString(obj);
    }
}
public class StringFormatProcess implements FormatProcess{
    @Override
    public <T> String format(T obj) {
        return "String format:" + obj.toString();
    }
}

4. 接着添加一个format配置类,添加@Configuration注解,表明这是一个配置类

把前面写的JsonFormatProcess和StringFormatProcess 注入IOC容器 。
其中一个加上@Primary,表明多个这是默认的Bean调用方法。
@ConditionalOnMissingClass(“com.alibaba.fastjson.JSON”)这个注解表示如果某个B项目依赖了我们这个A项目的话,如果B项目没有添加fastjson依赖,那么就会走这个getStringFormat()方法;如果B项目有这个fastjson依赖,就会走getJsonFormat,大意就是判断是否有某个类。

@Configuration
public class FormatConfiguration {
    
    @Bean
    @Primary
    @ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
    public StringFormatProcess getStringFormat(){
        return new StringFormatProcess();
    }
    
    @Bean
    @ConditionalOnClass(name = "com.alibaba.fastjson.JSON")
    public JsonFormatProcess getJsonFormat(){
        return new JsonFormatProcess();
    }
}

5. 新建一个模板类

新建HelloFormatTemplate,添加doFormat方法,通过FormatProcess 调用format方法。
后面通过注入HelloFormatTemplate 直接调doFormat来实现方法调用。

public class HelloFormatTemplate {

    private FormatProcess process;

    public HelloFormatTemplate(FormatProcess process) {
        this.process = process;
    }

    public <T> String doFormat(T obj){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("format result : ").append(process.format(obj)).append("<br/>");
        return stringBuilder.toString();
    }
}

6. 添加HelloConfiguration配置类

@Import(FormatConfiguration.class)导入前面的FormatConfiguration配置类。

@Configuration
@Import(FormatConfiguration.class)
public class HelloConfiguration {

    @Bean
    public HelloFormatTemplate helloFormatTemplate(FormatProcess formatProcess){
        return new HelloFormatTemplate(helloProperties,formatProcess);
    }

}

7. resources资源文件夹下添加META-INF文件夹

添加META-INF文件夹,创建spring.factories文件,这文件是key-value的形式。

key是EnableAutoConfiguration全路径,value就是HelloConfiguration的全路径

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.xjp.test.configuration.HelloConfiguration

8. A项目编写完成,maven命令install安装到本地

9. 新建一个SpringBoot项目
定义为B项目(后面统称为B项目),和前面的A项目区分
SpringBoot之Starter组件手写和启动原理解析
导入坐标

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        //下面这个是前面maven打包的A项目
		<dependency>
            <groupId>org.xjp.test</groupId>
            <artifactId>format-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

10. 新建Controller类和一个User实体类

@RestController
public class HelloController {

    @Autowired
    private HelloFormatTemplate helloFormatTemplate;

    @GetMapping("/hello")
    public String hello(){
        User user = new User();
        user.setAge(20);
        user.setName("测试spring starter");
        return helloFormatTemplate.doFormat(user);
    }
}
public class User {

    public Integer age;

    public String name;

    public Integer getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

然后导入fastjson的pom坐标,因为前面A项目有一个注解判断条件(@ConditionalOnMissingClass和@ConditionalOnClass)是否调用getStringFormat()或者getJsonFormat()方法。

		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

因为加入了json坐标,所以会走getJsonFormat()这个方法
SpringBoot之Starter组件手写和启动原理解析
注释fastjson坐标,会走getStringFormat()方法。
SpringBoot之Starter组件手写和启动原理解析
SpringBoot之Starter组件手写和启动原理解析
这个就是SpringBoot一个Starter的大概原理和加载流程

如果我们需要自定义配置文件,那就是下面要说的

读取配置文件

1. A项目添加坐标
这个坐标就是允许我们自定义配置文件

	<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <version>2.2.6.RELEASE</version>
      <optional>true</optional>//可选
    </dependency>

2. 添加配置文件配置类

@ConfigurationProperties表明这个是配置文件类,HelloProperties.HELLO_PREFIX这个表明配置文件的前缀是xjp.hello.prefix。
定义前缀和一个map集合。map集合的key就配置文件的key,Object就是value

@ConfigurationProperties(HelloProperties.HELLO_PREFIX)
public class HelloProperties {

    public static final String HELLO_PREFIX = "xjp.hello.prefix";

    public Map<String,Object> info;

    public Map<String, Object> getInfo() {
        return info;
    }

    public void setInfo(Map<String, Object> info) {
        this.info = info;
    }
}

重新maven打包

3. 添加Properties相关的字段和参数
SpringBoot之Starter组件手写和启动原理解析
SpringBoot之Starter组件手写和启动原理解析
4. 在B项目配置文件设置相关的key-value
key是String类型,value随意设置
SpringBoot之Starter组件手写和启动原理解析
然后在application设置的key-value就可以显示在页面
SpringBoot之Starter组件手写和启动原理解析
小白一个,还在继续学习中。。。。。。