您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

SpringBootStarter原理与应用

时间:2023-08-21 13:21:46  来源:  作者:程序员高级码农II

SpringBoot生态体系及扩展

在应用程序开发过程中,很难通过一个框架就实现所有的功能需求,也不存在满足各种应用场景的统一开发框架。尤其近几年来,随着微服务、云原生等概念的落地,诞生了一系列新型的开发技术和框架。Spring Boot作为Spring家族中最具代表性的开源框架,充分考虑到了与其他技术和框架的整合,逐步形成了一个生态圈。

Spring Boot通过Starter组件确保应用程序的扩展性和集成性。开发人员可以实现自定义的Spring Boot Starter来完成与Spring Boot框架的有效集成。通过这种方式,越来越多的应用程序内置了对Spring Boot开发模式的支持,使得Spring Boot的应用场景和范围得到了显著拓宽。这是SpringBoot构建生态圈的基础。

另外,Spring Boot也是Spring Cloud的基础。Spring Cloud是Spring家族中专门用来实现微服务架构的开发框架,而基于Spring Cloud开发的每个微服务本质上就是一个Spring Boot应用程序。

Spring Boot同样对云原生提供了开发框架方面的技术支持,专门实现了一个Spring Native框架。通过使用基于GraalVM的原生镜像,无论是应用程序的启动时间,还是运行时所占用的内存空间,都得到了显著的优化。

本章将对上述Spring Boot生态圈的相关内容详细讲解,并在最后给出测试Spring Boot应用程序的系统方法。

SpringBootStarter原理与应用

先来介绍Spring Boot生态中的第一部分内容——Spring Boot Starter组件,这是Spring Boot为开发人员提供扩展性的基础,也是各种外部系统与Spring Boot集成的基础。通过Spring Boot Starter组件,Spring Boot开发了各种各样的Starter,包括Spring官方提供的,第三方开源的,以及我们自己开发的。这些Starter构成了一个全面而强大的生态圈。在这个生态圈中,基本上普通应用程序开发所需要的开发组件都可以被找到。

在第1章中,我们已经明白Spring Boot与Spring MVC相比最大的优点就是简单,它采用的是约定大于配置的设计思想。而这种思想具体落地的形式就是Spring Boot Starter。Spring Boot Starter不但使用方式很简单,内部实现机制也并不复杂。

在本节中,我们将深入分析Spring Boot自动配置的实现原理,然后给出基于Spring Boot Starter组件集成Spring Boot的案例分析。

SpringBoot自动配置原理

Spring Boot的配置体系强大而复杂,其中最基础、最核心的就是自动配置(Auto-Configuration)机制。本节我们就围绕这个话题展开详细讨论,看看Spring Boot如何实现自动配置。让我们先从@SpringBootApplication注解开始讲起。

1. @SpringBootApplication注解

@SpringBootApplication注解位于spring-boot-autoconfigure工程的
org.springframework.boot.autoconfigure包中,其定义如代码清单13-1所示。

代码清单13-1 @SpringBootApplication注解定义代码

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,

classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM,

classes = AutoConfigurationExcludeFilter.class) })

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

}

相较于一般的注解,@SpringBootApplication注解显得有点复杂。我们可以通过exclude和excludeName属性来配置不需要自动装配的类或类名,也可以通过scanBasePackages和scanBasePackageClasses属性来配置需要扫描的包路径和类路径。

注意,@SpringBootApplication注解实际上由三个注解组合而成,分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

@ComponentScan注解不是被Spring Boot引入的新注解,而是属于Spring容器管理的内容。@ComponentScan注解就是扫描所有@Component等注解标注的包下的、需要注入的类,并把相关Bean定义批量加载到容器中。

显然,Spring Boot应用程序同样需要这个功能。而@SpringBootConfiguration注解比较简单,事实上它是一个空注解,只是使用了Spring中的@Configuration注解。@Configuration注解比较常见,提供了JAVAConfig配置类。

@EnableAutoConfiguration注解是需要重点剖析的对象,该注解的定义如代码清单13-2所示。

代码清单13-2 @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(
AutoConfiguration-ImportSelector.class)。

(1)@AutoConfigurationPackage注解

