前言
springboot 中有很多的starter 。我們用起來很爽不是嗎,之所以這麼爽難道不是因爲我們少了很多配置嗎,之前我們想要使用jdbcTemplete 是不是需要在xml 文件中配置bean 後才能使用。有被xml 一推配置支配的恐懼嗎?
爲了解救從多處於水深火熱的程序猿們,springboot 橫空出世,帶着starter 的利刃來普度我們了。
我們在心懷感激的同時,是不是也得有一絲探祕之心。所以我們就手擼一個自定義的starter 吧。
預備知識
在之前我們還是說一下借個註解
@ConfigurationProperties
這個註解我們應該有了解過,我們需要在application.properties 中配置一個對象的時候怎麼配置的?是不是類似下面這樣的。
所以我們就需要在我們的實體類中加上這個註解,並prefix=“user” .
然後我們在創建一個實體類的實例時,就可以獲取到從application.properties中配置的屬性值。
所以這個註解的作用就是獲取配置文件中配置的bean,作用在類上 。而 @Value 註解則是作用在類屬性上。
我們再看下註解ConfigurationProperties源碼:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}
@AliasFor 註解是爲屬性起一個別名。表示使用prefix 和使用value 的效果是一樣的。
@EnableConfigurationProperties
這個註解是幹嘛的?
是使得 用了 @ConfigurationProperties 註解的類生效。如果一個配置類只配置@ConfigurationProperties註解,而沒有使用@Component,那麼在IOC中是獲取不到properties 配置文件轉化的bean。@EnableConfigurationProperties 相當於把使用 @ConfigurationProperties 的類進行了一次注入。
源碼:
@ConditionalOnClass
這個註解我們相對陌生一些,但是這個註解卻是springboot 中實現自動自動裝配的關鍵。主要是判斷被該註解標註的類是否在classoath 中存在,如果存在,就將bean 載入到ioc 中。
是不是覺得很簡單,甚至有點索然無味?
不說廢話了,動手動手,開擼開擼。
依賴
既然上面說了這些註解,後面當然是需要用到了的,既然需要用到這些註解,就需要引入依賴啦。
我們首先創建一個普通的maven 項目,然後引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
</dependencies>
autoconfigure 的依賴是上面的註解需要用到的。lombok是我爲了用來代替實體類的get 和set 方法的。
實體類
我們引入依賴後,創建一個實體類,方便我們在application.properties 中配置屬性。
@Getter
@Setter
@ConfigurationProperties(prefix = "logo")
public class LogoProperties {
public static final String NAME="程序員愛酸奶";
public static final String URL="xyz.quellanan";
public static final String CONTENT="welcome !";
private String name;
private String url;
private String content;
}
再創建一個Logo 的實體類
* @Version 1.0
*/
@Getter
@Setter
@ToString
public class Logo {
private String name;
private String url;
private String time;
private String content;
}
config
接下來就關鍵啦,我們已經創建好了實體類,那我們怎麼將完成自動注入呢?
我們創建一config 類。內容如下:
@Configuration
@EnableConfigurationProperties(LogoProperties.class)
@ConditionalOnClass(Logo.class)
public class LogoConfig {
@Autowired
private LogoProperties logoProperties;
@Bean
Logo logo(){
Logo logo=new Logo();
logo.setName(logoProperties.getName()==null?LogoProperties.NAME:logoProperties.getName());
logo.setUrl(logoProperties.getUrl()==null?LogoProperties.URL:logoProperties.getUrl());
logo.setContent(logoProperties.getContent()==null?LogoProperties.CONTENT:logoProperties.getContent());
logo.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()));
System.out.println(logo.getName());
return logo;
}
}
使用了三個註解。
@Configuration 表示我們項目在啓動完的時候,會自動執行的類。
@EnableConfigurationProperties(LogoProperties.class) 就是我們前面的說的,使得
LogoProperties.class的 @ConfigurationProperties 註解生效。
@ConditionalOnClass(Logo.class) 標記Logo.class 需要自動裝載 bean 。
然後就會說@bean 註解,創建了一個logo的bean 並做了一些簡單處理。
spring.factories
做完上面那些你以爲就可以了嗎?
其實並沒有哈哈,還有關鍵一步。
前面這些並不能讓項目啓動的時候完成注入自定義的bean .
還得從我們springboot 項目啓動類說起。我們啓動類上是不是有@SpringBootApplication
我們看下源碼
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
主要就是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
三個註解的組合。我們這裏接着看下@EnableAutoConfiguration的註解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到導入了 AutoConfigurationImportSelector.class 那這個類是幹嘛的?
這個類自動加載相關配置的。我們看下getAutoConfigurationEntry方法。
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
public Class<? extends Group> getImportGroup() {
return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
}
getAutoConfigurationEntry方法獲取了spring-boot項目中需要自動配置的項(bean),最看見的就是 getCandidateConfigurations方法了,它獲取了所有可能參與到項目的候選配置bean,與之對應的,getExclusions獲取了所有不需要加載的配置bean。進一步查看getCandidateConfigurations方法的源碼。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
發現就是加載 META-INF/spring.factories 文件的內容。如果 spring.factories 中沒有配置,那麼就無法找到對應的類,實現自動注入了。
上面說了一堆,反正明白一個道理就可以了,需要配置spring.factories。
所以我們在resources 目錄下創建META-INF/spring.factories 文件。
內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xyz.quellanan.config.LogoConfig
指向我們需要自動加載的類。
測試
到此,算是真正的萬事俱備只欠東風了。
是騾子是馬,拉出來溜溜就知道了,所以我們來試試。
新建一個springboot 項目,依賴中引入我們自定義的starter.
<dependency>
<groupId>cn.quellanan</groupId>
<artifactId>logo-starter</artifactId>
<version>1.0.0</version>
</dependency>
然後寫一個測試接口
@RestController
public class HelloController {
@Autowired
Logo logo;
@RequestMapping("/hello")
public String hello(){
System.out.println(logo.toString());
return logo.toString();
}
}
然後我們啓動項目。發現打印了我們在自定義starter 中的logo 的name
System.out.println(logo.getName());
我們調接口看下
這是我們沒有配置的時候,使用的默認屬性。我們在application.properties 中配置自己的屬性
然後再重新啓動看下。
變成我們自己注入的屬性了不是嗎。
番外
到此我們手擼一個starter 也很簡單不是嗎?那自己擼一個,不香嗎?
兄弟們看到這了,不點贊無所謂(表面平靜,內心期待的我)