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