【SpringBoot2.x】-自定義Spring boot Starter(原理、demo代碼實現以及解決面試問題)

github: github.com/Ccww-lx/Spr…
模塊:spring-boot-starter-base-service

   SpringBoot的方便快捷主要體現之一starter pomSpring Boot爲咱們提供了簡化企業級開發絕大多數場景的 starter pom, 只要使用了應用場景所須要的starter pom,只須要引入對應的starter便可,便可以獲得Spring Boot爲咱們提供的自動配置的Beangit

  然而,可能在不少狀況下,咱們須要自定義stater,這樣能夠方便公司內部系統調用共同的配置模塊的時候能夠自動進行裝載配置。好比,不少公司將生產數據庫的密碼託管在公司的另一個專門管理生產密碼的系統上,公司每一個系統須要使用的時候都須要調用其方法進行使用,如今能夠經過starter自動配置的形式進行配置。github

1. SpringBoot Starter源碼分析

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項目的條件下。

2. 自定starter

在此將模擬公司獲取生產密碼模塊進行自定義starter demodom

2.1 核心依賴

<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>
複製代碼

2.2 服務類service以及屬性配置注入

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;
    }
}
複製代碼

2.3自動配置類

代碼解讀:

  • @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();
    }
}
複製代碼

2.4 註冊配置

  想自動配置生效, 須要註冊自動配置類,即在src/main/resources下新建METAINF/spring.factories。在spring.factorie配置以下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cn.ccww.configuration.AutoConfigurationPassoword
複製代碼

如有多個自動配置, 則用「」隔開, 此處「\」是爲了換行後還可以讀取到屬性。

3. 測試自定義starter

3.1 import 依賴

<dependencies>
        <dependency>
            <artifactId>spring-boot-starter-base-service</artifactId>
            <groupId>com.cn.ccww</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencies>
複製代碼

3.2 application.properties屬性

application.properties文件有對應的字段是否啓動自定義starter,還能夠設置starter所需的屬性。以下所示:

//自定義Starter配置
//當該屬性的值不爲true時,纔不會啓動自定義starter
spring.project.ThirdPartySystemService.enablePassword=true
project.starter.serviceName=ccww
project.starter.serviceVersion=1.0
複製代碼

4.總結

由上所述, starter的大致的工做流程:

  • SpringBoot啓動時會自動搜索包含spring.factories文件的JAR包;

  • 根據spring.factories文件加載自動配置類AutoConfiguration

  • 經過AutoConfiguration類,加載知足條件(@ConditionalOnXxx)beanSpring IOC容器中;

  • 使用者能夠直接使用自動加載到IOCbean


最後可關注公衆號:【ccww筆記】 一塊兒學習,天天會分享乾貨,還有學習視頻領取!

相關文章
相關標籤/搜索