Spring-boot自动装配

Spring-boot是基于Spring的一个框架,是Spring Cloud生态的基础,学习Spring应该从这个基础学习,向上学习Spring Cloud生态,向下学习Spring基础。

Spring-boot本身是基于SpringFramework,在SpringFramework上开发出Spring的自动装配框架。正所谓原理下面有原理,在学习Spring-boot之前,最好是把SpringFramework的基础学习一下。

Spring boot自动装配

Spring boot的底层本质还是Spring IOC,为了加载bean, Spring boot构造了一种机制,只要按照该积木方式,可以非常容易的把各种组件很容易的引入组装起来,是一种很精妙的构思。

要理解Spring boot的自动装配,就要先理解SpringFramework手动装配。

模式注解

SpringFramework中定义了若干模式注解,Configuration,相关的模式注解有@Service @Repository @Conponent,@Configuration,@Controller。

这些模式注解中@Configuration是一级元注解,其它四个注解继承于@Configuration,是二级元注解,相当于给予了特定场景下的语义,例如说@Repository用于数据库装载,@Service用于服务装载,@Controller即MVC中的Controller。

例如说有下面这个HellowordConfiguration

@Configuration
class HellowordConfiguration {
    @Bean
    public String SayHello() {
        return "hello"
    }
}
模式注解的装载

怎么加载模式注解呢,可以使用XML配置、API接口编程、注解方式。XML配置和API接口暂且不谈,不适合Spring boot自动装配的场景,使用注解方式有两种方式:

  • 加载制定目录下的所有模式注解,使用@ComponentScan注解,使用该注解可以加载指定目录下所有的模式注解。

  • 加载指定的模式注解,使用@Import注解
    Import注解定义如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

使用Import注解有两种方式,例如对于HellowordConfiguration,一种是直接Import

@Import(HellowordConfiguration.class)

一种是先定义selector,然后使用Import该selector

public class HelloWorldImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{HelloWorldConfiguration.class.getName()};
    }
}

@Import(HelloWorldImportSelector.class)

selector的方式更加灵活一点,对于要加载多个Configuration,一个类就搞定了,否则就要写多个注解。

Enable注解

Enable注解可以理解程Enable模式,意思就是一种开关,通过开关来控制模式注解加载。
Eanble注解在Spring中使用广泛,例如:

  • SpringFramework中,@EnableWebMVC、@EnableTransactionManagement、@EnableCaching、@EnableAsync、@EnableWebFlux、@EnableAspectJAutoProxy
  • Spring boot中,@EnableAutoConfiguration、@EnableManagementContext、@EnableConfigurationProperties
  • Spring Cloud中,@EnbleEurekaServer、@EnableEurekaClient、@EnableConfigServer、@EnableFeignClients、@EnableZuulProxy、@EnableCircuitBreaker

Spring-boot中使用的是@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 {};
}

其中@AutoConfigurationPackage注解,定义加载@Import({Registrar.class}),会记载主类以及主类目录子目录下的模式注解。

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

然后再看 @Import({AutoConfigurationImportSelector.class}),这个就是一个典型的selector的使用。

  public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            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 StringUtils.toStringArray(configurations);
        }
    }
    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;
    }

使用了@EnableAutoConfiguration注解后,看上面的代码,会按照预定扫描所有加载jar包下面的“META-INF/spring.factories”文件,读取该文件里面的自定义的AutoConfiguration配置,这就是一种约定编程。

关于自定义的AutoConfiguration下面再说。

条件装配

条件装配有两种,一种是使用在Bean上面的@Profile注解,一种是@Conditional注解。
@Profile注解不是Spring-boot场景选择(实际上@Profile也是从@Conditional继承的),这里先不讲,重点是Conditional类的注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * All {@link Condition}s that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();

}

这是@Conditonal注解的定义,然后我们看

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {

    /**
     * Java 系统属性名称
     * @return
     */
    String name();

    /**
     * Java 系统属性值
     * @return
     */
    String value();
}

@ConditionalOnSystemProperty注解,这个注解在系统属性中,如果存在某些参数,就允许条件通过。
另外看下面这个注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class<?>[] value() default {};

    String[] name() default {};
}

这个注解是如果存在某些类,就允许条件通过。当我们在pom文件中加载上某些类包后,starter中的条件才会通过,才会记载相应的bean。

上面两个条件是各种starter中最常用的条件注解。

springboot的自动装配

有了上面模式注解手动装配、Eanble模式注解、条件注解后,Spring-boot的自动装配就呼之欲出了,实际上在上面已经讲过了。

首先我们要写自己的自动装配类,包括

  • 自定义的模式注解,HellowordConfigration
  • 自定义的Enable模式注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class)
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
  • 自定义的AutoConfiguration
@Configuration // Spring 模式注解装配
@EnableHelloWorld // Spring @Enable 模块装配
@ConditionalOnSystemProperty(name = "user.name", value = "Mercy") // 条件装配
public class HelloWorldAutoConfiguration {
}

注意,如上都是自定义部分,这些自定义的如何工作,需要首先在约定编程中META-INFO/spring.factory中增加配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.imooc.diveinspringboot.configuration.HelloWorldAutoConfiguration

然后在主类上加上注解@SpringBootApplication

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 {};
}

这个注解上就加载了@EnableAutoConfiguration,如上Enable注解中讲述的,加载了这个注解,spring-boot启动时会扫描所有jar包下的META-INFO/spring.factories, 把刚才的HelloWorldAutoConfiguration返回,这个AutoConfiguration的加载就完成了对自定义Configuration的加载。

所以这里要区分一下,一个自动装配的完成分为:

  • 系统级注解,@SpringBootApplication, @EnableAutoConfiguration的注解,启动扫描META-INFO/spring.factories
  • 自定义注解,如上HelloWorldAutoConfiguration,配置在META-INFO/spring.factories中
    这里有点像自举型的编程语言一样,都是用了Eanble注解定义,但是EnableAutoConfiguration用来定义系统行为,自定义的AutoConfiguration被EnableAutoConfiguration加载。

小结

Spring-boot是基于Spring的框架,基于这个框架,开发人员可以自己二次开发starter,使用Spring-boot框架和starter,开发人员就可以非常容易的组装出服务,摆脱Spring的繁杂构造。
Spring-Cloud基于Spring-boot,构造了大量的Starter,可以非常方便的开箱即用,形成了一片生态。
Spring-boot框架本身让人感觉到优美,是量体裁衣良好设计,大大降低开发人员的开发难度。

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

推荐阅读更多精彩内容