springboot自定义starter

自定义starter

使用自定义starter

自动装配源代码跟踪

从springmvc到springboot最大的特点就是配置少,甚至不需要配置.这其中自动装配起了很大作用.这篇博客会带你了解下自动装配的源码以及怎么自己自定义starter

自定义starter

首先创建一个springboot工程.pom只需要导入spring-boot-starter依赖无需其他.

springboot自定义starter
<?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.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.datang</groupId>
    <artifactId>test1</artifactId>
    <version>1</version>
    <name>test1</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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

这是整体的目录结构.

springboot自定义starter

启动类不能少因为我们自定义的功能依然需要使用springboot的注解.启动类的@SpringBootApplication是一个组合注解它会帮我们自动扫描其他包中的注解.

springboot自定义starter
package com.datang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Test1Application {

    public static void main(String[] args) {
        SpringApplication.run(Test1Application.class, args);
    }

}
View Code

AddLog类是我们最终的功能类,很简单接收两个参数show方法打印出来.

springboot自定义starter
package com.datang.app;

public class AddLog {
    private String log1;
    private String log2;
    public AddLog(String log1,String log2) {
        this.log1 = log1;
        this.log2 = log2;
    }
    
    public void show() {
        System.out.println(log1+"-----"+log2);
    }
}
View Code

LogAutoConfiguration最最核心的类没有之一.类上的四个注解@Configurable它声明当前类是一个配置类,一般和方法注解@Bean配合使用.@ConditionalOnClass(AddLog.class)这是一个条件注解,如果你没有看过springboot的源码会很陌生.这个注解表示要只有类路径下找到AddLog.class才会配置这个bean.那什么情况下类路径会有AddLog.class呢,当然是你的项目引用了当前这个自定义starter的依赖时就会有.@EnableConfigurationProperties(LogProperties.class)这这个注解必须要和@Configurable用在一块,它表示需要将某个类作为配置类.稍后我们将看到怎么使用它.@AutoConfigureAfter(LogProperties.class)这也是一个条件注解,它表示当前类注册成bean需要在LogProperties之后,为什么呢?因为我们的成员变量使用了LogProperties.addLogBean()方法体创建了一个AddLog对象,使用LogProperties这个Bean的两个方法拿到参数构造自己.上有两个注解@Bean就是注册AddLog这个bean,另外一个也是条件注解,它表示当前Spring容器中没有AddLog这个类型的Bean时才需要将方法体内的对象注册到bean中,是为了防止bean冲突.

springboot自定义starter
package com.datang.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import com.datang.app.AddLog;

@Configurable
@ConditionalOnClass(AddLog.class)
@EnableConfigurationProperties(LogProperties.class)
@AutoConfigureAfter(LogProperties.class)
public class LogAutoConfiguration {
    @Autowired
    private LogProperties logProperties;

    @Bean
    @ConditionalOnMissingBean
    public AddLog addLogBean() {
        return new AddLog(logProperties.getLog1(), logProperties.getLog2());
    }
}
View Code

最后一个配置类,@ConfigurationProperties(prefix = "dfsn.log")它可以从yml或者properties读取属性然后封装到LogProperties对象中.要开启这个注解必须使用@EnableConfigurationProperties

springboot自定义starter
package com.datang.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "dfsn.log")
public class LogProperties {
    private String log1;
    private String log2;
    public String getLog1() {
        return log1;
    }
    public void setLog1(String log1) {
        this.log1 = log1;
    }
    public String getLog2() {
        return log2;
    }
    public void setLog2(String log2) {
        this.log2 = log2;
    }
    
    
}
View Code

现在思路很清晰了,我们自定义的starter功能类是AddLog它需要实例参数,它的参数由LogProperties类从配置文件中读取,LogAutoConfiguration用于在指定条件满足情况下将AddLog这个类装到Spring容器中.最后我们还需要最后一步,按照springboot的规则配置一个文件这个文件名是固定的需要在META-INF文件夹下名称为spring.factories,文件内容是一个key-value.它的key也是固定的value是LogAutoConfiguration配置类.

springboot自定义starter
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.datang.config.LogAutoConfiguration
View Code

使用自定义starter

pom文件中指定自定义starter的依赖.

springboot自定义starter
<?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.2.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <!--springBoot-start SpringBoot启动项  -->
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

