SpringBoot自动装配与自定义starter

SpringBoot的核心思想是约定优于配置,它简化了之前使用SpringMVC时候的大量配置xml,使得开发者能够快速的创建一个Web项目。那么SpringBoot是如何做到的呢?

@SpringBootApplication

当我们创建一个SpringBoot项目完成后,会有一个启动类,直接就可以运行web项目了。所以我们首先从这个启动类的注解上出发,看看SpringBoot是如何实现的。

@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这个注解就是比较核心的了,实现自动装配就是依赖这个注解,下面我们一步一步来看是如何实现的。

@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),这两个注解的作用都是根据条件动态的加载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 {

    //重写这个方法,根据AnnotationMetadata将bean注册到spring容器中
    //这里的AnnotationMetadata就是@SpringBootApplication注解的元数据
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      //new PackageImport(metadata).getPackageName()返回的是SpringBootApplication注解对应的包名
      //也就是启动类所在的包名,所以,SpringBoot项目的启动类和包名是有一定的要求的,这也是SpringBoot约定大约配置
      //的一种体现
            register(registry, new PackageImport(metadata).getPackageName());
        }

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

    }
AutoConfigurationImportSelector

下面要分析的这个类就是整个自动装配最关键的类了。查看源码可以知道,AutoConfigurationImportSelector实现了ImportSelector接口,ImportSelector接口中的selectImports方法会根据返回的String[]数组,然后Spring根据数组中的类的全路径类名把响应的类注入到Spring容器中。接着我们来看一下返回了哪些类。

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
    //加载元数据,这里面主要是一些Condition条件,目的是为了根据条件判断是否需要注入某个类
        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;
        }
    //根据注解获取注解的属性
        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);
    //根据上面loadMetadata方法加载的condition条件信息,过滤掉不符合条件的类
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

注意:这里getCandidateConfigurations方法是重点,SpringBoot中依赖的所有的starter都是基于此实现自动装配的。这里用到了SPI

SPI全称为Service Provier Interface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。

SpringBoot的各种starter依赖都是基于此实现的,每个maven依赖的starter的包下都会有一个META-INF/spring.factories配置文件,里面都会有org.springframework.boot.autoconfigure.EnableAutoConfiguration键和对应的需要自动加载的类的全限定名。

实现一个Starter

根据上面的分析,下面简单实现一个starter

IDEA创建一个简单的Maven项目,然后创建相应的包名和类。

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的方法,它有两个实现,StringFormatProcessor直接返回传入对象的toStringJsonFormatProcessor根据用fastjson将传入的对象转成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就完成了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容