前言
众所周知,Spring Boot由众多Starter组成,通常需要某个功能时,直接引入相应Starter,Spring Boot会自动进行相关配置,并将相应对象注入Spring IOC容器中,使用时直接用@Autowired引入即可,对于必须要配置的属性,如连接数据库的用户名和密码等,Spring Boot提供了约定的属性名,我们直接在application.properties或application.yml中配置即可。
Spring Boot Starter有什么神奇的呢?
带着好奇心下面我们来写一个自定义的Starter来一探究竟。
Starter命名问题
这里说下artifactId的命名问题,Spring 官方 Starter通常命名为spring-boot-starter-{name}如 spring-boot-starter-web, Spring官方建议非官方Starter命名应遵循{name}-spring-boot-starter的格式。
Spring 官方建议Starter格式
一个库的完整Spring Boot starter可能包含以下组件:
- autoconfigure模块:包含自动配置代码
- starter模块:整合autoconfigure模块和其他依赖库的依赖,总之,添加这个starter依赖就能使用这个库。通常是一个空jar。另外我们自己的Starter必须要直接或间接的引用官方的spring-boot-starter依赖。
可参考dubbo-spring-boot-starter
实现
这里讲一下我们的Starter要实现的功能,很简单,提供一个Service,包含一个能够将字符串加上前后缀的方法String wrap(String word)。
我这里并没有按照Spring官方的建议格式。
项目结构
1 2 3 4
| springboot-demo // 聚合工程 - autoconfig-test // 引入自定义的Starter的进行测试 - autoconfig-test-starter // 自定义的Starter - pom.xml
|
springboot-demo的pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.fuyi</groupId> <artifactId>springboot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging>
<modules> <module>autoconfig-test-starter</module> <module>autoconfig-test</module> </modules>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.14.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> </project>
|
autoconfig-test-starter项目
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<artifactId>autoconfig-test-starter</artifactId>
<parent> <groupId>com.fuyi</groupId> <artifactId>springboot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
|
1. 定义属性配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.fuyi.starter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** * 这个类定义了默认的属性值,如该类中,有两个属性prefix和suffix默认值分别为“www.”和“.com”. * <p> * {@ConfigurationProperties} 注解会定义一个前缀匹配,如果想修改属性值,<br> * 可以在application.properties中使用“前缀.属性=修改的值”进行修改。<br> * * @author fuyi * */ @ConfigurationProperties(prefix = "fuyi.service") public class FuyiProperties {
private String prefix = "www."; private String suffix = ".com"; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; }
}
|
2. 定义服务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.fuyi.starter;
/** * 服务类是指主要的功能类,如果没有SpringBoot,这些服务类在Spring中都是需要自己去配置生成的。 * <p> * 如Mybatis中的DataSource。 * <p> * 这里讲一下我们的Starter要实现的功能,很简单,提供一个Service,<br> * 包含一个能够将字符串加上前后缀的方法String wrap(String word)。 * * @author fuyi * */ public class FuyiService { private String prefix; private String suffix;
public FuyiService(String prefix, String suffix) { this.prefix = prefix; this.suffix = suffix; }
public String wrap(String word) { return prefix + word + suffix; } }
|
3. 定义自动配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.fuyi.starter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
/** * 自动配置类主要作用是SpringBoot的配置核心,它会写在MEAT-INF/spring.factories中<br> * 告知SpringBoot在启动时去读取该类并根据该类的规则进行配置。 * * @author fuyi * */ @Configuration @EnableConfigurationProperties(FuyiProperties.class) <4> @ConditionalOnClass(FuyiService.class) <1> @ConditionalOnProperty(prefix = "fuyi.service" , value = "enabled" , matchIfMissing = true)<2> public class FuyiServiceAutoConfiguration {
@Autowired private FuyiProperties fuyiProperties; @Bean @ConditionalOnMissingBean<3> public FuyiService autoConfig() { FuyiService fuyiService = new FuyiService(fuyiProperties.getPrefix(), fuyiProperties.getSuffix()); return fuyiService; } }
|
简单说下涉及的条件注解,后开专篇详解:
- @ConditionalOnClass(FuyiService.class):当classpath下存在FuyiService类时,才执行自动配置类FuyiServiceAutoConfiguration。
- @ConditionalOnProperty(prefix = “fuyi.service” , value = “enabled” , matchIfMissing = true):检查指定的属性有指定的值,此处配置为,如果没配置该属性默认true.
- @ConditionalOnMissingBean:当Spring IOC容器中不存在FuyiService实例时,才执行注解的方法。该方法则是生成一个FuyiService实例放入容器。
- @EnableConfigurationProperties注解根据FuyiProperties类开启属性注入,允许在application.properties修改里面的属性值的默认值。
1 2 3
| # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.fuyi.starter.FuyiServiceAutoConfiguration
|
好啦,搞定!下面可以使用maven install命令把starter存到本地,其他SpringBoot项目需要使用这个starter,直接导入就可以啦,然后可直接用@Autowired注入功能类FuyiService使用。
autoconfig-test项目
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>autoconfig-test</artifactId>
<parent> <groupId>com.fuyi</groupId> <artifactId>springboot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<dependencies> <!-- 引入自定义的Starter --> <dependency> <groupId>com.fuyi</groupId> <artifactId>autoconfig-test-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
Note: 引入spring-boot-starter-web的原因是:因为spring-boot-starter-test虽然会引入Spring Boot相关核心类, 但scope是test,只会在测试生命周期里存在,在运行时找不到Spring Boot相关核心类,引入spring-boot-starter-web用来引入运行时能存在的Spring Boot相关核心类。
1. 启动类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.fuyi.boot;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication public class BootApplication {
public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); }
}
|
2. 测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.fuyi.test;
import static org.assertj.core.api.Assertions.*; 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;
import com.fuyi.boot.BootApplication; import com.fuyi.starter.FuyiService;
@RunWith(SpringRunner.class) @SpringBootTest(classes = {BootApplication.class}) public class AppTest {
@Autowired private FuyiService fuyiService; @Test public void test1() { assertThat(fuyiService.wrap("fuyi")).isEqualTo("www.fuyi.com"); } }
|
测试通过!
3. 测试修改默认配置
在src/test/resources下application.properties文件中新增下面内容:
1 2
| fuyi.service.prefix=ftp. fuyi.service.suffix=.org
|
再执行原来的测试报错如下:
1
| org.junit.ComparisonFailure: expected:<"[www.fuyi.com]"> but was:<"[ftp.fuyi.org]">
|
很明显默认值已经被修改了。
总结下Starter的工作原理
- Spring Boot在启动时扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包
- 根据spring.factories配置加载AutoConfigure类
- 根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context