從SpringBoot源碼到自己封裝一個(gè)Starter

這篇博客主要講述一下springboot怎么給我們簡(jiǎn)化了大量的配置,然后跟著源碼自己封裝一個(gè)Starter,首先我們需要從兩個(gè)地方來(lái)說(shuō),第一就是springboot的起步依賴(lài),第二就是springboot自動(dòng)裝配;

起步依賴(lài)

我們?cè)趧?chuàng)建一個(gè)springboot工程時(shí)需要引入spring-boot-starter-web這個(gè)依賴(lài);

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

這個(gè)依賴(lài)我們點(diǎn)進(jìn)去可以看到其實(shí)這個(gè)起步依賴(lài)集成了常用的web依賴(lài),例如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的起步依賴(lài)說(shuō)白了就是對(duì)常用的依賴(lài)進(jìn)行再一次封裝,方便我們引入,簡(jiǎn)化了 pom.xml 配置,但是更重要的是將依賴(lài)的管理交給了 Spring Boot,我們無(wú)需關(guān)注不同的依賴(lài)的不同版本是否存在沖突的問(wèn)題,Spring Boot 都幫我們考慮好了,我們拿來(lái)用即可!

在使用 Spring Boot 的起步依賴(lài)之前,我們需要在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里面定義了常用的框架的依賴(lài)以及相應(yīng)的版本號(hào),我們無(wú)需擔(dān)心版本沖突問(wèn)題;

自動(dòng)裝配

首先我們知道springboot啟動(dòng)需要一個(gè)啟動(dòng)引導(dǎo)類(lèi),這個(gè)類(lèi)除了是應(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è)類(lèi)為配置類(lèi)的作用,而后者起到開(kāi)啟自動(dòng)掃描組件的作用。

我們重點(diǎn)分析一下@EnableAutoConfiguration這個(gè)注解,這個(gè)注解的作用就是開(kāi)啟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類(lèi);

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文件keyEnableAutoConfiguration的所有值取出,然后這些值其實(shí)是類(lèi)的全限定名,也就是自動(dòng)配置類(lèi)的全限定名,然后 Spring Boot 通過(guò)這些全限定名進(jìn)行類(lèi)加載(反射),將這些自動(dòng)配置類(lèi)添加到 Spring 容器中。

我們找到一個(gè)名為spring-boot-autoconfigure-2.1.4.RELEASE.jar的 jar 包,打開(kāi)它的spring.factories文件,發(fā)現(xiàn)這個(gè)文件有keyEnableAutoConfiguration的鍵值對(duì)

image

也就是這個(gè)jar包有自動(dòng)配置類(lèi),可以發(fā)現(xiàn)這些自動(dòng)配置配都是以xxxAutoConfiguration的命名規(guī)則來(lái)取名的,這些自動(dòng)配置類(lèi)包含我了們常用的框架的自動(dòng)配置類(lèi),比如aopmongo、redisweb等等,基本能滿(mǎn)足我們?nèi)粘i_(kāi)發(fā)的需求。例如我們程序中需要用到aop,直接引入相應(yīng)的依賴(lài)即可!

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

我們?nèi)∫粋€(gè)較為簡(jiǎn)單的配置類(lèi)進(jìn)行分析,看看是怎么發(fā)揮它的配置作用的;我們以HttpEncodingAutoConfiguration為例;部分代碼如下:

//聲明這個(gè)類(lèi)為配置類(lèi)
@Configuration 
//開(kāi)啟ConfigurationProperties功能,同時(shí)將配置文件和HttpProperties.class綁定起來(lái)
@EnableConfigurationProperties({HttpProperties.class})
//只有在web應(yīng)用下自動(dòng)配置類(lèi)才生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
//只有存在CharacterEncodingFilter.class情況下 自動(dòng)配置類(lèi)才生效
@ConditionalOnClass({CharacterEncodingFilter.class})
//判斷配置文件是否存在某個(gè)配置spring.http.encoding,如果存在其值為enabled才生效,如果不存在這個(gè)配置類(lèi)也生效。
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    private final Encoding properties;

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

    //將字符編碼過(guò)濾器組件添加到 Spring 容器中
    @Bean
    //僅在該注解規(guī)定的類(lèi)不存在于 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è)類(lèi)為配置類(lèi)(和我們平時(shí)寫(xiě)的配置類(lèi)一樣,同樣是在類(lèi)上加這個(gè)注解)。

