這篇博客主要講述一下springboot怎么給我們簡(jiǎn)化了大量的配置,然后跟著源碼自己封裝一個(gè)Starter,首先我們需要從兩個(gè)地方來說,第一就是springboot的起步依賴,第二就是springboot自動(dòng)裝配;
起步依賴
我們?cè)趧?chuàng)建一個(gè)springboot工程時(shí)需要引入spring-boot-starter-web
這個(gè)依賴;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
這個(gè)依賴我們點(diǎn)進(jìn)去可以看到其實(shí)這個(gè)起步依賴集成了常用的web依賴,例如spring-web
,spring-webmvc
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
Spring Boot的起步依賴說白了就是對(duì)常用的依賴進(jìn)行再一次封裝,方便我們引入,簡(jiǎn)化了 pom.xml 配置,但是更重要的是將依賴的管理交給了 Spring Boot,我們無需關(guān)注不同的依賴的不同版本是否存在沖突的問題,Spring Boot 都幫我們考慮好了,我們拿來用即可!
在使用 Spring Boot 的起步依賴之前,我們需要在pom.xml
中添加配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
即讓pom.xml
繼承 Spring Boot 的pom.xml
,而 Spring Boot 的pom.xml
里面定義了常用的框架的依賴以及相應(yīng)的版本號(hào),我們無需擔(dān)心版本沖突問題;
自動(dòng)裝配
首先我們知道springboot啟動(dòng)需要一個(gè)啟動(dòng)引導(dǎo)類,這個(gè)類除了是應(yīng)用的入口之外,還發(fā)揮著配置的 Spring Boot 的重要作用。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
可以看到@SpringBootApplication
這個(gè)注解,我們點(diǎn)擊進(jìn)去這個(gè)注解,發(fā)現(xiàn)它發(fā)揮著多個(gè)注解的作用,這也體現(xiàn)了注解的派生性和層次性;
@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 {};
//........
}
這里的@SpringBootConfiguration
和@ComponentScan
注解,前者其實(shí)就是@Configuration
注解,就是起到聲明這個(gè)類為配置類的作用,而后者起到開啟自動(dòng)掃描組件的作用。
我們重點(diǎn)分析一下@EnableAutoConfiguration
這個(gè)注解,這個(gè)注解的作用就是開啟Spring Boot 的自動(dòng)裝配功能,我們點(diǎn)進(jìn)行看下:
@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 {};
}
我們重點(diǎn)分析一下@Import({AutoConfigurationImportSelector.class})
這個(gè)注解,我們知道@Import
的作用是將組件添加到 Spring 容器中,而在這里即是將AutoConfigurationImportSelector
這個(gè)組件添加到 Spring 容器中。也就是將AutoConfigurationImportSelector
聲明成一個(gè)Bean;
我們重點(diǎn)分析一下@Import
注解中的AutoConfigurationImportSelector
類;
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
在getAutoConfigurationEntry
方法中掃描ClassPath
下的所有jar
包的spring.factories
文件,將spring.factories
文件key
為EnableAutoConfiguration
的所有值取出,然后這些值其實(shí)是類的全限定名,也就是自動(dòng)配置類的全限定名,然后 Spring Boot 通過這些全限定名進(jìn)行類加載(反射),將這些自動(dòng)配置類添加到 Spring 容器中。
我們找到一個(gè)名為spring-boot-autoconfigure-2.1.4.RELEASE.jar
的 jar 包,打開它的spring.factories
文件,發(fā)現(xiàn)這個(gè)文件有key
為EnableAutoConfiguration
的鍵值對(duì)
也就是這個(gè)jar
包有自動(dòng)配置類,可以發(fā)現(xiàn)這些自動(dòng)配置配都是以xxxAutoConfiguration
的命名規(guī)則來取名的,這些自動(dòng)配置類包含我了們常用的框架的自動(dòng)配置類,比如aop
、mongo
、redis
和web
等等,基本能滿足我們?nèi)粘i_發(fā)的需求。例如我們程序中需要用到aop,直接引入相應(yīng)的依賴即可!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
我們?nèi)∫粋€(gè)較為簡(jiǎn)單的配置類進(jìn)行分析,看看是怎么發(fā)揮它的配置作用的;我們以HttpEncodingAutoConfiguration
為例;部分代碼如下:
//聲明這個(gè)類為配置類
@Configuration
//開啟ConfigurationProperties功能,同時(shí)將配置文件和HttpProperties.class綁定起來
@EnableConfigurationProperties({HttpProperties.class})
//只有在web應(yīng)用下自動(dòng)配置類才生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//只有存在CharacterEncodingFilter.class情況下 自動(dòng)配置類才生效
@ConditionalOnClass({CharacterEncodingFilter.class})
//判斷配置文件是否存在某個(gè)配置spring.http.encoding,如果存在其值為enabled才生效,如果不存在這個(gè)配置類也生效。
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//將字符編碼過濾器組件添加到 Spring 容器中
@Bean
//僅在該注解規(guī)定的類不存在于 spring容器中時(shí),使用該注解的config或者bean聲明才會(huì)被實(shí)例化到容器中
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
Configuration:這個(gè)注解聲明了這個(gè)類為配置類(和我們平時(shí)寫的配置類一樣,同樣是在類上加這個(gè)注解)。
EnableConfigurationProperties:開啟ConfigurationProperties
功能,也就是將配置文件和HttpProperties.class
這個(gè)類綁定起來,將配置文件的相應(yīng)的值和HttpProperties.class
的變量關(guān)聯(lián)起來,可以點(diǎn)擊HttpProperties.class
進(jìn)去看看,
@ConfigurationProperties(
prefix = "spring.http"
)
public static final Charset DEFAULT_CHARSET;
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
通過ConfigurationProperties
指定前綴,將配置文件application.properties
前綴為spring.http
的值和HttpProperties.class
的變量關(guān)聯(lián)起來,通過類的變量可以發(fā)現(xiàn),我們可以設(shè)置的屬性是charset
、force
、forceRequest
、forceResponse
和mapping
。另外ConfigurationProperties
注解將HttpProperties
類注入到Spring容器成為一個(gè)bean對(duì)象,因?yàn)橐话銇碚f,像springboot默認(rèn)的包掃描路徑為xxxxxxApplication.java
所在包以及其所有子包,但是一些第三方的jar中的bean很明顯不能被掃描到,此時(shí)該注解就派上了用場(chǎng),當(dāng)然,你可能會(huì)說,我使用@ComponentScan
不就行了,這兩個(gè)注解的區(qū)別是:@ComponentScan
前提是你要的bean已經(jīng)存在bean容器中了,而@EnableConfigurationProperties
是要讓容器自動(dòng)去發(fā)現(xiàn)你要類并注冊(cè)成為bean。也就是我們除了使用 Spring Boot 默認(rèn)提供的配置信息之外,我們還可以通過配置文件指定配置信息。
-
ConditionalOnWebApplication:
這個(gè)注解的作用是自動(dòng)配置類在 Web 應(yīng)用中才生效。 -
ConditionalOnClass:
只有在存在CharacterEncodingFilter
這個(gè)類的情況下自動(dòng)配置類才會(huì)生效。 -
ConditionalOnProperty:
判斷配置文件是否存在某個(gè)配置 spring.http.encoding ,如果存在其值為 enabled 才生效,如果不存在這個(gè)配置類也生效。 -
@ConditionalOnMissingBean:
僅在該注解規(guī)定的類不存在于 spring容器中時(shí),使用該注解的config或者bean聲明才會(huì)被實(shí)例化到容器中
可以發(fā)現(xiàn)后面幾個(gè)注解都是ConditionalXXXX
的命名規(guī)則,這些注解是 Spring 制定的條件注解,只有在符合條件的情況下自動(dòng)配置類才會(huì)生效。
接下來的characterEncodingFilter
方法,創(chuàng)建一個(gè)CharacterEncodingFilter
的對(duì)象,也就是字符編碼過濾器,同時(shí)設(shè)置相關(guān)屬性,然后將對(duì)象返回,通過@Bean
注解,將返回的對(duì)象添加到 Spring 容器中。這樣字符編碼過濾器組件配置好了,而平時(shí)的話,我們需要在 web.xml 進(jìn)行如下配置:
<filter>
<filter-name>springUtf8Encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springUtf8Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
到這里原理我們已經(jīng)分析完了,下面我們動(dòng)手自己封裝一個(gè)類似上面的spring-boot-starter-aop
封裝一個(gè)Starter
1,SpringBoot Starter開發(fā)規(guī)范
- 1、命名使用
spring-boot-starter-xxx
,其中xxx
是我們具體的包名稱,如果集成Spring Cloud
則使用spring-cloud-starter-xxx
- 2、通常需要準(zhǔn)備兩個(gè)
jar
文件,其中一個(gè)不包含任何代碼,只用于負(fù)責(zé)引入相關(guān)以來的jar文件,另外一個(gè)則包含核心的代碼
如nacos
與Spring Cloud集成的starter如下圖:
更多Starter
制作規(guī)范,我們可以查看官網(wǎng)文檔
2,Starter開發(fā)步驟
我們創(chuàng)建一個(gè)名字為okay-spring-boot-starter
的工程,并引入相關(guān)依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<!-- 我們是基于Springboot的應(yīng)用 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
因?yàn)槲覀冃枰玫絊pringboot提供的相關(guān)注解,并且使用springboot提供的自動(dòng)配置功能,我們不得不引入spring-boot-autoconfigure
和spring-boot-dependencies
兩個(gè)依賴。
3,創(chuàng)建自動(dòng)配置類
一般來說,我們可能想在springboot啟動(dòng)的時(shí)候就預(yù)先注入自己的一些bean,此時(shí),我們要新建自己的自動(dòng)配置類,一般采用xxxxAutoConfiguration
。這里就類似于上面的HttpEncodingAutoConfiguration
,下面我們模仿HttpEncodingAutoConfiguration
新建一個(gè)OkayStarterAutoConfiguration
配置類;
@Configuration
@EnableConfigurationProperties(OkayProperties.class)
@ConditionalOnClass(Okay.class)
@ConditionalOnWebApplication
public class OkayStarterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
/**
* 當(dāng)存在okay.config.enable=true的配置時(shí),這個(gè)Okay bean才生效
*/
@ConditionalOnProperty(prefix = "okay.config", name = "enable", havingValue = "true")
public Okay defaultStudent(OkayProperties okayProperties) {
Okay okay = new Okay();
okay.setPlatform(okayProperties.getPlatform());
okay.setChannel(okayProperties.getChannel());
okay.setEnable(okayProperties.getEnable());
return okay;
}
}
這里每個(gè)注解的含義上面已經(jīng)解釋過了,這里就不做過多的解釋;
新建一個(gè)OkayProperties
,聲明該starter的使用者可以配置哪些配置項(xiàng)。
@ConfigurationProperties(prefix = "okay.config")
public class OkayProperties {
private String platform;
private String channel;
private Boolean enable;
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
@Override
public String toString() {
return "OkayProperties{" +
"platform='" + platform + '\'' +
", channel='" + channel + '\'' +
", enable=" + enable +
'}';
}
}
在resources
目錄下新建一個(gè)META-INF
目錄并且創(chuàng)建一個(gè)spring.factories
文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.haoxiaoyong.okay.starter.config.OkayStarterAutoConfiguration
到這里是不是和上面我們講解的源碼基本一致!
使用我們自己的Starter
新創(chuàng)建一個(gè)springboot工程,引入我們自己maven依賴:
<dependency>
<groupId>cn.haoxiaoyong.okay</groupId>
<artifactId>okay-spring-boot-starter</artifactId>
<version>0.0.2-SNAPSHO</version>
</dependency>
并在配置文件appliaction.yml中配置
你看多智能還會(huì)自動(dòng)提示!
okay:
config:
platform: pdd
channel: ws
enable: true
@RestController
@Slf4j
public class OkController {
@Autowired
Okay okay;
@RequestMapping("okay")
public String testOkay() {
log.info(okay.getChannel() + " " + okay.getPlatform() + " " + okay.getEnable());
return okay.getChannel() + " " + okay.getPlatform() + " " + okay.getEnable();
}
}
瀏覽器輸入:localhost:8082/okay,控制臺(tái)打印:
這個(gè)例子只是展示一下邏輯效果,這篇使用自定義Starter 并制作一個(gè)簡(jiǎn)單的圖床
示例地址:https://github.com/haoxiaoyong1014/springboot-examples/tree/master/okay-spring-boot-starter