github: https://github.com/Ccww-lx/Sp...
模块:spring-boot-starter-base-service
SpringBoot的方便快捷主要体现之一 starter pom , Spring Boot 为我们提供了简化企业级开发绝大多数场景的
starter pom , 只要使用了应用场景所需要的 starter pom ,只需要引入对应的 starter 即可,即可以得到 Spring Boot 为我们提供的自动配置的 Bean 。
然而,可能在很多情况下,我们需要 自定义 stater ,这样可以方便公司内部系统调用共同的配置模块的时候可以自动进行装载配置。比如,很多公司将生产数据库的密码托管在公司的另外一个专门管理生产密码的系统上,公司每个系统需要使用的时候都需要调用其方法进行使用,现在可以通过 starter 自动配置的形式进行配置。
@SpringBootApplication 注解中核心注解 @EnableAutoConfiguration 注解在starter起什么作用呢? @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 {};
}
可以从源码看出关键功能是 @import 注解导入自动配置功能类 AutoConfigurationImportSelector 类,主要方法 getCandidateConfigurations() 使用了 SpringFactoriesLoader.loadFactoryNames() 方法加载META-INF/spring.factories的文件(spring.factories声明具体自动配置)。
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;
}
Q:通常情况下, starter 会根据条件进行操作处理,比如根据不同条件创建不同 Bean 。在 SpringBoot 有哪些注解可用呢? 可使用 org.springframwork.boot.autoconfigure.condition 的条件注解,具体如下所示:
| 注解 | 解析 |
|---|---|
| @ConditionalOnBean | 当容器里有指定的Bean的条件下。 |
| @ConditionalOnClass | 当类路径下有指定的类的条件下。 |
| @ConditionalOnExpression | 基于SpEL表达式作为判断条件。 |
| @ConditionalOnJava | 基于JVM版本作为判断条件。 |
| @ConditionalOnJndi | 在JNDI存在的条件下查找指定的位置。 |
| @ConditionalOnMissingBean | 当容器里没有指定Bean的情况下。 |
| @ConditionalOnMissingClass | 当类路径下没有指定的类的条件下。 |
| @ConditionalOnNotWebApplication | 当前项目不是Web项目的条件下。 |
| @ConditionalOnProperty | 指定的属性是否有指定的值。 |
| @ConditionalOnResource | 类路径是否有指定的值。 |
| @ConditionalOnSingleCandidate | 当指定Bean在容器中只有一个, 或者虽然有多个但是指定首选的Bean。 |
| @ConditionalOnWebApplicatio | 当前项目是Web项目的条件下。 |
在此将模拟公司获取生产密码模块进行自定义 starter demo
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
PasswordService 服务类:
public class PasswordService {
//第三方系统获取密码所需的key
private String objectKey;
@Autowired
//模拟的第三方系统service
private ThirdPartySystemService thirdPartySystemService;
public String getSystemPassword(String objectKey,String originalPassord){
if(StringUtils.isEmpty(objectKey)){
return originalPassord;
}
//从第三方系统获取密码
String password= thirdPartySystemService.getPassword(objectKey);
//返回密码
return password!=null?password:originalPassord;
}
}
//模拟第三方系统service
public class ThirdPartySystemService {
public String getPassword(String objectKey){
//返回一个32位随机数
return UUID.randomUUID().toString();
}
}
//通过@ConfigurationProperties注解获取属性值
@ConfigurationProperties(prefix = "project.starter")
public class BaseServiceProperties {
private String serviceName;
private String serviceVersion;
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceVersion() {
return serviceVersion;
}
public void setServiceVersion(String serviceVersion) {
this.serviceVersion = serviceVersion;
}
}
public class BaseStarterService {
public void addServiceName(BaseServiceProperties baseServiceProperties){
System.out.println("serviceName:"+baseServiceProperties.getServiceName()+"----"+"serviceVersion"+baseServiceProperties.getServiceVersion());
}
}
//判断是否windows系统
public class WindowsCondition implements Condition {
private final static String WINDOWS="Windows";
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获取当前环境变量
Environment environment=conditionContext.getEnvironment();
//获取bean注册器
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取环境变量中操作系统
String property = environment.getProperty("os.name");
//判断操作系统是否为windows
if(property.contains(WINDOWS)){
//判断是否存在baseWindowsSevice类,不存在则进行bean注册
boolean isWindowsSevice = registry.containsBeanDefinition("baseStarterService");
if(!isWindowsSevice){
//指定Bean定义信息;(Bean的类型,Bean的一系列信息)
RootBeanDefinition beanDefinition = new RootBeanDefinition(BaseStarterService.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("baseStarterService", beanDefinition);
BaseStarterService windowsSevice = (BaseStarterService)beanFactory.getBean("baseStarterService");
}
return true;
}
return false;
}
}
代码解读:
@EnableConfigurationProperties :读取配置文件的属性 @Import :导入其他配置类或者自定义类 @Conditional :判断当前环境是否为windows,是则注册该类 @ConditionalOnProperty :判断属性 spring.project.ThirdPartySystemService.isPassword 是否等于 true ,不为 true 则不注册该类 @ConditionalOnClass :判断IOC容器中是否存在 ThirdPartySystemService 类,存在则创建 PasswordService bean @Configuration
//自动加载配置文件属性值
@EnableConfigurationProperties(BaseServiceProperties.class)
@Import(BeanConfiguration.class)
//判断当前环境是否为windows
@Conditional(WindowsCondition.class):
//判断属性spring.project.ThirdPartySystemService.isPassword是否等于true
@ConditionalOnProperty(prefix = "spring.project.ThirdPartySystemService",value = "enablePassword", havingValue = "true",matchIfMissing = true)
public class AutoConfigurationPassoword {
@Autowired
private BaseServiceProperties baseServiceProperties;
@Autowired
private BaseStarterService baseWindowsService;
//加载第三方系统service
@Bean("thirdPartySystemService")
public ThirdPartySystemService thirdPartySystemService(){
baseWindowsService.addServiceName(baseServiceProperties);
return new ThirdPartySystemService();
}
@Bean
//判断IOC容器中是否存在ThirdPartySystemService类,存在则创建PasswordService bean
@ConditionalOnClass(ThirdPartySystemService.class)
public PasswordService passwordService(){
baseWindowsService.addServiceName(baseServiceProperties);
return new PasswordService();
}
}
src/main/resources 下新建 METAINF/spring.factories 。在 spring.factorie 配置如下: org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ com.cn.ccww.configuration.AutoConfigurationPassoword
若有多个自动配置, 则用“, ”隔开, 此处“”是为了换行后还能够读取到属性。
<dependencies>
<dependency>
<artifactId>spring-boot-starter-base-service</artifactId>
<groupId>com.cn.ccww</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
application.properties 文件有对应的字段是否启动自定义starter,还可以设置starter所需的属性。如下所示:
//自定义Starter配置 //当该属性的值不为true时,才不会启动自定义starter spring.project.ThirdPartySystemService.enablePassword=true project.starter.serviceName=ccww project.starter.serviceVersion=1.0
由上所述, starter 的大体的工作流程:
SpringBoot 启动时会自动搜索包含 spring.factories 文件的JAR包; spring.factories 文件加载自动配置类 AutoConfiguration ; AutoConfiguration 类,加载满足条件 (@ConditionalOnXxx) 的 bean 到 Spring IOC 容器中; IOC 的 bean 。