本文基于 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目录下新建一个名称为接口全限定名的文件,内容为接口实现类的全限定名,之后程序通过代码:

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

获取指定的实现类。

在 Spring 框架中,这个类是SpringFactoriesLoader,需要在META-INF/spring.factories文件中指定接口以及对应的实现类,例如 Spring Cloud Commons 中的:

  1. # Environment Post Processors
  2. org.springframework.boot.env.EnvironmentPostProcessor=\
  3. org.springframework.cloud.client.HostInfoEnvironmentPostProcessor

其中指定了EnvironmentPostProcessor的实现HostInfoEnvironmentPostProcessor

同时,Spring Boot 中会通过SpringFactoriesLoader.loadXXX类似的方法读取所有的EnvironmentPostProcessor的实现类并生成 Bean 到 ApplicationContext 中:

EnvironmentPostProcessorApplicationListener

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

EnvironmentPostProcessorsFactory

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

spring.factories 的特殊使用 - EnableAutoConfiguration

META-INF/spring.factories 文件中不一定指定的是接口以及对应的实现类,例如:

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

其中EnableAutoConfiguration是一个注解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置类并不是EnableAutoConfiguration的实现。那么这个是什么意思呢?EnableAutoConfiguration是一个注解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置类。spring.factories这里是另一种特殊使用,记录要载入的 Bean 类。EnableAutoConfiguration在注解被使用的时候,这些 Bean 会被加载。这就是spring.factories的另外一种用法。

EnableAutoConfiguration是 Spring-boot 自动装载的核心注解。有了这个注解,Spring-boot 就可以自动加载各种@Configuration注解的类。那么这个机制是如何实现的呢?

来看下EnableAutoConfiguration的源码

EnableAutoConfiguration

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. //排除的类
  10. Class<?>[] exclude() default {};
  11. //排除的Bean名称
  12. String[] excludeName() default {};
  13. }

我们看到了有 @Import 这个注解。这个注解是 Spring 框架的一个很常用的注解,是 Spring 基于 Java 注解配置的主要组成部分。

@Import注解的作用

@Import注解提供了@Bean注解的功能,同时还有原来Spring基于 xml 配置文件里的<import>标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration的类。这个注解的功能与用法包括

1. 引入其他的@Configuration

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

  1. package com.test
  2. interface ServiceInterface {
  3. void test();
  4. }
  5. class ServiceA implements ServiceInterface {
  6. @Override
  7. public void test() {
  8. System.out.println("ServiceA");
  9. }
  10. }
  11. class ServiceB implements ServiceInterface {
  12. @Override
  13. public void test() {
  14. System.out.println("ServiceB");
  15. }
  16. }

两个@Configuration,其中ConfigA``@Import``ConfigB:

  1. package com.test
  2. @Import(ConfigB.class)
  3. @Configuration
  4. class ConfigA {
  5. @Bean
  6. @ConditionalOnMissingBean
  7. public ServiceInterface getServiceA() {
  8. return new ServiceA();
  9. }
  10. }
  11. @Configuration
  12. class ConfigB {
  13. @Bean
  14. @ConditionalOnMissingBean
  15. public ServiceInterface getServiceB() {
  16. return new ServiceB();
  17. }
  18. }

通过ConfigA创建AnnotationConfigApplicationContext,获取ServiceInterface,看是哪种实现:

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

输出为:ServiceB.证明@Import的优先于本身的的类定义加载。

2. 直接初始化其他类的Bean

Spring 4.2之后,@Import可以直接指定实体类,加载这个类定义到context中。

例如把上面代码中的ConfigA@Import修改为@Import(ServiceB.class),就会生成ServiceBBean到容器上下文中,之后运行main方法,输出为:ServiceB.证明@Import的优先于本身的的类定义加载.

3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载

指定实现ImportSelector的类,通过AnnotationMetadata里面的属性,动态加载类。AnnotationMetadataImport注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。