EnableConfigurationProperties:開(kāi)啟ConfigurationProperties功能,也就是將配置文件和HttpProperties.class這個(gè)類(lèi)綁定起來(lái),將配置文件的相應(yīng)的值和HttpProperties.class的變量關(guān)聯(lián)起來(lái),可以點(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;

通過(guò)ConfigurationProperties指定前綴,將配置文件application.properties前綴為spring.http的值和HttpProperties.class的變量關(guān)聯(lián)起來(lái),通過(guò)類(lèi)的變量可以發(fā)現(xiàn),我們可以設(shè)置的屬性是charset、force、forceRequestforceResponsemapping。另外ConfigurationProperties注解將HttpProperties類(lèi)注入到Spring容器成為一個(gè)bean對(duì)象,因?yàn)橐话銇?lái)說(shuō),像springboot默認(rèn)的包掃描路徑為xxxxxxApplication.java所在包以及其所有子包,但是一些第三方的jar中的bean很明顯不能被掃描到,此時(shí)該注解就派上了用場(chǎng),當(dāng)然,你可能會(huì)說(shuō),我使用@ComponentScan不就行了,這兩個(gè)注解的區(qū)別是:@ComponentScan前提是你要的bean已經(jīng)存在bean容器中了,而@EnableConfigurationProperties是要讓容器自動(dòng)去發(fā)現(xiàn)你要類(lèi)并注冊(cè)成為bean。也就是我們除了使用 Spring Boot 默認(rèn)提供的配置信息之外,我們還可以通過(guò)配置文件指定配置信息。

  • ConditionalOnWebApplication:這個(gè)注解的作用是自動(dòng)配置類(lèi)在 Web 應(yīng)用中才生效。
  • ConditionalOnClass:只有在存在CharacterEncodingFilter這個(gè)類(lèi)的情況下自動(dòng)配置類(lèi)才會(huì)生效。
  • ConditionalOnProperty:判斷配置文件是否存在某個(gè)配置 spring.http.encoding ,如果存在其值為 enabled 才生效,如果不存在這個(gè)配置類(lèi)也生效。
  • @ConditionalOnMissingBean:僅在該注解規(guī)定的類(lèi)不存在于 spring容器中時(shí),使用該注解的config或者bean聲明才會(huì)被實(shí)例化到容器中

可以發(fā)現(xiàn)后面幾個(gè)注解都是ConditionalXXXX的命名規(guī)則,這些注解是 Spring 制定的條件注解,只有在符合條件的情況下自動(dòng)配置類(lèi)才會(huì)生效。

接下來(lái)的characterEncodingFilter方法,創(chuàng)建一個(gè)CharacterEncodingFilter的對(duì)象,也就是字符編碼過(guò)濾器,同時(shí)設(shè)置相關(guān)屬性,然后將對(duì)象返回,通過(guò)@Bean注解,將返回的對(duì)象添加到 Spring 容器中。這樣字符編碼過(guò)濾器組件配置好了,而平時(shí)的話(huà),我們需要在 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è)類(lèi)似上面的spring-boot-starter-aop

封裝一個(gè)Starter

1,SpringBoot Starter開(kāi)發(fā)規(guī)范

  • 1、命名使用spring-boot-starter-xxx,其中xxx是我們具體的包名稱(chēng),如果集成Spring Cloud則使用spring-cloud-starter-xxx
  • 2、通常需要準(zhǔn)備兩個(gè)jar文件,其中一個(gè)不包含任何代碼,只用于負(fù)責(zé)引入相關(guān)以來(lái)的jar文件,另外一個(gè)則包含核心的代碼

nacos與Spring Cloud集成的starter如下圖:

更多Starter制作規(guī)范,我們可以查看官網(wǎng)文檔

2,Starter開(kāi)發(fā)步驟

我們創(chuàng)建一個(gè)名字為okay-spring-boot-starter的工程,并引入相關(guān)依賴(lài):

    <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-autoconfigurespring-boot-dependencies兩個(gè)依賴(lài)。

3,創(chuàng)建自動(dòng)配置類(lèi)

一般來(lái)說(shuō),我們可能想在springboot啟動(dòng)的時(shí)候就預(yù)先注入自己的一些bean,此時(shí),我們要新建自己的自動(dòng)配置類(lèi),一般采用xxxxAutoConfiguration。這里就類(lèi)似于上面的HttpEncodingAutoConfiguration,下面我們模仿HttpEncodingAutoConfiguration新建一個(gè)OkayStarterAutoConfiguration配置類(lèi);

@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)解釋過(guò)了,這里就不做過(guò)多的解釋?zhuān)?/p>

新建一個(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依賴(lài):

    <dependency>
        <groupId>cn.haoxiaoyong.okay</groupId>
        <artifactId>okay-spring-boot-starter</artifactId>
        <version>0.0.2-SNAPSHO</version>
    </dependency>

并在配置文件appliaction.yml中配置

image

你看多智能還會(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)打?。?/p>

image

這個(gè)例子只是展示一下邏輯效果,這篇使用自定義Starter 并制作一個(gè)簡(jiǎn)單的圖床

示例地址:https://github.com/haoxiaoyong1014/springboot-examples/tree/master/okay-spring-boot-starter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容