1. 程式人生 > >Spring Boot入門教程(三十一): 自定義Starter

Spring Boot入門教程(三十一): 自定義Starter

在springboot中,使用的最多的就是starter。starter可以理解為一個可拔插式的外掛,例如,你想使用jdbc外掛,那麼可以使用spring-boot-starter-jdbc;如果想使用mongodb,可以使用spring-boot-starter-data-mongodb。 自定義starter

  • 自定義starter
  • 使用starter

這裡寫圖片描述

1. 建立一個maven工程(maven-archetype-quickstart)

注意artifactId的命名規則,Spring官方Starter通常命名為spring-boot-starter-{name}如 spring-boot-starter-web, Spring官方建議非官方Starter命名應遵循{name}-spring-boot-starter的格式, 如mybatis-spring-boot-starter。這裡建立的專案的artifactId為helloworld-spring-boot-starter

2. 引入必要的依賴

注意:這裡的packaging為jar,starter需要使用到Spring boot的自動配置功能,所以需要引入自動配置相關的依賴

<groupId>com.mengday</groupId>
<artifactId>helloworld-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
    <dependency
>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId
>
spring-boot-configuration-processor</artifactId> <version>2.0.0.RELEASE</version> <optional>true</optional> </dependency> </dependencies>

3. XxxProperties

在使用Spring官方的Starter時通常可以在application.properties中來配置引數覆蓋掉預設的值,例如在使用redis時一般就會有對應的RedisProperties

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
}

我們來模仿來定義自己的Properties類

@ConfigurationProperties(prefix = "spring.person")
public class PersonProperties {
    // 姓名
    private String name;
    // 年齡
    private int age;
    // 性別
    private String sex = "M";

    // Getter & Setter
}    

4. 核心服務類

每個starter都有自己的功能,例如在spring-boot-starter-jdbc中最重要的類時JdbcTemplate,每個starter中的核心業務類明白都不同,也沒什麼規律(像spring-boot-starter-data-xxx的命名是比較有規律的),這裡使用PersonService來定義helloworld-spring-boot-starter的功能,這裡通過一個sayHello來模擬一個功能。

public class PersonService {

    private PersonProperties properties;

    public PersonService() {
    }

    public PersonService(PersonProperties properties) {
        this.properties = properties;
    }

    public void sayHello(){
        System.out.println("大家好,我叫: " + properties.getName() + ", 今年" + properties.getAge() + "歲"
            + ", 性別: " + properties.getSex());
    }
}

5. 自動配置類

一般每個starter都至少會有一個自動配置類,一般命名規則使用XxxAutoConfiguration, 例如RedisAutoConfiguration

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected static class RedisConnectionConfiguration {
        private final RedisProperties properties;

        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }
    }       

    @Configuration
    protected static class RedisConfiguration {
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
                        throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }

}

這裡我們定義自己的自動配置PersonServiceAutoConfiguration,並將核心功能類PersonService放入到Spring Context容器中

@Configuration
@EnableConfigurationProperties(PersonProperties.class)
@ConditionalOnClass(PersonService.class)
@ConditionalOnProperty(prefix = "spring.person", value = "enabled", matchIfMissing = true)
public class PersonServiceAutoConfiguration {

    @Autowired
    private PersonProperties properties;

    @Bean
    @ConditionalOnMissingBean(PersonService.class)  // 當容器中沒有指定Bean的情況下,自動配置PersonService類
    public PersonService personService(){
        PersonService personService = new PersonService(properties);
        return personService;
    }
}
  • @ConditionalOnClass:當類路徑classpath下有指定的類的情況下進行自動配置
  • @ConditionalOnMissingBean:當容器(Spring Context)中沒有指定Bean的情況下進行自動配置
  • @ConditionalOnProperty(prefix = “example.service”, value = “enabled”, matchIfMissing = true),當配置檔案中example.service.enabled=true時進行自動配置,如果沒有設定此值就預設使用matchIfMissing對應的值
  • @ConditionalOnMissingBean,當Spring Context中不存在該Bean時。
  • @ConditionalOnBean:當容器(Spring Context)中有指定的Bean的條件下
  • @ConditionalOnMissingClass:當類路徑下沒有指定的類的條件下

  • @ConditionalOnExpression:基於SpEL表示式作為判斷條件

  • @ConditionalOnJava:基於JVM版本作為判斷條件
  • @ConditionalOnJndi:在JNDI存在的條件下查詢指定的位置
  • @ConditionalOnNotWebApplication:當前專案不是Web專案的條件下
  • @ConditionalOnWebApplication:當前專案是Web專案的條件下
  • @ConditionalOnResource:類路徑下是否有指定的資源
  • @ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個,或者在有多個Bean的情況下,用來指定首選的Bean

6.src/main/resources/META-INF/spring.factories

注意:META-INF是自己手動建立的目錄,spring.factories也是手動建立的檔案,在該檔案中配置自己的自動配置類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.mengday.helloworld.PersonServiceAutoConfiguration

7. 打包mvn clean install

8. 建立一個Spring Boot工程並引入依賴

<dependency>
    <groupId>com.mengday</groupId>
    <artifactId>helloworld-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

9.配置application.properties

spring.person.name=mengday
spring.person.age=28

10.test

import com.mengday.helloworld.PersonService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MystarterApplicationTests {

    @Autowired
    private PersonService personService;

    @Test
    public void testHelloWorld() {
        personService.sayHello();
    }
}

這裡寫圖片描述
從使用者的角度來看,自己並沒有將PersonService放入到Spring容器中,就直接來使用了,進行注入進來了。

總結下Starter的工作原理

  1. Spring Boot在啟動時掃描專案所依賴的JAR包,尋找包含spring.factories檔案的JAR包,
  2. 然後讀取spring.factories檔案獲取配置的自動配置類AutoConfiguration,
  3. 然後將自動配置類下滿足條件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context)
  4. 這樣使用者就可以直接用來注入,因為該類已經在容器中了

這裡寫圖片描述

  • @ConfigurationProperties: 註解主要用來把properties配置檔案轉化為對應的XxxProperties來使用的,並不會把該類放入到IOC容器中,如果想放入到容器中可以在XxxProperties上使用@Component來標註,也可以使用@EnableConfigurationProperties(XxxProperties.class)統一配置到Application上來,這種方式可以在Application上來統一開啟指定的屬性,這樣也沒必要在每個XxxProperties上使用@Component

  • @EnableConfigurationProperties(XxxProperties.class) 註解的作用是@ConfigurationProperties註解生效。如果只配置@ConfigurationProperties註解,在IOC容器中是獲取不到properties配置檔案轉化的bean的

local.host=127.0.0.1
local.port=8080

dev.host=192.168.0.1
dev.port=8888
@ConfigurationProperties(prefix="local")
public class LocalProperties {

    private String host;
    private String port;
}

@ConfigurationProperties(prefix="dev")
public class DevProperties {

    private String host;
    private String port;
}        
@SpringBootApplication
@EnableConfigurationProperties({LocalProperties.class, DevProperties.class})
public class MystarterApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(MystarterApplication.class, args);
        System.out.println(applicationContext.getBean(LocalProperties.class));
        System.out.println(applicationContext.getBean(DevProperties.class));
        applicationContext.close();
    }
}

如果在每個Properties上都使用@Component來標註,那麼在XxxApplication上也不需要使用@EnableConfigurationProperties({XxxProperties.class})來標註了,同樣也可以在spring上下文容器中也能獲取到XxxProperties對應的bean