从命名上讲,我们可以对@AutoConfigurationPackage注解所在包下的类进行自动配置,而在实现上用到了Spring中的@Import注解。

@AutoConfigurationPackage注解的定义如代码清单13-3所示。

代码清单13-3 @AutoConfigurationPackage注解定义代码

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import(AutoConfigurationPackages.Registrar.class)

public @interface AutoConfigurationPackage {

}

在使用Spring Boot时,@Import也是一个非常常见的注解,可以动态创建Bean。为了便于理解后续内容,这里有必要对@Import注解的运行机制展开讲解,该注解的定义如代码清单13-4所示。

代码清单13-4 @Import注解定义代码

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Import {

Class<?>[] value();

}

在@Import注解的属性中可以设置需要引入的类名,例如@AutoConfigurationPackage注解上的@Import(
AutoConfigurationPackages.Registrar.class)就引入了AutoConfiguration-Packages包下的Registrar类。根据所引入类的不同类型,Spring容器对@Import注解有以下四种处理方式。

如果该类实现了ImportSelector接口,Spring容器就会实例化该类,并且调用其selectImports()方法完成类的导入。

如果该类实现了DeferredImportSelector接口,则Spring容器也会实例化该类并调用其selectImports()方法。DeferredImportSelector继承了ImportSelector,区别在于DeferredImportSelector实例的selectImports()方法的调用时机晚于ImportSelector实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用。

如果该类实现了
ImportBeanDefinitionRegistrar接口,Spring容器就会实例化该类,并且调用其registerBeanDefinitions()方法。

如果该类没有实现上述三种接口中的任何一个,Spring容器就会直接实例化该类。

对@Import注解有了基本理解后,我们来看
AutoConfigurationPackages.Registrar类,该类定义如代码清单13-5所示。

代码清单13-5 
AutoConfigurationPackages.Registrar类实现代码

static class Registrar implements ImportBeanDefinitionRegistrar,

DeterminableImports {

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

register(registry, new

PackageImport(metadata).getPackageName());

}

@Override

public Set<Object> determineImports(AnnotationMetadata metadata) {

return Collections.singleton(new PackageImport(metadata));

}

}

可以看到Registrar类实现了前面第三种情况中提到的
ImportBeanDefinitionRegistrar接口并重写了registerBeanDefinitions()方法,该方法调用了AutoConfigurationPackages自身的register()方法,如代码清单13-6所示。

代码清单13-6 AutoConfigurationPackages中的register()方法代码

public static void register(BeanDefinitionRegistry registry, String...

packageNames) {

if (registry.contAInsBeanDefinition(BEAN)) {

BeanDefinition beanDefinition =

registry.getBeanDefinition(BEAN);

ConstructorArgumentValues constructorArguments =

beanDefinition.getConstructorArgumentValues();

constructorArguments.addIndexedArgumentValue(0,

addBasePackages(constructorArguments, packageNames));

}

else {

GenericBeanDefinition beanDefinition = new

GenericBeanDefinition();

beanDefinition.setBeanClass(BasePackages.class);

beanDefinition.getConstructorArgumentValues()

.addIndexedArgumentValue(0, packageNames);

beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

registry.registerBeanDefinition(BEAN, beanDefinition);

}

}

这个方法的逻辑是先判断这个Bean有没有被注册,如果已经被注册则获取Bean的定义,进而获取构造函数的参数并添加参数值;如果没有,则创建一个新的Bean定义,设置Bean的类型为AutoConfigurationPackages类型并进行Bean的注册。

(2)@Import(AutoConfigurationImportSelector)

然后我们来看@EnableAutoConfiguration注解中的@Import(
AutoConfigurationImportSel-ector.class)部分。首先我们明确AutoConfigurationImportSelector类实现了@Import注解第二种情况中的DeferredImportSelector接口,所以会执行如代码清单13-7所示的selectImports()方法。

代码清单13-7 
AutoConfigurationImportSelector中的selectImports()方法代码

@Override

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return NO_IMPORTS;

}

AutoConfigurationMetadata autoConfigurationMetadata =

AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

AnnotationAttributes attributes =

getAttributes(annotationMetadata);