需要实现selectImports方法,返回要加载的@Configuation或者具体Bean类的全限定名的String数组。

  1. package com.test;
  2. class ServiceImportSelector implements ImportSelector {
  3. @Override
  4. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  5. //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
  6. return new String[]{"com.test.ConfigB"};
  7. }
  8. }
  9. @Import(ServiceImportSelector.class)
  10. @Configuration
  11. class ConfigA {
  12. @Bean
  13. @ConditionalOnMissingBean
  14. public ServiceInterface getServiceA() {
  15. return new ServiceA();
  16. }
  17. }

再次运行main方法,输出:ServiceB.证明@Import的优先于本身的的类定义加载。

一般的,框架中如果基于AnnotationMetadata的参数实现动态加载类,一般会写一个额外的Enable注解,配合使用。例如:

  1. package com.test;
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Target(ElementType.TYPE)
  5. @Import(ServiceImportSelector.class)
  6. @interface EnableService {
  7. String name();
  8. }
  9. class ServiceImportSelector implements ImportSelector {
  10. @Override
  11. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  12. //这里的importingClassMetadata针对的是使用@EnableService的非注解类
  13. //因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
  14. Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
  15. String name = (String) map.get("name");
  16. if (Objects.equals(name, "B")) {
  17. return new String[]{"com.test.ConfigB"};
  18. }
  19. return new String[0];
  20. }
  21. }

之后,在ConfigA中增加注解@EnableService(name = "B")

  1. package com.test;
  2. @EnableService(name = "B")
  3. @Configuration
  4. class ConfigA {
  5. @Bean
  6. @ConditionalOnMissingBean
  7. public ServiceInterface getServiceA() {
  8. return new ServiceA();
  9. }
  10. }

再次运行main方法,输出:ServiceB.

还可以实现DeferredImportSelector接口,这样selectImports返回的类就都是最后加载的,而不是像@Import注解那样,先加载。

例如:

  1. package com.test;
  2. class DefferredServiceImportSelector implements DeferredImportSelector {
  3. @Override
  4. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  5. Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
  6. String name = (String) map.get("name");
  7. if (Objects.equals(name, "B")) {
  8. return new String[]{"com.test.ConfigB"};
  9. }
  10. return new String[0];
  11. }
  12. }

修改EnableService注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Documented
  3. @Target(ElementType.TYPE)
  4. @Import(DefferredServiceImportSelector.class)
  5. @interface EnableService {
  6. String name();
  7. }

这样ConfigA就优先于DefferredServiceImportSelector返回的ConfigB加载,执行main方法,输出:ServiceA

4. 指定实现ImportBeanDefinitionRegistrar的类,用于个性化加载

ImportSelector用法与用途类似,但是如果我们想重定义Bean,例如动态注入属性,改变Bean的类型和Scope等等,就需要通过指定实现ImportBeanDefinitionRegistrar的类实现。例如:

定义ServiceC

  1. package com.test;
  2. class ServiceC implements ServiceInterface {
  3. private final String name;
  4. ServiceC(String name) {
  5. this.name = name;
  6. }
  7. @Override
  8. public void test() {
  9. System.out.println(name);
  10. }
  11. }

定义ServiceImportBeanDefinitionRegistrar动态注册ServiceC,修改EnableService

  1. package com.test;
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Target(ElementType.TYPE)
  5. @Import(ServiceImportBeanDefinitionRegistrar.class)
  6. @interface EnableService {
  7. String name();
  8. }
  9. class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  10. @Override
  11. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  12. Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
  13. String name = (String) map.get("name");
  14. BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
  15. //增加构造参数
  16. .addConstructorArgValue(name);
  17. //注册Bean
  18. registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
  19. }
  20. }

ImportBeanDefinitionRegistrar@Bean 注解之后加载,所以要修改ConfigA去掉其中被@ConditionalOnMissingBean注解的Bean,否则一定会生成ConfigAServiceInterface

  1. package com.test;
  2. @EnableService(name = "TestServiceC")
  3. @Configuration
  4. class ConfigA {
  5. // @Bean
  6. // @ConditionalOnMissingBean
  7. // public ServiceInterface getServiceA() {
  8. // return new ServiceA();
  9. // }
  10. }

之后运行main,输出:TestServiceC

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

上面我们提到了@EnableAutoConfiguration注解里面的:

  1. @Import(AutoConfigurationImportSelector.class)

属于@Import注解的第三种用法,也就是通过具体的ImportSelector进行装载,实现其中的selectImports接口返回需要自动装载的类的全限定名称。这里的AutoConfigurationImportSelector实现是:

AutoConfigurationImportSelector

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. //`spring.boot.enableautoconfiguration`这个属性没有指定为false那就是启用了Spring Boot自动装载,否则就是没启用。没启用的话,返回空数组
  4. if (!isEnabled(annotationMetadata)) {
  5. return NO_IMPORTS;
  6. }
  7. //获取要加载的类,详情见下面源代码
  8. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  9. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  10. }
  11. //获取要加载的类
  12. protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  13. //`spring.boot.enableautoconfiguration`这个属性没有指定为false那就是启用了Spring Boot自动装载,否则就是没启用。没启用的话,返回空数组
  14. if (!isEnabled(annotationMetadata)) {
  15. return EMPTY_ENTRY;
  16. }
  17. //获取注解
  18. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  19. //从spring.factories读取所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类
  20. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  21. //去重
  22. configurations = removeDuplicates(configurations);
  23. //根据EnableAutoConfiguration注解的属性去掉要排除的类
  24. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  25. checkExcludedClasses(configurations, exclusions);
  26. configurations.removeAll(exclusions);
  27. configurations = getConfigurationClassFilter().filter(configurations);
  28. //发布AutoConfigurationImportEvent事件
  29. fireAutoConfigurationImportEvents(configurations, exclusions);
  30. return new AutoConfigurationEntry(configurations, exclusions);
  31. }

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类:

  1. package com.test.spring.context.bean;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class RootBean {
  7. private Stirng name;
  8. }

Context类:

  1. import com.hopegaming.scaffold.spring.context.bean.RootBean;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. @PropertySource(value = "classpath:/root.yaml", factory = YamlPropertyLoaderFactory.class)
  6. public class RootContext {
  7. @Bean
  8. public RootBean getFatherBean() {
  9. RootBean rootBean = new RootBean();
  10. rootBean.setName("root");
  11. return rootBean;
  12. }
  13. }

root.yml:

  1. # 配置这些主要是将actuator相关接口暴露出来。
  2. management:
  3. endpoint:
  4. health:
  5. show-details: always
  6. endpoints:
  7. jmx:
  8. exposure:
  9. exclude: '*'
  10. web:
  11. exposure:
  12. include: '*'

由于我们使用了yml,这里需要我们自定义一个YamlPropertyLoaderFactory用于加载yml配置:

  1. package com.test.spring.context.config;
  2. import org.springframework.boot.env.YamlPropertySourceLoader;
  3. import org.springframework.core.env.PropertySource;
  4. import org.springframework.core.io.support.DefaultPropertySourceFactory;
  5. import org.springframework.core.io.support.EncodedResource;
  6. import java.io.IOException;
  7. public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
  8. @Override
  9. public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
  10. if (resource == null){
  11. return super.createPropertySource(name, resource);
  12. }
  13. return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).get(0);
  14. }
  15. }

定义child context的公共Bean类:

  1. package com.test.spring.context.bean;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class ChildBean {
  7. private RootBean fatherBean;
  8. private String name;
  9. }

定义ChildContext1:

  1. package com.test.spring.context.config.child1;
  2. import com.hopegaming.scaffold.spring.context.bean.ChildBean;
  3. import com.hopegaming.scaffold.spring.context.bean.RootBean;
  4. import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.PropertySource;
  9. @SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
  10. @PropertySource(value = "classpath:/bean-config-1.yaml", factory = YamlPropertyLoaderFactory.class)
  11. public class ChildContext1 {
  12. @Bean
  13. public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
  14. ChildBean childBean = new ChildBean();
  15. childBean.setFatherBean(fatherBean);
  16. childBean.setName(name);
  17. return childBean;
  18. }
  19. }

bean-config-1.yaml

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: child1

接下来分别是ChildContext2,ChildContext3的:

  1. package com.test.spring.context.config.child2;
  2. import com.hopegaming.scaffold.spring.context.bean.ChildBean;
  3. import com.hopegaming.scaffold.spring.context.bean.RootBean;
  4. import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.PropertySource;
  9. @SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
  10. @PropertySource(value = "classpath:/bean-config-2.yaml", factory = YamlPropertyLoaderFactory.class)
  11. public class ChildContext2 {
  12. @Bean
  13. public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
  14. ChildBean childBean = new ChildBean();
  15. childBean.setFatherBean(fatherBean);
  16. childBean.setName(name);
  17. return childBean;
  18. }
  19. }
  1. server:
  2. port: 8081
  3. spring:
  4. application:
  5. name: child2
  6. management:
  7. endpoint:
  8. health:
  9. show-details: always
  10. endpoints:
  11. jmx:
  12. exposure:
  13. exclude: '*'
  14. web:
  15. exposure:
  16. include: '*'
  1. package com.test.spring.context.config.child3;
  2. import com.hopegaming.scaffold.spring.context.bean.ChildBean;
  3. import com.hopegaming.scaffold.spring.context.bean.RootBean;
  4. import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.PropertySource;
  9. @SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
  10. @PropertySource(value = "classpath:/bean-config-3.yaml", factory = YamlPropertyLoaderFactory.class)
  11. public class ChildContext3 {
  12. @Bean
  13. public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
  14. ChildBean childBean = new ChildBean();
  15. childBean.setFatherBean(fatherBean);
  16. childBean.setName(name);
  17. return childBean;
  18. }
  19. }
  1. server:
  2. port: 8082
  3. spring:
  4. application:
  5. name: child3
  6. management:
  7. endpoint:
  8. health:
  9. show-details: always
  10. endpoints:
  11. jmx:
  12. exposure:
  13. exclude: '*'
  14. web:
  15. exposure:
  16. include: '*'

测试接口TestController

  1. package com.test.spring.context.controller;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import java.util.Locale;
  7. @RestController
  8. public class TestController {
  9. @Autowired
  10. private ChildBean childBean;
  11. @RequestMapping("/test")
  12. public ChildBean getChildBean() {
  13. return childBean;
  14. }
  15. }

启动类:

  1. package com.test.spring.context;
  2. import com.hopegaming.scaffold.spring.context.config.child1.ChildContext1;
  3. import com.hopegaming.scaffold.spring.context.config.child2.ChildContext2;
  4. import com.hopegaming.scaffold.spring.context.config.child3.ChildContext3;
  5. import com.hopegaming.scaffold.spring.context.config.root.RootContext;
  6. import org.springframework.boot.builder.SpringApplicationBuilder;
  7. import org.springframework.context.ConfigurableApplicationContext;
  8. public class ContextMain {
  9. public static void main(String[] args) {
  10. SpringApplicationBuilder appBuilder =
  11. new SpringApplicationBuilder()
  12. .sources(RootContext.class)
  13. //第一个子context用child,剩下的都用sibling
  14. .child(ChildContext1.class)
  15. .sibling(ChildContext2.class)
  16. .sibling(ChildContext3.class);
  17. ConfigurableApplicationContext applicationContext = appBuilder.run();
  18. }
  19. }

启动后,访问http://127.0.0.1:8080/test返回:

  1. {"fatherBean":{"name":"root"},"name":"child1"}

访问http://127.0.0.1:8081/test返回:

  1. {"fatherBean":{"name":"root"},"name":"child2"}

访问http://127.0.0.1:8082/test返回:

  1. {"fatherBean":{"name":"root"},"name":"child3"}

访问http://127.0.0.1:8080/actuator/beans会有类似于下面的返回(省略了不关心的bean):

  1. {
  2. "contexts": {
  3. "application-1": {
  4. "beans": {
  5. "getChildBean": {
  6. "aliases": [],
  7. "scope": "singleton",
  8. "type": "com.hopegaming.scaffold.spring.context.bean.ChildBean",
  9. "resource": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2",
  10. "dependencies": [
  11. "getFatherBean"
  12. ]
  13. },
  14. "childContext2": {
  15. "aliases": [],
  16. "scope": "singleton",
  17. "type": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2$$EnhancerBySpringCGLIB$$26f80b15",
  18. "resource": null,
  19. "dependencies": []
  20. }
  21. .......
  22. },
  23. "parentId": "application"
  24. },
  25. "application": {
  26. "beans": {
  27. "getFatherBean": {
  28. "aliases": [],
  29. "scope": "singleton",
  30. "type": "com.hopegaming.scaffold.spring.context.bean.RootBean",
  31. "resource": "com.hopegaming.scaffold.spring.context.config.root.RootContext",
  32. "dependencies": []
  33. },
  34. "rootContext": {
  35. "aliases": [],
  36. "scope": "singleton",
  37. "type": "com.hopegaming.scaffold.spring.context.config.root.RootContext$$EnhancerBySpringCGLIB$$18d9c26f",
  38. "resource": null,
  39. "dependencies": []
  40. }
  41. .......
  42. },
  43. "parentId": null
  44. }
  45. }
  46. }

通过这个例子,想必大家对于 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 加载完之后再加载。例如:

  1. @Configuration
  2. public class FirstConfiguration {
  3. @Bean
  4. @ConditionalOnMissingBean
  5. public Service service1() {
  6. ......
  7. }
  8. }
  1. @Configuration
  2. public class SecondConfiguration {
  3. @Bean
  4. @ConditionalOnMissingBean
  5. public Service service1() {
  6. ......
  7. }
  8. }

假设这两个类在不同 jar 包,我们没有办法确定最后创建的是哪一个类的 Service,这时候我们就需要用到一些决定 Configuration 加载顺序的注解。注意这里的 Configuration 加载顺序仅仅是 Bean 定义加载顺序,主要是为了限制上面提到的 Bean 加载条件的判断顺序,而不是创建 Bean 的顺序。Bean 创建的顺序主要由 Bean 依赖决定以及@DependsOn注解限制。

相关的注解如下:

  • @AutoConfigureAfter 指定当前 Configuration 在 某个 Configuration 之后加载。
  • @AutoConfigureBefore 指定当前 Configuration 在 某个 Configuration 之前加载。
  • @AutoConfigureOrder 类似于@Order注解,指定当前 Configuration 的加载序号,默认是 0 ,越小越先加载。

Bean 排序

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

  1. public interface Service {
  2. void test();
  3. }
  4. @Componenet
  5. public class ServiceA implements Service {
  6. @Override
  7. public void test() {
  8. System.out.println("ServiceA");
  9. }
  10. }
  11. @Componenet
  12. public class ServiceB implements Service {
  13. @Override
  14. public void test() {
  15. System.out.println("ServiceB");
  16. }
  17. }
  18. @Componenet
  19. public class Test {
  20. @Autowired
  21. private List<Service> services;
  22. }

private List<Service> services 中就会有 serviceAserviceB 这两个 Bean,但是谁在前谁在后呢?可以通过@Order注解指定。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
  3. @Documented
  4. public @interface Order {
  5. /**
  6. * The order value.
  7. * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
  8. * @see Ordered#getOrder()
  9. */
  10. int value() default Ordered.LOWEST_PRECEDENCE;
  11. }

值越小,越靠前。

Spring Cloud系列之Commons - 1. 背景与基础知识准备的更多相关文章

  1. spring cloud系列教程第四篇-Eureka基础知识

    通过前三篇文章学习,我们搭建好了两个微服务工程.即:order80和payment8001这两个服务.有了这两个基础的框架之后,我们将要开始往里面添加东西了.还记得分布式架构的几个维度吗?我们要通过一 ...

  2. Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇

    Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇 本文主要内容: 1:spring cloud整合Eureka总结 本文是由凯哥(凯哥Java:kagejava ...

  3. Spring Cloud 系列之 Gateway 服务网关(四)

    本篇文章为系列文章,未读第一集的同学请猛戳这里: Spring Cloud 系列之 Gateway 服务网关(一) Spring Cloud 系列之 Gateway 服务网关(二) Spring Cl ...

  4. Spring Cloud系列文,Feign整合Ribbon和Hysrix

    在本博客之前的Spring Cloud系列里,我们讲述了Feign的基本用法,这里我们将讲述下Feign整合Ribbon实现负载均衡以及整合Hystrix实现断路保护效果的方式. 1 准备Eureka ...

  5. Spring Cloud系列(二) 介绍

    Spring Cloud系列(一) 介绍 Spring Cloud是基于Spring Boot实现的微服务架构开发工具.它为微服务架构中涉及的配置管理.服务治理.断路器.智能路由.微代理.控制总线.全 ...

  6. Spring Cloud 系列之 Spring Cloud Stream

    Spring Cloud Stream 是消息中间件组件,它集成了 kafka 和 rabbitmq .本篇文章以 Rabbit MQ 为消息中间件系统为基础,介绍 Spring Cloud Stre ...

  7. Spring Cloud 系列之 Consul 注册中心(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Consul 注册中心(一) 本篇文章讲解 Consul 集群环境的搭建. Consul 集群 上图是一个简单的 Co ...

  8. Spring Cloud 系列之 Gateway 服务网关(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Gateway 服务网关(一) 本篇文章讲解 Gateway 网关的多种路由规则.动态路由规则(配合服务发现的路由规则 ...

  9. Spring Cloud 系列之 Gateway 服务网关(三)

    本篇文章为系列文章,未读第一集的同学请猛戳这里: Spring Cloud 系列之 Gateway 服务网关(一) Spring Cloud 系列之 Gateway 服务网关(二) 本篇文章讲解 Ga ...

随机推荐

  1. java类的定义位置

    java的类可以定义在任何位置: 一般的类是一个文件里面写一个类,且类名和文件名相同,但是定义类的位置可以是任意的如图: 上图示例: public class A{ class B{ } static ...

  2. Java对象赋值与引用

    当需要创建多个相同类型的对象且有某些字段的值是相同的,如果直接 get,set 的话,属性多的时候代码会很长,于是乎,以下代码产生了( java 基础差没搞清楚赋值与引用) 复制代码 1 User u ...

  3. JSP 的 4 种作用域?

    page:代表与一个页面相关的对象和属性. request:代表与客户端发出的一个请求相关的对象和属性.一个请求可能跨越多个页面,涉及多个 Web 组件:需要在页面显示的临时数据可以置于此作用域. s ...

  4. JAVADOC 文档注释命令

    简介 javadoc命令是用来生成自己API文档的 javadoc参数信息 @author 作者名 @version 版本号 @since 指明需要最早使用的jdk版本 @param 参数名 @ret ...

  5. rocketmq 架构设计

    1 消息存储 消息存储是RocketMQ中最为复杂和最为重要的一部分,本节将分别从RocketMQ的消息存储整体架构.PageCache与Mmap内存映射以及RocketMQ中两种不同的刷盘方式三方面 ...

  6. C语言实现的多线程定时器

    目录 1. 大致功能介绍 2. API库介绍 3. 一个例子 4. 库文件源码 注意事项 1. 大致功能介绍 实现任务列表,定时器会间隔一段时间遍历列表发现要执行的任务 任务列表中的所有任务并行执行 ...

  7. R语言学习笔记-Corrplot相关性分析

    示例图像 首先安装需要的包 install.packages("Corrplot") #安装Corrplot install.packages("RColorBrewer ...

  8. 通过trace分析优化其如何选择执行计划

    mysql5.6提供了对sql的跟踪trace,通过trace文件能够进一步了解为什么优化其选择执行计划a而不选b执行计划,帮助我们更好的理解优化其的行为. 使用方式:首先打开trace,设置格式为j ...

  9. 【Oracle】删除表空间

    删除表空间如果是 SQL> DROP TABLEPSACE XXXX; 是无法将数据文件一同都删除的 想要删除表空间和数据文件需要如下操作: SQL> drop tablespace XX ...

  10. 【Oracle】11G 11.2.0.4 RAC环境打补丁

    一.准备工作 1,数据库环境 操作系统版本  : RedHat 7.2 x64   数据库版本    : Oracle 11.2.0.4 x64 RAC    Grid          : 11.2 ...