Spring boot運行原理-自定義自動配置類


在前面SpringBoot的文章中介紹了SpringBoot的基本配置,今天我們將給大家講一講SpringBoot的運行原理,然后根據原理我們自定義一個starter pom。
本章對於后續繼續學習SpringBoot至關重要,了解SpringBoot運行原理對於我們深入學習SpringBoot有着非常重要的作用。

SpringBoot的自動配置從何而來

要想了解SpringBoot的自動配置,我們可以在源碼看到相關代碼和配置。
SpringBoot關於自動配置的源碼在spring-boot-autoconfigure-2.1.x.jar內,打開maven依賴我們可以看見

image

如果想了解SpringBoot為我們做了哪些自動配置,可以通過下面方式查看當前項目中已啟用和未啟用的自動配置的報告。
  1. 運行jar時增加--debug參數:
java -jar xx.jar --debug
  1. 在application.properties中設置屬性:
debug=true
啟動時,通過控制台我們可以看到哪些配置已使用自動配置,哪些配置沒有自動配置。

已啟用自動配置
image

未啟用自動配置
image

仔細看上圖我們可以發現,相關如

@ConditionalOnClass found required class ...    \   @ConditionalOnClass did not find required class ...

的字眼非常多,可見@ConditionalOnClass注解可能在自動配置中起着主要作用,那究竟是如何起作用的呢?

運行原理

關於SpringBoot的運作原理,我們還是回歸到@SpringBootApplication注解上來,這個注解是一個組合注解,它的核心功能是一個開啟自動配置注解@EnableAutoConfiguration

下面我們來看下@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 {};
}

這里我們重點關注@Import的導入功能,AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法來掃描META-INF/spring.factories文件中描述的jar包,所以我們立馬到剛剛打開的自動配置類中的META-INF/spring.factories中找一下是否真的有這樣一個文件。

好家伙,還真的有
image

馬上打開看一看
image

核心注解

我們打開上面配置的任何其中一個注解,一般都有下面的條件注解,打開源碼spring-boot-autoconfigure下的org/springframework/boot/condition看看都有哪些注解

image

簡單介紹一下每個注解代表代表的條件:
@ConditionalOnBean: 當容器里有指定的Bean條件下。
@ConditionalOnClass: 當類路徑下有指定的類的條件下。
@ConditionalOnExpression: 基於SpEL表達式作為判斷條件。
@ConditionalOnJava: 基於JVM版本作為判斷條件。
@ConditionalOnjndi: 在基於JNDI存在的條件下查找指定的位置。
@ConditionalOnMissingBean: 當容器里沒有Bean的情況下。
@ConditionalOnMIssingClass: 當類路徑下沒有指定的類的條件下。
@ConditionalOnNotWebApplication: 當前項目不是Web項目的條件下。
@ConditionalOnProperty: 指定的屬性是否有指定的值。
@ConditionalOnResource: 類路徑是否有指定的值。
@ConditionalOnSingleCandidate: 當指定Bean在容器中只有一個,或者雖然有多個但是指定首選的Bean
@ConditionalOnWebApplication: 當前項目是web項目的條件下
這些注解都是組合了@Conditional元注解,只是使用了不同的條件(Condition)
下面我們簡單分析一下@ConditionalOnWebApplication注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
    ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;

    public static enum Type {
        ANY,
        SERVLET,
        REACTIVE;

        private Type() {
        }
    }
}

看看OnWebApplicationCondition.class 是如何定義條件的

這里我們主要看isWebApplication方法,判斷條件主要如下:
(1)GenericWebApplicationContext是否在類路徑中;
(2)容器中是否有名為session的scope;
(3)當前容器的Environment是否為ConfigurableWebEnvironment
(4)當前的ResourceLoader是否為WebApplicationContext(ResourceLoader是ApplicationContext的頂級接口之一);
(5)構造ConditionOutcom類的isMatch方法返回布爾值來確定條件。

這里的isWebApplication()方法,spring-boot-1.x和2.x有區別,在1.x中只判斷了servlet,而在2.x增加了了reative和any的webapplication判斷

簡單示例

在以往的web項目中,通常需要在web.xml中配置一個filter,對請求進行編碼,如下所示:
<filter>
		<filter-name>encodingFilter</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>
因此如果要實現自動配置的話,需要滿足一下條件:
(1)能配置CharacterEncodingFilter這個Bean;
(2)能配置encoding和forceEncoding這兩個參數。

參數配置

在上一章我們講到了類型安全的配置,Spring Boot的自動配置也是基於這一點實現的,這里的配置可以在application.properties中直接配置。
源碼如下圖:

image

image

代碼解釋:

(1)在application.properties配置的前綴是spring.http.encoding;
(2)默認編碼方式為UTF-8,若修改可配置spring.http.encoding.charset=編碼;
(3)設置force,默認為true,若修改可配置spring.http.encoding.force=false;

配置Bean

上面我們已經配置好相關參數,現在根據條件配置CharacterEncodingFilter的Bean,下面看看源碼:

@Configuration
@EnableConfigurationProperties({HttpProperties.class}) //1
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})//2
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)//3
public class HttpEncodingAutoConfiguration {
    private final Encoding properties;

    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    
    @Bean//4
    @ConditionalOnMissingBean//5
    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;
    }

代碼解釋:
(1)開啟屬性注入,通過@EnableCondigurationProperties聲明
(2)當CharacterEncodingFilter在類路徑的條件下;
(3)當設置spring.http.encoding.enabled的情況下,如果沒有設置則默認為true,即條件符合;
(4)像使用Java配置的方式配置CharacterEncoding這個Bean;
(5)當容器中沒有這個Bean的時候新建Bean

實戰-自定義一個starter pom

前面我們已經詳細講述了spring boot是如何實現自動化配置的,現在我們來動手自己寫一個starter pom實現自動化配置。

要求:當某個類存在的時候,自動配置這個類的Bean,並可將Bean的屬性在application.properties中配置

創建一個普通MAVEN工程

創建一個普通MAVEN工程,並加入spring boot自動配置依賴

image

屬性配置

我們仿照HttpProperties來配置,我們自定義starter 的配置文件
@ConfigurationProperties(prefix = "xicent.service")
public class MyServiceProperties {
    private MyServiceProperties.MyProperties myProperties = new MyServiceProperties.MyProperties();

    public MyProperties getMyProperties() {
        return myProperties;
    }

    public void setMyProperties(MyProperties myProperties) {
        this.myProperties = myProperties;
    }

    public static class MyProperties{
        public static final String DEFAULT_NAME;
        private String author;
        private String age;
        
        static {
            DEFAULT_NAME = "wjx";
        }
        
    省略 get/set..

使用類型安全的方式獲取屬性。author如果不設置,會給默認值。

判斷依據類

public class MyService {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

這個類作為我們的判斷依據類,如果存在,則創建這個類的Bean。

自動配置類(關鍵)


@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "xicent.service",value = "enabled",matchIfMissing = true)
public class MyServiceAutoConfiguration {
    private MyServiceProperties.MyProperties properties;

    public MyServiceAutoConfiguration(MyServiceProperties properties) {
        this.properties = properties.getMyProperties();
    }
    
    @Bean
    @ConditionalOnMissingBean(MyService.class)
    public MyService myService(){
        MyService myService = new MyService();
        myService.setName(properties.getAuthor());
        myService.setAge(properties.getAge());
        
        return myService;
    }
}

這里我們仿照了HttpEncodingAutoConfiguration的寫法,其實MyServiceProperties也可以直接用@Autowired直接注入的。
@ConditionalOnClass判斷MyService這個類是否在類路徑中存在,並且容器中沒有這個Bean的情況下,我們對這個Bean進行自動配置。

注冊配置

在前面我們也帶大家看過,每個自動配置的包中,在src/main/resources/META-INF下都會有一個spring.factories配置文件。
下面我們就將我們剛剛寫好的自動配置類,在這個配置文件中進行注冊。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.xicent.starter.config.MyServiceAutoConfiguration
如果有多個自動配置,用","隔開,此處的"\"是為了換行后仍能讀到屬性。

添加在倉庫

如果是公司提供給其他項目使用,則可以直接上傳到公司私服。這里為了方便測試,我們就打包到本地倉庫。
直接點擊idea->maven project->lifecycle->install

image

新建Spring Boot項目加入依賴

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.xicent</groupId>
			<artifactId>mystarter</artifactId>
			<version>1.0.0</version>
		</dependency>
	</dependencies>

mystarter是我們剛剛手動寫好的自動配置,加入web是為了待會用接口訪問,方便測試。

添加配置

剛剛我們的自動配置中允許配置兩個參數,其中author如果不配置,提供默認值。
xicent.service.my-properties.age=23
xicent.service.my-properties.author=kris

注入依賴

@SpringBootApplication
@RestController
public class TeststarterApplication {

	@Autowired
	MyService myService;

	@GetMapping("/")
	String testStarter(){
		return myService.getName()+":"+myService.getAge();
	}

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

}

訪問接口

image

如果不配置author
image

到這里,我們自定義的starter pom就大功告成啦~ 是不是感覺其實挺簡單的,Spring Boot自動配置的神秘面紗也就被我們悄悄揭開了。

如果您覺得有用記得分享喔~
公眾號搜索:喜訊XiCent 獲取更多福利~


公眾號搜索:喜訊XiCent 獲取更多福利資源~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM