本文基于 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