模塊:spring-boot-starter-base-service
SpringBoot的方便快捷主要體現之一starter pom
,Spring Boot
爲咱們提供了簡化企業級開發絕大多數場景的 starter pom
, 只要使用了應用場景所須要的starter pom
,只須要引入對應的starter
便可,便可以獲得Spring Boot
爲咱們提供的自動配置的Bean
。git
然而,可能在不少狀況下,咱們須要自定義stater
,這樣能夠方便公司內部系統調用共同的配置模塊的時候能夠自動進行裝載配置。好比,不少公司將生產數據庫的密碼託管在公司的另一個專門管理生產密碼的系統上,公司每一個系統須要使用的時候都須要調用其方法進行使用,如今能夠經過starter
自動配置的形式進行配置。github
Q:@SpringBootApplication
註解中核心註解@EnableAutoConfiguration
註解在starter起什麼做用呢?spring
@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聲明具體自動配置)。windows
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
的條件註解,具體以下所示:app
註解 | 解析 |
---|---|
@ConditionalOnBean | 當容器裏有指定的Bean的條件下。 |
@ConditionalOnClass | 當類路徑下有指定的類的條件下。 |
@ConditionalOnExpression | 基於SpEL表達式做爲判斷條件。 |
@ConditionalOnJava | 基於JVM版本做爲判斷條件。 |
@ConditionalOnJndi | 在JNDI存在的條件下查找指定的位置。 |
@ConditionalOnMissingBean | 當容器裏沒有指定Bean的狀況下。 |
@ConditionalOnMissingClass | 當類路徑下沒有指定的類的條件下。 |
@ConditionalOnNotWebApplication | 當前項目不是Web項目的條件下。 |
@ConditionalOnProperty | 指定的屬性是否有指定的值。 |
@ConditionalOnResource | 類路徑是否有指定的值。 |
@ConditionalOnSingleCandidate | 當指定Bean在容器中只有一個, 或者雖然有多個可是指定首選的Bean。 |
@ConditionalOnWebApplicatio | 當前項目是Web項目的條件下。 |
在此將模擬公司獲取生產密碼模塊進行自定義starter demo
dom
<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
服務類:spring-boot
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
。