SpringBoot自動裝配與自定義starter

SpringBoot的核心思想是約定優(yōu)于配置,它簡化了之前使用SpringMVC時候的大量配置xml,使得開發(fā)者能夠快速的創(chuàng)建一個Web項目。那么SpringBoot是如何做到的呢?

@SpringBootApplication

當我們創(chuàng)建一個SpringBoot項目完成后,會有一個啟動類,直接就可以運行web項目了。所以我們首先從這個啟動類的注解上出發(fā),看看SpringBoot是如何實現(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
    )
  //...
}

可以看到@SpringBootApplication主要是三個注解的復合注解。

@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

這個注解最簡單,它是對@Configuration的封裝,@Configuration我們最熟悉不過了,這里就不做分析了。

@ComponentScan

這個注解的作用主要是掃描定義的包下的所有的包含@Controller、@Service@Component、@Repository等注解的類,把他們注冊到Spring的容器中。具體是如何掃描,如何加載注解信息、如何生成Bean以及如何注冊到Spring容器中,這里的邏輯相對來說比較復雜,不是本文的重點,不具體分析了。

@EnableAutoConfiguration

EnableAutoConfiguration這個注解就是比較核心的了,實現(xiàn)自動裝配就是依賴這個注解,下面我們一步一步來看是如何實現(xià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 {};
}

可以看到EnableAutoConfiguration注解中,主要依賴兩個注解,AutoConfigurationPackageImport(AutoConfigurationImportSelector.class),這兩個注解的作用都是根據(jù)條件動態(tài)的加載BeanSpring容器中,下面具體分析。

@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

這里主要是@Import(AutoConfigurationPackages.Registrar.class)注解,Import注解一定很熟悉了,主要是將import的類注入Spring容器中,下面具體分析AutoConfigurationPackages.Registrar。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //重寫這個方法,根據(jù)AnnotationMetadata將bean注冊到spring容器中
    //這里的AnnotationMetadata就是@SpringBootApplication注解的元數(shù)據(jù)
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      //new PackageImport(metadata).getPackageName()返回的是SpringBootApplication注解對應的包名
      //也就是啟動類所在的包名,所以,SpringBoot項目的啟動類和包名是有一定的要求的,這也是SpringBoot約定大約配置
      //的一種體現(xiàn)
            register(registry, new PackageImport(metadata).getPackageName());
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }

    }
AutoConfigurationImportSelector

下面要分析的這個類就是整個自動裝配最關(guān)鍵的類了。查看源碼可以知道,AutoConfigurationImportSelector實現(xiàn)了ImportSelector接口,ImportSelector接口中的selectImports方法會根據(jù)返回的String[]數(shù)組,然后Spring根據(jù)數(shù)組中的類的全路徑類名把響應的類注入到Spring容器中。接著我們來看一下返回了哪些類。

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
    //加載元數(shù)據(jù),這里面主要是一些Condition條件,目的是為了根據(jù)條件判斷是否需要注入某個類
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
    //加載所有自動裝載的元素
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
    //根據(jù)注解獲取注解的屬性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //使用SpringFactoryLoader加載classpath下所有的META-INF/spring.factories中,key是           
    //org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //刪除重復的類
        configurations = removeDuplicates(configurations);
    //刪除被排除的類
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
    //根據(jù)上面loadMetadata方法加載的condition條件信息,過濾掉不符合條件的類
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

注意:這里getCandidateConfigurations方法是重點,SpringBoot中依賴的所有的starter都是基于此實現(xiàn)自動裝配的。這里用到了SPI。

SPI全稱為Service Provier Interface,是一種服務發(fā)現(xiàn)機制。SPI的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。

SpringBoot的各種starter依賴都是基于此實現(xiàn)的,每個maven依賴的starter的包下都會有一個META-INF/spring.factories配置文件,里面都會有org.springframework.boot.autoconfigure.EnableAutoConfiguration鍵和對應的需要自動加載的類的全限定名。

實現(xiàn)一個Starter

根據(jù)上面的分析,下面簡單實現(xiàn)一個starter。

IDEA創(chuàng)建一個簡單的Maven項目,然后創(chuàng)建相應的包名和類。

image.png

簡單說明一下這個starter中的作用

  • FormatTemplate類提供一個模版方法doFormat,可以將傳入的泛型對象輸出一個字符串
public class FormatTemplate {
    private FormatProcessor formatProcessor;

    public FormatTemplate(FormatProcessor formatProcessor) {
        this.formatProcessor = formatProcessor;
    }
    public <T>String doFormat(T data) {
        return formatProcessor.format(data);
    }
}
  • FormatProcessor是一個接口,提供了一個format的方法,它有兩個實現(xiàn),StringFormatProcessor直接返回傳入對象的toString,JsonFormatProcessor根據(jù)用fastjson將傳入的對象轉(zhuǎn)成json字符串。
public class JsonFormatProcessor implements FormatProcessor {
    @Override
    public <T> String format(T data) {
        return JSON.toJSONString(data);
    }
}
public class StringFormatProcessor implements FormatProcessor {
    @Override
    public <T> String format(T data) {
        return data.toString();
    }
}
  • FormatAutoConfiguration利用@Configuration分別將JsonFormatProcessor和StringFormatProcessor注入到spring容器中,這里用了@Condition條件注解,只有當項目中引用了fastjson的時候,才會注入JsonFormatProcessor
@Configuration
public class FormatAutoConfiguration {

    @Bean
    @Primary
    @ConditionalOnClass(name = "com.alibaba.fastjson.JSON")
    public FormatProcessor jsonFormat(){
        return new JsonFormatProcessor();
    }

    @Bean
    @ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
    public FormatProcessor stringFormat(){
        return new StringFormatProcessor();
    }

}
  • TemplateAutoConfiguration引用FormatAutoConfiguration并注入了一個FormatTemplate
@Configuration
@Import(FormatAutoConfiguration.class)
public class TemplateAutoConfiguration {

    @Bean
    public FormatTemplate formatTemplate(FormatProcessor formatProcessor) {
        return new FormatTemplate(formatProcessor);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(TemplateAutoConfiguration.class);
        FormatTemplate bean = context.getBean(FormatTemplate.class);
        FormatProcessor formatProcessor = context.getBean(FormatProcessor.class);
        System.out.printf(bean.doFormat("aaa"));
        System.out.println(formatProcessor.format("bbb"));
    }
}
  • resources/META-INF下的spring.factories中定義了要自動裝配的類的全路徑,即TemplateAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cross.springbootdemo.autoconfiguration.TemplateAutoConfiguration

編寫好后,將項目進行打包,然后在其他項目中,就可以引入了,使用的時候直接可以用@Autowired引入FormatTemplate了。

@RestController
public class TestController {

    @Autowired
    private FormatTemplate formatTemplate;

    @GetMapping(value = "test")
    public String test() {
        User user = new User();
        user.setName("crossyf---");
        user.setAge(18);
        return formatTemplate.doFormat(user);
    }
}

一個簡單的starter就完成了。

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

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

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