//获取configurations集合

List<String> configurations =

getCandidateConfigurations(annotationMetadata, attributes);

configurations = removeDuplicates(configurations);

Set<String> exclusions = getExclusions(annotationMetadata,

attributes);

checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = filter(configurations,

autoConfigurationMetadata);

fireAutoConfigurationImportEvents(configurations, exclusions);

return StringUtils.toStringArray(configurations);

}

这段代码的核心是通过
getCandidateConfigurations()方法获取configurations集合并进行过滤。getCandidateConfigurations()方法如代码清单13-8所示。

代码清单13-8 
AutoConfigurationImportSelector中的getCandidateConfigurations()方法代码

protected List<String> getCandidateConfigurations(AnnotationMetadata

metadata, AnnotationAttributes attributes) {

List<String> configurations =

SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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;

}

在这段代码中,我们先关注这个Assert校验,该校验是一个非空校验,会提示“在META-INF/spring.factories中没有找到自动配置类”这个异常信息。讲到这里,不得不提到JDK中的SPI机制,因为SpringFactoriesLoader这个类的命名和META-INF/spring.factories这个文件目录,存在很大的相通性。关于JDK中的SPI机制,我们马上在后续介绍到。

从类名上看,
AutoConfigurationImportSelector类是一种选择器,负责从各种配置项中找到需要导入的具体配置类。该类的类层结构如图13-1所示。

 

图13-1 AutoConfigurationImportSelector类层结构图

显然,
AutoConfigurationImportSelector所依赖的最关键组件就是SpringFactoriesLoader,下面我们对其展开具体讨论。

2. SPI机制和SpringFactoriesLoader

要想理解SpringFactoriesLoader类,首先需要了解JDK中的SPI(Service Provider Interface,服务提供者接口)机制。

(1)JDK中的SPI机制JDK提供了一个工具类java.util.ServiceLoader来实现SPI机制,该类用于实现服务查找和加载。当服务提供者提供了服务接口的一种实现之后,我们可以在JAR包的META-INF/services/目录下创建一个以该服务接口命名的文件,并在这个文件中配置一组Key-Value,用于指定服务接口与其具体实现类的映射关系。当外部程序装配这个JAR包时,它就能通过该JAR包METAINF/services/目录中的配置文件找到具体的实现类名,并装载实例化,从而完成目标服务的注入。SPI提供了一种约定,基于该约定就能很有效地找到服务接口的实现类,而无须硬编码指定。JDK中SPI机制开发流程如图13-2所示。

图13-2 JDK中SPI机制开发流程图

(2)SpringFactoriesLoader

SpringFactoriesLoader与JDK中的SPI机制类似,只不过以服务接口命名的文件是放在META-INF/spring.factories文件夹下,对应的Key为EnableAutoConfiguration。Spring-FactoriesLoader会查找所有在METAINF/spring.factories文件夹中的配置文件,并把Key为EnableAutoConfiguration的对应配置项通过反射实例化为配置类,并加载到容器中。我们可以在如代码清单13-9所示的loadSpringFactories()方法中印证这一点。

代码清单13-9 SpringFactoriesLoader中loadSpringFactories()方法代码

private static Map<String, List<String>> loadSpringFactories(@Nullable

ClassLoader classLoader) {

MultiValueMap<String, String> result = cache.get(classLoader);

if (result != null) {

return result;

}

try {

Enumeration<URL> urls = (classLoader != null ?

classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

result = new LinkedMultiValueMap<>();

while (urls.hasMoreElements()) {

URL url = urls.nextElement();

UrlResource resource = new UrlResource(url);

Properties properties =

PropertiesLoaderUtils.loadProperties(resource);

for (Map.Entry<?, ?> entry : properties.entrySet()) {

String factoryClassName = ((String)

entry.getKey()).trim();

for (String factoryName :

StringUtils.commaDelimitedListToStringArray((String)

entry.getValue())) {

result.add(factoryClassName, factoryName.trim());

}

}

}

cache.put(classLoader, result);

return result;

}

catch (IOException ex) {

...

}

}

代码清单13-10所示的就是spring-boot-autoconfigure工程中所使用的spring.factories配置文件片段,可以看到在
org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项中有各式各样的配置项,这些配置项在Spring Boot启动过程中能够通过Spring-FactoriesLoader加载到运行时环境,从而实现自动化配置。

代码清单13-10 spring.factories配置文件片段

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmx

AutoConfiguration,

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,

org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,

org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigur

ation,

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,

org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfigur

ation,

org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,

org.springframework.boot.autoconfigure.context.ConfigurationProperties

AutoConfiguration,

...

以上就是Spring Boot基于@SpringBootApplication注解实现自动配置的基本过程和原理。当然,@SpringBootApplication注解也可以通过外部配置文件加载配置信息。基于约定优于配置的思想,Spring Boot在加载外部配置文件的过程中大量使用了默认配置。

3. @ConditionalOn系列条件注解

Spring Boot默认提供了100多个AutoConfiguration类,显然我们不可能全部引入。所以在自动装配时,Spring Boot会去类路径下寻找是否有对应的配置类。如果有配置类,则按条件进行判断,决定是否需要装配。这就引出了在阅读Spring Boot源码时经常会碰到的另一批注解——@ConditionalOn系列条件注解。

(1)@ConditionalOn系列条件注解的示例

先通过一个简单的示例来了解@ConditionalOn系列条件注解的使用方式,代码清单13-11所示的
ConfigServicePropertySourceLocator类就是其中一种典型应用,该代码位于Spring Cloud Config的客户端代码工程springcloud-config-client中。

代码清单13-11 
ConfigServicePropertySourceLocator类代码

@Bean

@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)

@ConditionalOnProperty(value = "spring.cloud.config.enabled",

matchIfMissing = true)

public ConfigServicePropertySourceLocator

configServicePropertySource(ConfigClientProperties properties) {

ConfigServicePropertySourceLocator locator = new

ConfigServicePropertySourceLocator(properties);

return locator;

}

可以看到这里用到了两个@ConditionalOn注解,一个是@ConditionalOnMissingBean,另一个是@ConditionalOnProperty。再比如在Spring Cloud Config的服务器端代码工程
spring-cloud-config-server中,存在代码清单13-12所示的ConfigServerAutoConfiguration自动配置类。

代码清单13-12 
ConfigServerAutoConfiguration自动配置类代码

@Configuration

@ConditionalOnBean(ConfigServerConfiguration.Marker.class)

@EnableConfigurationProperties(ConfigServerProperties.class)@Import({ EnvironmentRepositoryConfiguration.class,

CompositeConfiguration.class, ResourceRepositoryConfiguration.class,

ConfigServerEncryptionConfiguration.class,

ConfigServerMvcConfiguration.class })

public class ConfigServerAutoConfiguration {

}

这里用到了@ConditionalOnBean注解。实际上,Spring Boot提供了一系列条件注解,常见的如下。

@ConditionalOnProperty:只有当提供的属性属于true时才会实例化Bean。

@ConditionalOnBean:只有在当前上下文中存在某个对象时才会实例化Bean。

@ConditionalOnClass:只有当某个Class位于类路径上时才会实例化Bean。

@ConditionalOnExpression:只有当表达式为true时才会实例化Bean。

@ConditionalOnMissingBean:只有在当前上下文中不存在某个对象时才会实例化Bean。

@ConditionalOnMissingClass:只有当某个Class在类路径上不存在时才会实例化Bean。

@
ConditionalOnNotWebApplication:只有当不是Web应用时才会实例化Bean。

当然,Spring Boot还提供了一系列不大常用的@ConditionalOnxxx注解,这些注解都定义在
org.springframework.boot.autoconfigure.condition包中。

基于@ConditionalOn系列注解,我们明确了上述
ConfigServicePropertySourceLocator类只有在spring.cloud.config.enabled属性为true(通过matchIfMissing配置项则默认为true),以及类路径上不存在ConfigServicePropertySourceLocator时才会实例化。而ConfigServer-AutoConfiguration只有在类路径上存在ConfigServerConfiguration.Marker类时才会实例化。这是常用的自动配置控制技巧。

(2)@ConditionalOn系列条件注解的实现原理@ConditionalOn系列条件注解非常多,我们不对所有注解展开讲解。事实上这些注解的实现原理大致相同,我们只需要深入了解其中一个就能做到举一反三。这里我们挑选具有代表性的@ConditionalOnClass注解展开讲解,该注解的定义如代码清单13-13所示。

代码清单13-13 @ConditionalOnClass注解代码

@Target({ ElementType.TYPE, ElementType.METHOD })

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional(OnClassCondition.class)

public @interface ConditionalOnClass {

Class<?>[] value() default {};

String[] name() default {};

}

可以看到@ConditionalOnClass注解本身带有两个属性,一个是Class类型的value,另一个是String类型的name,我们可以采用这两个属性中的任意一个来使用该注解。同时,@ConditionalOnClass注解本身还带了一个@Conditional(OnClassCondition.class)注解。所以,@ConditionalOnClass注解的判断条件其实就包含在OnClassCondition这个类中。

OnClassCondition是SpringBootCondition的子类,而SpringBootCondition又实现了Condition接口。Condition接口只有一个matches()方法,如代码清单13-14所示。

代码清单13-14 Condition接口代码

public interface Condition {

boolean matches(ConditionContext context, AnnotatedTypeMetadata

metadata);

}

SpringBootCondition中的matches()方法如代码清单13-15所示。

代码清单13-15 SpringBootCondition的matches()方法代码

@Override

public final boolean matches(ConditionContext context,

AnnotatedTypeMetadata metadata) {

String classOrMethodName = getClassOrMethodName(metadata);

try {

ConditionOutcome outcome = getMatchOutcome(context, metadata);

logOutcome(classOrMethodName, outcome);

recordEvaluation(context, classOrMethodName, outcome);

return outcome.isMatch();

}

//省略其他方法

}

这里的getClassOrMethodName()方法用于获取被添加了@ConditionalOnClass注解的类或者方法的名称,而getMatchOutcome()方法用于获取匹配的输出。我们可以看到getMatchOutcome()方法实际上是一个抽象方法,需要通过SpringBootCondition的各个子类完成实现,这里的子类就是OnClassCondition类。在理解OnClassCondition时,我们要明白在SpringBoot中,@ConditionalOnClass注解或者@ConditionalOnMissingClass注解对应的条件类都是OnClassCondition,所以OnClassCondition的getMatchOutcome()会同时处理两种情况。

这里我们挑选出处理@ConditionalOnClass注解的代码,其核心逻辑如代码清单13-16所示。

代码清单13-16 @ConditionalOnClass注解的核心逻辑代码

ClassLoader classLoader = context.getClassLoader();

ConditionMessage matchMessage = ConditionMessage.empty();

List<String> onClasses = getCandidates(metadata,

ConditionalOnClass.class);

if (onClasses != null) {

List<String> missing = getMatches(onClasses, MatchType.MISSING,

classLoader);

if (!missing.isEmpty()) {

return

ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnCl

ass.class).didNotFind("required class", "required

classes").items(Style.QUOTE, missing));

}

matchMessage =

matchMessage.andCondition(ConditionalOnClass.class).found("required

class", "required classes").items(Style.QUOTE, getMatches(onClasses,

MatchType.PRESENT, classLoader));

}

这里有两个方法值得注意,一个是getCandidates()方法,另一个是getMatches()方法。首先,我们通过getCandidates()方法获取了ConditionalOnClass的name属性和value属性。然后通过getMatches()方法将这些属性值进行比对,得到这些属性所指定的、但在类加载器中还不存在的类。如果发现类加载器中应该存在但事实上又不存在的类,则返回匹配失败的Condition;反之,如果类加载器中存在对应类的话,则把匹配信息进行记录并返回ConditionOutcome。

基于Starter集成Spring Boot案例分析

在本节中,我们将给出在业务系统中基于Starter集成Spring Boot的案例分析。与前面所有介绍过的案例不同,本案例并不是从零开始实现一个Spring Boot Starter组件,而是参考业界主流的开源框架,通过源码解析来深入理解Spring Boot Starter的实现方式。

