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 Processors
org.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 配置文件里的<import>标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration的类。这个注解的功能与用法包括
1. 引入其他的@Configuration
假设有如下接口和两个实现类:
package com.test
interface ServiceInterface {
    void test();
}
class ServiceA implements ServiceInterface {
    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}
class ServiceB implements ServiceInterface {
    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}
两个@Configuration,其中ConfigA``@Import``ConfigB:
package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public 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 {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
        return new String[]{"com.test.ConfigB"};
    }
}
@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public 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 {
    @Override
    public 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")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
再次运行main方法,输出:ServiceB.
还可以实现DeferredImportSelector接口,这样selectImports返回的类就都是最后加载的,而不是像@Import注解那样,先加载。
例如:
package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public 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;
    }
    @Override
    public 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 {
    @Override
    public 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);
        //注册Bean
        registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
    }
}
ImportBeanDefinitionRegistrar 在 @Bean 注解之后加载,所以要修改ConfigA去掉其中被@ConditionalOnMissingBean注解的Bean,否则一定会生成ConfigA的ServiceInterface
package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}
之后运行main,输出:TestServiceC
Spring Boot 核心自动装载的实现原理
上面我们提到了@EnableAutoConfiguration注解里面的:
@Import(AutoConfigurationImportSelector.class)
属于@Import注解的第三种用法,也就是通过具体的ImportSelector进行装载,实现其中的selectImports接口返回需要自动装载的类的全限定名称。这里的AutoConfigurationImportSelector实现是:
AutoConfigurationImportSelector
@Override
public 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
@NoArgsConstructor
public 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 {
    @Bean
    public RootBean getFatherBean() {
        RootBean rootBean = new RootBean();
        rootBean.setName("root");
        return rootBean;
    }
}
root.yml:
# 配置这些主要是将actuator相关接口暴露出来。
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    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 {
    @Override
    public 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
@NoArgsConstructor
public 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 {
    @Bean
    public 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: 8080
spring:
  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 {
    @Bean
    public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
        ChildBean childBean = new ChildBean();
        childBean.setFatherBean(fatherBean);
        childBean.setName(name);
        return childBean;
    }
}
server:
  port: 8081
spring:
  application:
    name: child2
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    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 {
    @Bean
    public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
        ChildBean childBean = new ChildBean();
        childBean.setFatherBean(fatherBean);
        childBean.setName(name);
        return childBean;
    }
}
server:
  port: 8082
spring:
  application:
    name: child3
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    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;
@RestController
public class TestController {
    @Autowired
    private 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 加载完之后再加载。例如:
@Configuration
public class FirstConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Service service1() {
        ......
    }
}
@Configuration
public class SecondConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public 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();
}
@Componenet
public class ServiceA implements Service {
    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}
@Componenet
public class ServiceB implements Service {
    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}
@Componenet
public class Test {
    @Autowired
    private List<Service> services;
}
private List<Service> services 中就会有 serviceA 和 serviceB 这两个 Bean,但是谁在前谁在后呢?可以通过@Order注解指定。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
	/**
	 * The order value.
	 * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
	 * @see Ordered#getOrder()
	 */
	int value() default Ordered.LOWEST_PRECEDENCE;
}
值越小,越靠前。
Spring Cloud系列之Commons - 1. 背景与基础知识准备的更多相关文章
- spring cloud系列教程第四篇-Eureka基础知识
		通过前三篇文章学习,我们搭建好了两个微服务工程.即:order80和payment8001这两个服务.有了这两个基础的框架之后,我们将要开始往里面添加东西了.还记得分布式架构的几个维度吗?我们要通过一 ... 
- Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇
		Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇 本文主要内容: 1:spring cloud整合Eureka总结 本文是由凯哥(凯哥Java:kagejava ... 
- Spring Cloud 系列之 Gateway 服务网关(四)
		本篇文章为系列文章,未读第一集的同学请猛戳这里: Spring Cloud 系列之 Gateway 服务网关(一) Spring Cloud 系列之 Gateway 服务网关(二) Spring Cl ... 
- Spring Cloud系列文,Feign整合Ribbon和Hysrix
		在本博客之前的Spring Cloud系列里,我们讲述了Feign的基本用法,这里我们将讲述下Feign整合Ribbon实现负载均衡以及整合Hystrix实现断路保护效果的方式. 1 准备Eureka ... 
- Spring Cloud系列(二) 介绍
		Spring Cloud系列(一) 介绍 Spring Cloud是基于Spring Boot实现的微服务架构开发工具.它为微服务架构中涉及的配置管理.服务治理.断路器.智能路由.微代理.控制总线.全 ... 
- Spring Cloud 系列之 Spring Cloud Stream
		Spring Cloud Stream 是消息中间件组件,它集成了 kafka 和 rabbitmq .本篇文章以 Rabbit MQ 为消息中间件系统为基础,介绍 Spring Cloud Stre ... 
- Spring Cloud 系列之 Consul 注册中心(二)
		本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Consul 注册中心(一) 本篇文章讲解 Consul 集群环境的搭建. Consul 集群 上图是一个简单的 Co ... 
- Spring Cloud 系列之 Gateway 服务网关(二)
		本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Gateway 服务网关(一) 本篇文章讲解 Gateway 网关的多种路由规则.动态路由规则(配合服务发现的路由规则 ... 
- Spring Cloud 系列之 Gateway 服务网关(三)
		本篇文章为系列文章,未读第一集的同学请猛戳这里: Spring Cloud 系列之 Gateway 服务网关(一) Spring Cloud 系列之 Gateway 服务网关(二) 本篇文章讲解 Ga ... 
随机推荐
- 阿里云Ubuntu配置安装MQTT服务器
			先来说说mqtt协议: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,它比较适合于在低带宽.不可靠的网络的进行远程 ... 
- 用漫画的形式展现——什么是web
			Web主要经历了web1.0和web2.0的阶段.万维网的初期都是web1.0的时代:静态页面. 在不同的时代,流行的web安全问题也不太相同.在web1.0时代,web安全主要是 Web1.0:sq ... 
- 大话MySQL锁
			一.锁介绍 不同存储引擎支持的锁是不同的,比如MyISAM只有表锁,而InnoDB既支持表锁又支持行锁. 下图展示了InnoDB不同锁类型之间的关系: 图中的概念比较多不好理解,下面依次进行说明. 1 ... 
- web.xml中配置启动时加载的servlet,load-on-starup
			web.xml中配置启动时加载的servlet,load-on-starup 使用servlet来初始化配置文件数据: 在servlet的配置当中,<load-on-startup>1&l ... 
- 日常入坑1-Calendar类
			1.当前时间是一号的时候,通过计算上一天的日期的时候,需要注意了 Calendar calendar = Calendar.getInstance();calendar.set(2019,10,1); ... 
- Linux LVM Logical Volume Management 逻辑卷的管理
			博主是一个数据库DBA,但是一般来说,是不做linux服务器LVM 逻辑卷的创建.扩容和减容操作的,基本上有系统管理员操作,一是各司其职,专业的事专业的人做,二是做多了你的责任也多了,哈哈! 但是li ... 
- Python基础(下篇)
			本篇文章主要内容:异常处理,函数,模块和包. 在开始正篇之前我们先来看看上一篇可乐留下的题目. 题目: 变量 a= {"name": "可乐", "a ... 
- LinkedList 的 API 与数据结构
			LinkedList 是 List 接口和 Deque 接口的双向链表实现,它所有的 API 调用都是基于对双向链表的操作.本文将介绍 LinkedList 的数据结构和分析 API 中的算法. 数据 ... 
- Java springboot支付宝小程序授权,获取用户信息,支付及回调
			参考官方文档https://opendocs.alipay.com/mini/introduce/pay 支付宝小程序的支付和微信小程序的支付一样第一步都是要获取到用户的唯一标识,在微信中我们获取到的 ... 
- Laravel - 验证码
			安装扩展包 使用 Composer 安装: composer require "mews/captcha:~2.0" 运行以下命令生成配置文件 config/captcha.php ... 