<dependency>
    <groupId>com.datang</groupId>
    <artifactId>test1</artifactId>
    <version>1</version>
    </dependency>

        <!--  <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency> -->


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

使用@Autowired注入自定义starter中的应用类调用方法.

springboot自定义starter
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.datang.app.AddLog;


@RestController
@SpringBootApplication
public class DemoApplication {
    
    @Autowired
    private AddLog addLog;
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("test")
    public String test() {
        addLog.show();
        return "success";
    }
    
}
View Code

yml文件中配置两个参数就是LogProperties需要读取的两个参数.

springboot自定义starter
# Spring配置
#spring:
  # redis配置  
  #redis: 
     # 地址
     #host: 127.0.0.1
     # 端口,默认为6379
     #port: 6379 
     
dfsn:
  log:
    log1: 我是log1
    log2: 我是log2
     
View Code

springboot自定义starter

springboot自定义starter

springboot自定义starter

自动装配源代码跟踪

看源代码首先需要明确,自己要找的功能是什么,不要盲目进入源码.首先我们思考,springboot自动装配都能干啥,我们平时整合Redis,mongoDB等中间件时,一般只需要引入依赖,yml配置,然后就可以使用bean了.思考一下,springboot需要整合那么多中间件,是不是要有一个文件描述了那些中间件是支持的.spring-boot-autoconfigure这个项目它就是spring自动装配的核心项目.在MATE-INF下有一个spring.factories文件它里边就有org.springframework.boot.autoconfigure.EnableAutoConfiguration属性,它的value是集合.这就像是我们自定义的starter一样.

springboot自定义starter

springboot自定义starter

springboot自定义starter

我们找一个熟悉的org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration那这个类就是Redis的自动装配配置类了.

springboot自定义starter

springboot自定义starter

这个Redis的配置类和我们写的核心步骤是没有区别的.它多了一个@Import注解,这个注解就是spring中的<import>标签表示该类还有其他的配置类.

现在我们大概知道了,springboot自动装配的步骤就是,现在spring.factories文件中配置需要整合的中间件的配置类,如果是我们自定义的也需要在META-INF下创建同名的文件,并且使用相同的key.然后spring会根据value去找配置类.从配置类上读取条件注解,判断是否需要注册bean.条件注解中两个相当重要的是@ConditionalOnClass和@EnableConfigurationProperties它们分别对应pom文件中必须要有依赖,yml中必须有配置.我们自定义的starter也需要这样.

接下来我们看springboot的核心注解.SpringBootApplication除了元注解外,它有@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解.@SpringBootConfiguration其实就是封装了@Configuration表示当前类是一个配置类.@ComponentScan是一个包扫描标识,它和xml中的<context:component-scan>标签作用一致.告诉springboot需要扫描某个包中的文件看看它里边有没有注解需要解析.springboot2后这个注解默认会扫描启动类同包以及其子包.

springboot自定义starter
@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 {
View Code

@EnableAutoConfiguration这个注解是自动装配的核心注解.它导入了AutoConfigurationImportSelector类.

springboot自定义starter
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
View Code

AutoConfigurationImportSelector的getCandidateConfigurations返回了一个集合,这个集合中包含了很多类全路径,在这个集合中的都是符合条件被自动装配的bean

springboot自定义starter

现在我们反着推导,看看从哪里筛选出这些符合条件的类的.

进入这个方法SpringFactoriesLoader.loadFactoryNames这个方法通过classLoader去public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";路径下找配置.springboot自动装配和我们自定义starter的都是这个路径.这样就把所有可能需要装配的类的配置类给找到了.

springboot自定义starter

springboot自定义starter

springboot自定义starter

再次回到AutoConfigurationImportSelector现在已经找到了这些配置类,要看看如何筛选符合条件的类.找到谁调用了getCandidateConfigurations()

springboot自定义starter

 springboot自定义starter

springboot自定义starter

接下来我们就看OnClassCondition是怎么判断的.

springboot自定义starter

springboot自定义starter

springboot自定义starter

springboot自定义starter

springboot自定义starter

springboot自定义starter

 

上一篇:一线互联网企业高级Java工程师面试题大全,Java面试重点问题


下一篇:Springcloud学习笔记36--Springboot 项目maven 常用依赖和application.yml配置