我们选择的开源框架是分布式数据库中间件ShardingSphere。作为Apache的顶级项目,ShardingSphere提供了一系列应对海量数据的方案,包括分库分表、读写分离、分布式事务、代理服务器等。

接下来,我们来看ShardingSphere实现一个自定义Spring Boot Starter的过程。在4.x版本中,ShardingSphere提供了sharding-jdbc-spring-bootstarter和
sharding-jdbc-orchestration-spring-boot-starter这两个Starter工程。篇幅关系,我们只关注sharding-jdbc-spring-boot-starter工程。

1. SpringBootConfiguration中的注解

接下来,我们就来看这个
sharding-jdbc-spring-boot-starter工程中的SpringBootConfiguration类,首先关注加在该类上的各种注解,如代码清单13-17所示。

代码清单13-17 SpringBootConfiguration注解定义代码

@Configuration

@ComponentScan("org.apache.shardingsphere.spring.boot.converter")

@EnableConfigurationProperties({

SpringBootShardingRuleConfigurationProperties.class,

SpringBootMasterSlaveRuleConfigurationProperties.class,

SpringBootEncryptRuleConfigurationProperties.class,

SpringBootPropertiesConfigurationProperties.class})

@ConditionalOnProperty(prefix = "spring.shardingsphere", name =

"enabled", havingValue = "true", matchIfMissing = true)

@AutoConfigureBefore(DataSourceAutoConfiguration.class)

@RequiredArgsConstructor

public class SpringBootConfiguration implements EnvironmentAware首先,我们看到熟悉的@Configuration注解和@ComponentScan注解。注意,这里通过@ComponentScan注解扫描的包路径位于另一个代码工程sharding-spring-boot-util的
org.apache.shardingsphere.spring.boot.converter包中。

然后,我们看到@
EnableConfigurationProperties注解,该注解的作用就是使添加了@ConfigurationProperties注解的类生效。在Spring Boot中,如果一个类只使用了@ConfigurationProperties注解,并且没有在扫描路径下或者没有使用@Component等注解,就无法被扫描为一个有效的Bean。

这时候就必须在配置类上使用@
EnableConfiguration-Properties注解指定这个类,才能使@ConfigurationProperties注解生效,并作为一个Bean被添加进Spring容器中。这里的@EnableConfigurationProperties注解包含了四个具体的ConfigurationProperties。以SpringBootShardingRuleConfigurationProperties为例,该类的定义如代码清单13-18所示。可以看到,这里直接继承了sharding-core-common代码工程中用于设置分片规则的YamlShardingRuleConfiguration配置类。

代码清单13-18 
SpringBootShardingRuleConfigurationProperties类代码

@ConfigurationProperties(prefix = "spring.shardingsphere.sharding")

public class SpringBootShardingRuleConfigurationProperties extends

YamlShardingRuleConfiguration {

}

添加到SpringBootConfiguration的下一个注解是@ConditionalOnProperty,该注解只有当所提供的属性属于true时才会实例化Bean。

最后一个与自动加载相关的注解是@AutoConfigureBefore。如果该注解被添加在类名上,其作用是标识在加载当前类之前需要加载注解中所设置的配置类。基于这一点,我们明确在加载SpringBootConfiguration类之前,Spring Boot会先加载
DataSource-AutoConfiguration。这一步的作用与我们后面要看到的各种DataSource创建过程相关。

2. SpringBootConfiguration的功能

介绍完这些注解之后,我们来看一下SpringBootConfiguration类提供的功能。

我们知道对于ShardingSphere而言,其对外的入口实际上就是各种DataSource。因此,SpringBootConfiguration提供了一批创建不同DataSource的入口方法,例如代码清单13-19所示的shardingDataSource()方法。

代码清单13-19 shardingDataSource()方法代码

@Bean

@Conditional(ShardingRuleCondition.class)

public DataSource shardingDataSource() throws SQLException {

return ShardingDataSourceFactory.createDataSource(dataSourceMap,

new ShardingRuleConfigurationYamlSwapper().swap(shardingRule),

props.getProps());

}

该方法添加了两个注解,一个是常见的@Bean,另一个则是@Conditional注解,该注解的作用是只有满足指定条件才能加载这个Bean。我们看到在@Conditional注解中设置了一个ShardingRuleCondition,该类如代码清单13-20所示。

代码清单13-20 ShardingRuleCondition类代码

public final class ShardingRuleCondition extends SpringBootCondition { @Override

public ConditionOutcome getMatchOutcome(final ConditionContext

conditionContext, final AnnotatedTypeMetadata annotatedTypeMetadata) {

boolean isMasterSlaveRule = new

MasterSlaveRuleCondition().getMatchOutcome(conditionContext,

annotatedTypeMetadata).isMatch();

boolean isEncryptRule = new

EncryptRuleCondition().getMatchOutcome(conditionContext,

annotatedTypeMetadata).isMatch();

return isMasterSlaveRule || isEncryptRule ?

ConditionOutcome.noMatch("Have found master-slave or encrypt rule in

environment") : ConditionOutcome. match();

}

}

可以看到ShardingRuleCondition是一个标准的SpringBootCondition,实现了13.1.1节介绍的getMatchOutcome()抽象方法。我们知道SpringBootCondition代表一种用于注册类或加载Bean的条件,而ShardingRuleCondition类在实现上分别调用了MasterSlaveRuleCondition和EncryptRuleCondition来判断是否满足这两个SpringBootCondition。显然,对于ShardingRule-Condition而言,只有在两个条件都不满足的情况下才应该被加载。对于masterSlaveData-Source()和encryptDataSource()这两个方法而言,处理逻辑也类似,不做赘述。

最后,我们注意到SpringBootConfiguration还实现了Spring的EnvironmentAware接口。在Spring中,当一个类实现了EnvironmentAware接口并重写了其中的setEnvironment()方法之后,在代码工程启动时就可以获得application.properties配置文件中各个配置项的属性值。

SpringBootConfiguration中所重写的setEnvironment()方法如代码清单13-21所示。

代码清单13-21 SpringBootConfiguration的setEnvironment()方法代码

@Override

public final void setEnvironment(final Environment environment) {

String prefix = "spring.shardingsphere.datasource.";

for (String each : getDataSourceNames(environment, prefix)) {

try {

dataSourceMap.put(each, getDataSource(environment, prefix,

each));

} catch (...) {

...

}

}

}

这里的代码逻辑是获取
spring.shardingsphere.datasource.name或spring.shardingsphere.datasource.names配置项,然后根据该配置项中所指定的DataSource信息构建新的Data-Source并加载到dataSourceMap这个LinkedHashMap中。这点我们可以结合Sharding-Sphere的常用配置项来加深理解,如代码清单13-22所示。可以看到,这里我们定义了两个DataSource。

代码清单13-22 ShardingSphere常用配置项

spring.shardingsphere.datasource.names=ds0,ds1

spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.Druid

DataSource

spring.shardingsphere.datasource.ds0.driver-class

name=com.MySQL.jdbc.Driver

spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost/ds0

spring.shardingsphere.datasource.ds0.username=root

spring.shardingsphere.datasource.ds0.password=root

spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.Druid

DataSource

spring.shardingsphere.datasource.ds1.driver-class

name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost/ds1

spring.shardingsphere.datasource.ds1.username=root

spring.shardingsphere.datasource.ds1.password=root

至此,对整个SpringBootConfiguration的实现过程介绍完毕,ShardingSphere基于Starter机制完成了与Spring Boot的集成。



Tags:SpringBootStarter   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
SpringBootStarter原理与应用
SpringBoot生态体系及扩展在应用程序开发过程中,很难通过一个框架就实现所有的功能需求,也不存在满足各种应用场景的统一开发框架。尤其近几年来,随着微服务、云原生等概念的落...【详细内容】
2023-08-21  Search: SpringBootStarter  点击:(358)  评论:(0)  加入收藏
保姆级,使用 KotlinScript 构建 SpringBootStarter
因业务需要, 公司内需要使用 SpringBoot Starter 构建 SDK. 不同的是使用了更为灵活的 Kotlin 语言, 构建脚本也换成了 Kotlin Script. 框架: SpringBoot 业务代码语言: Kot...【详细内容】
2022-09-27  Search: SpringBootStarter  点击:(333)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(10)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条