自定義starter
使用自定義starter
自動裝配源代碼跟蹤
從springmvc到springboot最大的特點就是配置少,甚至不需要配置.這其中自動裝配起了很大作用.這篇博客會帶你了解下自動裝配的源碼以及怎么自己自定義starter
自定義starter
首先創建一個springboot工程.pom只需要導入spring-boot-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>
這是整體的目錄結構.
啟動類不能少因為我們自定義的功能依然需要使用springboot的注解.啟動類的@SpringBootApplication是一個組合注解它會幫我們自動掃描其他包中的注解.

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); } }
AddLog類是我們最終的功能類,很簡單接收兩個參數show方法打印出來.

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); } }
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沖突.

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()); } }
最后一個配置類,@ConfigurationProperties(prefix = "dfsn.log")它可以從yml或者properties讀取屬性然后封裝到LogProperties對象中.要開啟這個注解必須使用@EnableConfigurationProperties

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; } }
現在思路很清晰了,我們自定義的starter功能類是AddLog它需要實例參數,它的參數由LogProperties類從配置文件中讀取,LogAutoConfiguration用於在指定條件滿足情況下將AddLog這個類裝到Spring容器中.最后我們還需要最后一步,按照springboot的規則配置一個文件這個文件名是固定的需要在META-INF文件夾下名稱為spring.factories,文件內容是一個key-value.它的key也是固定的value是LogAutoConfiguration配置類.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.datang.config.LogAutoConfiguration
使用自定義starter
pom文件中指定自定義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>
使用@Autowired注入自定義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"; } }
yml文件中配置兩個參數就是LogProperties需要讀取的兩個參數.

# Spring配置 #spring: # redis配置 #redis: # 地址 #host: 127.0.0.1 # 端口,默認為6379 #port: 6379 dfsn: log: log1: 我是log1 log2: 我是log2
自動裝配源代碼跟蹤
看源代碼首先需要明確,自己要找的功能是什么,不要盲目進入源碼.首先我們思考,springboot自動裝配都能干啥,我們平時整合Redis,mongoDB等中間件時,一般只需要引入依賴,yml配置,然后就可以使用bean了.思考一下,springboot需要整合那么多中間件,是不是要有一個文件描述了那些中間件是支持的.spring-boot-autoconfigure這個項目它就是spring自動裝配的核心項目.在MATE-INF下有一個spring.factories文件它里邊就有org.springframework.boot.autoconfigure.EnableAutoConfiguration屬性,它的value是集合.這就像是我們自定義的starter一樣.
我們找一個熟悉的org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration那這個類就是Redis的自動裝配配置類了.
這個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后這個注解默認會掃描啟動類同包以及其子包.

@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 {
@EnableAutoConfiguration這個注解是自動裝配的核心注解.它導入了AutoConfigurationImportSelector類.

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector的getCandidateConfigurations返回了一個集合,這個集合中包含了很多類全路徑,在這個集合中的都是符合條件被自動裝配的bean
現在我們反着推導,看看從哪里篩選出這些符合條件的類的.
進入這個方法SpringFactoriesLoader.loadFactoryNames這個方法通過classLoader去public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";路徑下找配置.springboot自動裝配和我們自定義starter的都是這個路徑.這樣就把所有可能需要裝配的類的配置類給找到了.
再次回到AutoConfigurationImportSelector現在已經找到了這些配置類,要看看如何篩選符合條件的類.找到誰調用了getCandidateConfigurations()
接下來我們就看OnClassCondition是怎么判斷的.