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
啓動時,經過控制檯咱們能夠看到哪些配置已使用自動配置,哪些配置沒有自動配置。

已啓用自動配置
imagejava

未啓用自動配置
imageweb

仔細看上圖咱們能夠發現,相關如spring

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

的字眼很是多,可見@ConditionalOnClass註解可能在自動配置中起着主要做用,那到底是如何起做用的呢?segmentfault

運行原理

關於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中找一下是否真的有這樣一個文件。安全

好傢伙,還真的有
imagesession

立刻打開看一看
imageapp

核心註解

咱們打開上面配置的任何其中一個註解,通常都有下面的條件註解,打開源碼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判斷==webapp

簡單示例

在以往的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;maven

配置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
imageide

到這裏,咱們自定義的starter pom就大功告成啦~ 是否是感受其實挺簡單的,Spring Boot自動配置的神祕面紗也就被咱們悄悄揭開了。

若是您以爲有用記得分享喔~公衆號搜索:喜訊XiCent 獲取更多福利~

相關文章
相關標籤/搜索