AI智能
改变未来

Spring Cloud系列之Commons – 1. 背景与基础知识准备


本文基于 Spring Cloud 2020.0 发布版的依赖

本系列会深入分析 Spring Cloud 的每一个组件,从

Spring Cloud Commons

这个 Spring Cloud 所有元素的抽象说起,深入设计思路与源码,并结合实际使用例子深入理解。本系列适合有一定 Spring 或者 Spring Boot 使用经验的人阅读。

什么是

Spring Cloud Commons

Spring Cloud框架包括如下功能:

  • 分布式多版本配置管理
  • 服务注册与发现
  • 路由
  • 微服务调用
  • 负载均衡
  • 断路器
  • 分布式消息

Spring Cloud Commons包含实现这一切要加载的基础组件的接口,以及Spring Cloud启动如何加载,加载哪些东西。其中:

  • spring cloud context:包括Spring Cloud应用需要加载的
    ApplicationContext

    的内容

  • spring cloud common: 包括如下几个基本组件以及其加载配置:服务注册接口:
    org.springframework.cloud.serviceregistry

  • 服务发现接口:
    org.springframework.cloud.discovery

  • 负载均衡接口:
    org.springframework.cloud.loadbalancer

  • 断路器接口:
    org.springframework.cloud.circuitbreaker

  • spring cloud loadbalancer:类似于ribbon,并且是ribbon的替代品。实现了上述负载均衡接口的组件
  • 这个系列我们要讲述的是 spring cloud common 这个模块,spring cloud loadbalancer 还有 spring cloud context 将会在另一个单独的系列。

    Spring 与 Spring Boot 背景知识补充

    我们在看一个 Spring Cloud 模块源代码时,需要记住任何一个 Spring Cloud 模块都是基于 Spring Boot 扩展而来的,这个扩展一般是通过 spring.factories SPI 机制。任何一个 Spring Cloud 模块源代码都可以以这个为切入点进行理解

    spring.factories SPI 机制

    spring-core

    项目中提供了 Spring 框架多种 SPI 机制,其中一种非常常用并灵活运用在了 Spring-boot 的机制就是基于

    spring.factories

    的 SPI 机制。

    那么什么是 SPI(Service Provider)呢? 在系统设计中,为了模块间的协作,往往会设计统一的接口供模块之间的调用。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码,而是将指定哪个实现置于程序之外指定。Java 中默认的 SPI 机制就是通过

    ServiceLoader

    来实现,简单来说就是通过在

    META-INF/services

    目录下新建一个名称为接口全限定名的文件,内容为接口实现类的全限定名,之后程序通过代码:

    //指定加载的接口类,以及用来加载类的类加载器,如果类加载器为 null 则用根类加载器加载ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class, someClassLoader);Iterator<SpiService> iterator = serviceLoader.iterator();while (iterator.hasNext()){SpiService spiService = iterator.next();}

    获取指定的实现类。

    在 Spring 框架中,这个类是

    SpringFactoriesLoader

    ,需要在

    META-INF/spring.factories

    文件中指定接口以及对应的实现类,例如 Spring Cloud Commons 中的:

    # Environment Post Processorsorg.springframework.boot.env.EnvironmentPostProcessor=\\org.springframework.cloud.client.HostInfoEnvironmentPostProcessor

    其中指定了

    EnvironmentPostProcessor

    的实现

    HostInfoEnvironmentPostProcessor

    同时,Spring Boot 中会通过

    SpringFactoriesLoader.loadXXX

    类似的方法读取所有的

    EnvironmentPostProcessor

    的实现类并生成 Bean 到 ApplicationContext 中:

    EnvironmentPostProcessorApplicationListener

    //这个类也是通过spring.factories中指定ApplicationListener的实现而实现加载的,这里省略public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {//创建这个Bean的时候,会调用public EnvironmentPostProcessorApplicationListener() {this(EnvironmentPostProcessorsFactory.fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));}}

    EnvironmentPostProcessorsFactory

    static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {return new ReflectionEnvironmentPostProcessorsFactory(//通过 SpringFactoriesLoader.loadFactoryNames 获取文件中指定的实现类并初始化SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));}

    spring.factories 的特殊使用 – EnableAutoConfiguration

    META-INF/spring.factories

    文件中不一定指定的是接口以及对应的实现类,例如:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\\org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\\

    其中

    EnableAutoConfiguration

    是一个注解,

    LoadBalancerAutoConfiguration

    BlockingLoadBalancerClientAutoConfiguration

    都是配置类并不是

    EnableAutoConfiguration

    的实现。那么这个是什么意思呢?

    EnableAutoConfiguration

    是一个注解,

    LoadBalancerAutoConfiguration

    BlockingLoadBalancerClientAutoConfiguration

    都是配置类。

    spring.factories

    这里是另一种特殊使用,记录要载入的 Bean 类。

    EnableAutoConfiguration

    在注解被使用的时候,这些 Bean 会被加载。这就是

    spring.factories

    的另外一种用法。

    EnableAutoConfiguration

    是 Spring-boot 自动装载的核心注解。有了这个注解,Spring-boot 就可以自动加载各种

    @Configuration

    注解的类。那么这个机制是如何实现的呢?

    来看下

    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 {};//排除的Bean名称String[] excludeName() default {};}

    我们看到了有

    @Import

    这个注解。这个注解是 Spring 框架的一个很常用的注解,是 Spring 基于 Java 注解配置的主要组成部分。

    @Import

    注解的作用

    @Import

    注解提供了

    @Bean

    注解的功能,同时还有原来

    Spring

    基于 xml 配置文件里的

    &lt;import&gt;

    标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的

    @Configuration

    的类。这个注解的功能与用法包括

    1. 引入其他的

    @Configuration

    假设有如下接口和两个实现类:

    package com.testinterface ServiceInterface {void test();}class ServiceA implements ServiceInterface {@Overridepublic void test() {System.out.println(\"ServiceA\");}}class ServiceB implements ServiceInterface {@Overridepublic void test() {System.out.println(\"ServiceB\");}}

    两个

    @Configuration

    ,其中

    ConfigA``@Import``ConfigB

    :

    package com.test@Import(ConfigB.class)@Configurationclass ConfigA {@Bean@ConditionalOnMissingBeanpublic ServiceInterface getServiceA() {return new ServiceA();}}@Configurationclass ConfigB {@Bean@ConditionalOnMissingBeanpublic ServiceInterface getServiceB() {return new ServiceB();}}

    通过

    ConfigA

    创建

    AnnotationConfigApplicationContext

    ,获取

    ServiceInterface

    ,看是哪种实现:

    public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);ServiceInterface bean = ctx.getBean(ServiceInterface.class);bean.test();}

    输出为:

    ServiceB

    .证明

    @Import

    的优先于本身的的类定义加载。

    2. 直接初始化其他类的

    Bean

    Spring 4.2之后,

    @Import

    可以直接指定实体类,加载这个类定义到

    context

    中。
    例如把上面代码中的

    ConfigA

    @Import

    修改为

    @Import(ServiceB.class)

    ,就会生成

    ServiceB

    Bean

    到容器上下文中,之后运行

    main

    方法,输出为:

    ServiceB

    .证明

    @Import

    的优先于本身的的类定义加载.

    3. 指定实现

    ImportSelector

    (以及

    DefferredServiceImportSelector

    )的类,用于个性化加载

    指定实现

    ImportSelector

    的类,通过

    AnnotationMetadata

    里面的属性,动态加载类。

    AnnotationMetadata

    Import

    注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。

    需要实现

    selectImports

    方法,返回要加载的

    @Configuation

    或者具体

    Bean

    类的全限定名的

    String

    数组。

    package com.test;class ServiceImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {//可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称return new String[]{\"com.test.ConfigB\"};}}@Import(ServiceImportSelector.class)@Configurationclass ConfigA {@Bean@ConditionalOnMissingBeanpublic ServiceInterface getServiceA() {return new ServiceA();}}

    再次运行

    main

    方法,输出:

    ServiceB

    .证明

    @Import

    的优先于本身的的类定义加载。
    一般的,框架中如果基于

    AnnotationMetadata

    的参数实现动态加载类,一般会写一个额外的

    Enable

    注解,配合使用。例如:

    package com.test;@Retention(RetentionPolicy.RUNTIME)@Documented@Target(ElementType.TYPE)@Import(ServiceImportSelector.class)@interface EnableService {String name();}class ServiceImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {//这里的importingClassMetadata针对的是使用@EnableService的非注解类//因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);String name = (String) map.get(\"name\");if (Objects.equals(name, \"B\")) {return new String[]{\"com.test.ConfigB\"};}return new String[0];}}

    之后,在

    ConfigA

    中增加注解

    @EnableService(name = \"B\")

    package com.test;@EnableService(name = \"B\")@Configurationclass ConfigA {@Bean@ConditionalOnMissingBeanpublic ServiceInterface getServiceA() {return new ServiceA();}}

    再次运行

    main

    方法,输出:

    ServiceB

    .

    还可以实现

    DeferredImportSelector

    接口,这样

    selectImports

    返回的类就都是最后加载的,而不是像

    @Import

    注解那样,先加载。
    例如:

    package com.test;class DefferredServiceImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);String name = (String) map.get(\"name\");if (Objects.equals(name, \"B\")) {return new String[]{\"com.test.ConfigB\"};}return new String[0];}}

    修改

    EnableService

    注解:

    @Retention(RetentionPolicy.RUNTIME)@Documented@Target(ElementType.TYPE)@Import(DefferredServiceImportSelector.class)@interface EnableService {String name();}

    这样

    ConfigA

    就优先于

    DefferredServiceImportSelector

    返回的

    ConfigB

    加载,执行

    main

    方法,输出:

    ServiceA

    4. 指定实现

    ImportBeanDefinitionRegistrar

    的类,用于个性化加载

    ImportSelector

    用法与用途类似,但是如果我们想重定义

    Bean

    ,例如动态注入属性,改变

    Bean

    的类型和

    Scope

    等等,就需要通过指定实现

    ImportBeanDefinitionRegistrar

    的类实现。例如:

    定义

    ServiceC

    package com.test;class ServiceC implements ServiceInterface {private final String name;ServiceC(String name) {this.name = name;}@Overridepublic void test() {System.out.println(name);}}

    定义

    ServiceImportBeanDefinitionRegistrar

    动态注册

    ServiceC

    ,修改

    EnableService

    package com.test;@Retention(RetentionPolicy.RUNTIME)@Documented@Target(ElementType.TYPE)@Import(ServiceImportBeanDefinitionRegistrar.class)@interface EnableService {String name();}class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);String name = (String) map.get(\"name\");BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)//增加构造参数.addConstructorArgValue(name);//注册Beanregistry.registerBeanDefinition(\"serviceC\", beanDefinitionBuilder.getBeanDefinition());}}

    ImportBeanDefinitionRegistrar

    @Bean

    注解之后加载,所以要修改

    ConfigA

    去掉其中被

    @ConditionalOnMissingBean

    注解的

    Bean

    ,否则一定会生成

    ConfigA

    ServiceInterface

    package com.test;@EnableService(name = \"TestServiceC\")@Configurationclass ConfigA {//    @Bean//    @ConditionalOnMissingBean//    public ServiceInterface getServiceA() {//        return new ServiceA();//    }}

    之后运行

    main

    ,输出:

    TestServiceC

    Spring Boot 核心自动装载的实现原理

    上面我们提到了

    @EnableAutoConfiguration

    注解里面的:

    @Import(AutoConfigurationImportSelector.class)

    属于

    @Import

    注解的第三种用法,也就是通过具体的

    ImportSelector

    进行装载,实现其中的

    selectImports

    接口返回需要自动装载的类的全限定名称。这里的

    AutoConfigurationImportSelector

    实现是:

    AutoConfigurationImportSelector

    @Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//`spring.boot.enableautoconfiguration`这个属性没有指定为false那就是启用了Spring Boot自动装载,否则就是没启用。没启用的话,返回空数组if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}//获取要加载的类,详情见下面源代码AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}//获取要加载的类protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//`spring.boot.enableautoconfiguration`这个属性没有指定为false那就是启用了Spring Boot自动装载,否则就是没启用。没启用的话,返回空数组if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}//获取注解AnnotationAttributes attributes = getAttributes(annotationMetadata);//从spring.factories读取所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//去重configurations = removeDuplicates(configurations);//根据EnableAutoConfiguration注解的属性去掉要排除的类Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);//发布AutoConfigurationImportEvent事件fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}

    Spring Boot中的 ApplicationContext 的层级是什么

    ApplicationContext 是 spring 用来容纳管理 beans 以及其生命周期的容器。ApplicationContext 的分层规定了bean的界限以及可以复用的 bean。关于 ApplicationContext 层级可以参考官方文档,这里我们通过一个简单的例子来说明下 ApplicationContext 层级以及其中的bean界限,例如某些 bean 可以被多个 ApplicationContext 共享,同时某些 bean 只在某个 ApplicationContext 生效,不同 ApplicationContext 可以声明同名或者同类型的bean这样。我们将实现一个下图所示的 ApplicationContext 结构:

    我们会实现,一个 parent context 与三个对应 child context 的结构。

    首先定义Parent context:

    Bean类:

    package com.test.spring.context.bean;import lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructorpublic class RootBean {private Stirng name;}

    Context类:

    import com.hopegaming.scaffold.spring.context.bean.RootBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@PropertySource(value = \"classpath:/root.yaml\", factory = YamlPropertyLoaderFactory.class)public class RootContext {@Beanpublic RootBean getFatherBean() {RootBean rootBean = new RootBean();rootBean.setName(\"root\");return rootBean;}}

    root.yml:

    # 配置这些主要是将actuator相关接口暴露出来。management:endpoint:health:show-details: alwaysendpoints:jmx:exposure:exclude: \'*\'web:exposure:include: \'*\'

    由于我们使用了yml,这里需要我们自定义一个

    YamlPropertyLoaderFactory

    用于加载yml配置:

    package com.test.spring.context.config;import org.springframework.boot.env.YamlPropertySourceLoader;import org.springframework.core.env.PropertySource;import org.springframework.core.io.support.DefaultPropertySourceFactory;import org.springframework.core.io.support.EncodedResource;import java.io.IOException;public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {@Overridepublic PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {if (resource == null){return super.createPropertySource(name, resource);}return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).get(0);}}

    定义child context的公共Bean类:

    package com.test.spring.context.bean;import lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructorpublic class ChildBean {private RootBean fatherBean;private String name;}

    定义ChildContext1:

    package com.test.spring.context.config.child1;import com.hopegaming.scaffold.spring.context.bean.ChildBean;import com.hopegaming.scaffold.spring.context.bean.RootBean;import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.PropertySource;@SpringBootApplication(scanBasePackages = {\"com.test.spring.context.controller\"})@PropertySource(value = \"classpath:/bean-config-1.yaml\", factory = YamlPropertyLoaderFactory.class)public class ChildContext1 {@Beanpublic ChildBean getChildBean(@Value(\"${spring.application.name}\") String name, RootBean fatherBean) {ChildBean childBean = new ChildBean();childBean.setFatherBean(fatherBean);childBean.setName(name);return childBean;}}

    bean-config-1.yaml

    server:port: 8080spring:application:name: child1

    接下来分别是ChildContext2,ChildContext3的:

    package com.test.spring.context.config.child2;import com.hopegaming.scaffold.spring.context.bean.ChildBean;import com.hopegaming.scaffold.spring.context.bean.RootBean;import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.PropertySource;@SpringBootApplication(scanBasePackages = {\"com.test.spring.context.controller\"})@PropertySource(value = \"classpath:/bean-config-2.yaml\", factory = YamlPropertyLoaderFactory.class)public class ChildContext2 {@Beanpublic ChildBean getChildBean(@Value(\"${spring.application.name}\") String name, RootBean fatherBean) {ChildBean childBean = new ChildBean();childBean.setFatherBean(fatherBean);childBean.setName(name);return childBean;}}
    server:port: 8081spring:application:name: child2management:endpoint:health:show-details: alwaysendpoints:jmx:exposure:exclude: \'*\'web:exposure:include: \'*\'
    package com.test.spring.context.config.child3;import com.hopegaming.scaffold.spring.context.bean.ChildBean;import com.hopegaming.scaffold.spring.context.bean.RootBean;import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.PropertySource;@SpringBootApplication(scanBasePackages = {\"com.test.spring.context.controller\"})@PropertySource(value = \"classpath:/bean-config-3.yaml\", factory = YamlPropertyLoaderFactory.class)public class ChildContext3 {@Beanpublic ChildBean getChildBean(@Value(\"${spring.application.name}\") String name, RootBean fatherBean) {ChildBean childBean = new ChildBean();childBean.setFatherBean(fatherBean);childBean.setName(name);return childBean;}}
    server:port: 8082spring:application:name: child3management:endpoint:health:show-details: alwaysendpoints:jmx:exposure:exclude: \'*\'web:exposure:include: \'*\'

    测试接口

    TestController

    package com.test.spring.context.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Locale;@RestControllerpublic class TestController {@Autowiredprivate ChildBean childBean;@RequestMapping(\"/test\")public ChildBean getChildBean() {return childBean;}}

    启动类:

    package com.test.spring.context;import com.hopegaming.scaffold.spring.context.config.child1.ChildContext1;import com.hopegaming.scaffold.spring.context.config.child2.ChildContext2;import com.hopegaming.scaffold.spring.context.config.child3.ChildContext3;import com.hopegaming.scaffold.spring.context.config.root.RootContext;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.context.ConfigurableApplicationContext;public class ContextMain {public static void main(String[] args) {SpringApplicationBuilder appBuilder =new SpringApplicationBuilder().sources(RootContext.class)//第一个子context用child,剩下的都用sibling.child(ChildContext1.class).sibling(ChildContext2.class).sibling(ChildContext3.class);ConfigurableApplicationContext applicationContext = appBuilder.run();}}

    启动后,访问

    http://127.0.0.1:8080/test

    返回:

    {\"fatherBean\":{\"name\":\"root\"},\"name\":\"child1\"}

    访问

    http://127.0.0.1:8081/test

    返回:

    {\"fatherBean\":{\"name\":\"root\"},\"name\":\"child2\"}

    访问

    http://127.0.0.1:8082/test

    返回:

    {\"fatherBean\":{\"name\":\"root\"},\"name\":\"child3\"}

    访问

    http://127.0.0.1:8080/actuator/beans

    会有类似于下面的返回(省略了不关心的bean):

    {\"contexts\": {\"application-1\": {\"beans\": {\"getChildBean\": {\"aliases\": [],\"scope\": \"singleton\",\"type\": \"com.hopegaming.scaffold.spring.context.bean.ChildBean\",\"resource\": \"com.hopegaming.scaffold.spring.context.config.child2.ChildContext2\",\"dependencies\": [\"getFatherBean\"]},\"childContext2\": {\"aliases\": [],\"scope\": \"singleton\",\"type\": \"com.hopegaming.scaffold.spring.context.config.child2.ChildContext2$$EnhancerBySpringCGLIB$$26f80b15\",\"resource\": null,\"dependencies\": []}.......},\"parentId\": \"application\"},\"application\": {\"beans\": {\"getFatherBean\": {\"aliases\": [],\"scope\": \"singleton\",\"type\": \"com.hopegaming.scaffold.spring.context.bean.RootBean\",\"resource\": \"com.hopegaming.scaffold.spring.context.config.root.RootContext\",\"dependencies\": []},\"rootContext\": {\"aliases\": [],\"scope\": \"singleton\",\"type\": \"com.hopegaming.scaffold.spring.context.config.root.RootContext$$EnhancerBySpringCGLIB$$18d9c26f\",\"resource\": null,\"dependencies\": []}.......},\"parentId\": null}}}

    通过这个例子,想必大家对于 ApplicationContext 层级有了一定的理解

    Bean 加载条件

    我们会经常看到

    @Conditional

    相关的注解,例如

    @ConditionalOnBean

    还有

    @ConditionalOnClass

    等等,这些注解提供了自动装载时候根据某些条件加载不同类的灵活性。

    @Conditional

    注解是 spring-context 提供的特性,Spring Boot 在这个注解的基础上,提供了更多具体的条件配置注解,包括:

    • @ConditionalOnBean

      ,如果当前 ApplicationContext 的 BeanFactory 已经包含这些 Bean,则满足条件。与之相反的是

      @ConditionalOnMissingBean

      ,如果当前 ApplicationContext 的 BeanFactory 不包含这些 Bean,则满足条件。

    • @ConditionalOnClass

      ,如果当前 classpath 中有这些类,则满足条件。与之相反的是

      @ConditionalOnMissingClass

      ,如果当前 classpath 中没有这些类,则满足条件

    • @ConditionalOnProperty

      ,指定属性是否存在,并且值满足

      havingValue

      指定的值(没设置就是不为

      false

      就行),

      matchIfMissing

      代表如果属性不存在代表条件满足还是不满足。

    以上几个注解是比较常用的,剩下的例如

    ConditionalOnCloudPlatform

    这些不太常用,这里先不提了。

    如果有多个类似的

    @Conditional

    注解作用于同一个方法或者类,这些加载条件是“And”的关系

    Configuration 加载顺序

    由于 Bean 加载条件的复杂性,有时候我们想某些 Configuration 类先加载,某些在特定的 Configuration 加载完之后再加载。例如:

    @Configurationpublic class FirstConfiguration {@Bean@ConditionalOnMissingBeanpublic Service service1() {......}}
    @Configurationpublic class SecondConfiguration {@Bean@ConditionalOnMissingBeanpublic Service service1() {......}}

    假设这两个类在不同 jar 包,我们没有办法确定最后创建的是哪一个类的

    Service

    ,这时候我们就需要用到一些决定 Configuration 加载顺序的注解。注意这里的 Configuration 加载顺序仅仅是 Bean 定义加载顺序,主要是为了限制上面提到的 Bean 加载条件的判断顺序,而不是创建 Bean 的顺序。Bean 创建的顺序主要由 Bean 依赖决定以及

    @DependsOn

    注解限制。

    相关的注解如下:

    • @AutoConfigureAfter

      指定当前 Configuration 在 某个 Configuration 之后加载。

    • @AutoConfigureBefore

      指定当前 Configuration 在 某个 Configuration 之前加载。

    • @AutoConfigureOrder

      类似于

      @Order

      注解,指定当前 Configuration 的加载序号,默认是 0 ,越小越先加载。

    Bean 排序

    对于同一类型的 Bean(实现了同一接口的 Bean),我们可以用一个 List 进行自动装载,例如:

    public interface Service {void test();}@Componenetpublic class ServiceA implements Service {@Overridepublic void test() {System.out.println(\"ServiceA\");}}@Componenetpublic class ServiceB implements Service {@Overridepublic void test() {System.out.println(\"ServiceB\");}}@Componenetpublic class Test {@Autowiredprivate List<Service> services;}

    private List&lt;Service&gt; services

    中就会有

    serviceA

    serviceB

    这两个 Bean,但是谁在前谁在后呢?可以通过

    @Order

    注解指定。

    @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})@Documentedpublic @interface Order {/*** The order value.* <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.* @see Ordered#getOrder()*/int value() default Ordered.LOWEST_PRECEDENCE;}

    值越小,越靠前。

    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Spring Cloud系列之Commons – 1. 背景与基础知识准备