手擼一個自定義starter,它不香嗎

前言

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 也很簡單不是嗎?那自己擼一個,不香嗎?

兄弟們看到這了,不點贊無所謂(表面平靜,內心期待的我)
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章