前言
Spring Boot 最大的特性可以说就是开箱即用,内部提供的默认自动配置功能,使得我们可以再 “零配置” 的情况下,就能够很方便地运行起应用。
这一切都要归功于启动器 starter, 比如说,想搭建一个 Spring Boot Web 项目,我们只需要添加 spring-boot-starter-web
依赖即可,可以在不做一行配置的情况下,启动一个 Tomcat 应用。
下面是我们常用的 starter
启动器:
- 单元测试:
spring-boot-starter-web
; - 数据库持久层框架 JPA:
spring-boot-starter-data-jpa
; - 安全框架:
spring-boot-starter-security
; - Redis 缓存:
spring-boot-starter-data-redis
; - …
用起来是爽了,但是也有人抱怨不如以前的 SSM 框架那样透明化,虽然配置是烦了点,但是我至少知道我开启了哪些功能,如今用了 Spring Boot 后,出了问题,压根不知道问题根源在哪里,蛋疼!
所以说,了解启动器 starter
默认地自动化配置是如何工作的,还是非常有必要的!本文以手写一个自定义的入门级 starter
, 带你了解其工作原理。
实践
需求描述
假设我们有这样一个需求:需要自定义一个女盆友 starter, 她带有一个问候的功能,要如何来实现呢?
新建 maven 项目
为其取名为:girl-friend-spring-boot-starter
.
Spring 官方对
starter
的命名是有规范的,只有官方提供的starter
, 才能命名为spring-boot-starter-{name}
, 比如spring-boot-starter-web
; 而对于非官方的,需以{name}-spring-boot-starter
的格式命名。
添加 spring-boot-autoconfigure 依赖
pom.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>site.exception</groupId>
<artifactId>girl-friend-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 自动化配置依赖,自定义 starter 核心依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
新增功能类
新增下面三个类:
-
GirlFriendServiceProperties
(配置类); -
GirlFriendService
(女盆友接口); -
GirlFriendServiceImpl
(女盆友接口实现);
上源码:
GirlFriendServiceProperties.java
:
package site.exception;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author www.excpetion.site(exception 教程网)
* @date 2019/1/30
* @time 11:22
* @discription
**/
@ConfigurationProperties(prefix = "spring.girlfriend")
public class GirlFriendServiceProperties {
/** 默认输出 */
private String message = "Hi, good morning !";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@ConfigurationProperties
注解能够自动获取 application.properties 配置文件中前缀为 spring.girlfriend
节点下 message
属性的内容,这里我们给了它一个默认值: Hi, good morning !
。
GirlFriendService.java
:
package site.exception;
/**
* @author www.excpetion.site(exception 教程网)
* @date 2019/1/30
* @time 11:07
* @discription
**/
public interface GirlFriendService {
/**
* 打招呼
* @return
*/
void say();
}
GirlFriendServiceImpl.java
:
package site.exception;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author www.excpetion.site(exception 教程网)
* @date 2019/1/30
* @time 11:07
* @discription
**/
public class GirlFriendServiceImpl implements GirlFriendService {
@Autowired
private GirlFriendServiceProperties girlFriendServiceProperties;
/**
* 打招呼
*
*/
@Override
public void say() {
String message = girlFriendServiceProperties.getMessage();
System.out.println("Girl Friend: " + message);
}
}
GirlFriendServiceImpl
实现了 GirlFriendService
接口的 say()
方法,它会获取 GirlFriendServiceProperties
配置类 message
内容,并打印到控制台。
硬菜:新增自动配置类
创建类 GirlFriendAutoConfiguration.java
来实现自动配置化功能:
package site.exception;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author www.excpetion.site(exception 教程网)
* @date 2019/1/30
* @time 11:11
* @discription
**/
@Configuration
@ConditionalOnClass(GirlFriendService.class)
@EnableConfigurationProperties(GirlFriendServiceProperties.class)
public class GirlFriendAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public GirlFriendService girlFriendService() {
return new GirlFriendServiceImpl();
}
}
接下来,对上面相关注解说明一下:
@Configuration
: 标注类为一个配置类,让 spring 去扫描它;@ConditionalOnClass
:条件注解,只有在classpath
路径下存在指定 class 文件时,才会实例化 Bean;@EnableConfigurationProperties
:使指定配置类生效;@Bean
: 创建一个实例类注入到 Spring Ioc 容器中;@ConditionalOnMissingBean
:条件注解,意思是,仅当 Ioc 容器不存在指定类型的 Bean 时,才会创建 Bean。
新增 spring.factories 文件
在 resources
目录下创建名为 META-INF
的目录,并新建文件 spring.factories
,内容如下:
# 指定刚刚创建的 GirlFriendAutoConfiguration 的全路径名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=site.exception.GirlFriendAutoConfiguration
Spring Boot 会在启动时,自动会去查找指定文件
/META-INF/spring.factories
,若有,就会根据配置的类的全路径去自动化配置。
完成这一步后,一个入门级的 spring-boot-starter
已经开发完成了,看下整体的目录结构:
打包 jar
接下来,就是将 girl-friend-spring-boot-starter
打成 jar
包,放到本地的 maven
仓库中去,在项目根路径下执行 maven 命令: mvn clean install
.
C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] -----------< site.exception:girl-friend-spring-boot-starter >-----------
[INFO] Building girl-friend-spring-boot-starter 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ girl-friend-spring-boot-starter ---
[INFO] Deleting C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ girl-friend-spring-boot-starter ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ girl-friend-spring-boot-starter ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ girl-friend-spring-boot-starter ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ girl-friend-spring-boot-starter ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ girl-friend-spring-boot-starter ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ girl-friend-spring-boot-starter ---
[INFO] Building jar: C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\target\girl-friend-spring-boot-starter-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ girl-friend-spring-boot-starter ---
[INFO] Installing C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\target\girl-friend-spring-boot-starter-1.0-SNAPSHOT.jar to C:\User
s\allen\.m2\repository\site\exception\girl-friend-spring-boot-starter\1.0-SNAPSHOT\girl-friend-spring-boot-starter-1.0-SNAPSHOT.jar
[INFO] Installing C:\dev\idea_workspace_personal\spring-boot-tutorial\girl-friend-spring-boot-starter\pom.xml to C:\Users\allen\.m2\repository\site\exception\girl-frien
d-spring-boot-starter\1.0-SNAPSHOT\girl-friend-spring-boot-starter-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.247 s
[INFO] Finished at: 2019-01-30T15:31:03+08:00
[INFO] ------------------------------------------------------------------------
新建一个 Spring Boot Web 项目,引用自定 starter
新创建一个 Spring Boot Web 项目,在 pom.xml
文件中加入自定义的 starter
依赖:
<!-- Girl Friend starter -->
<dependency>
<groupId>site.exception</groupId>
<artifactId>girl-friend-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
在 Applicaiton
启动类中,自动注入 GirlFriendService
实例,并调用 say()
方法:
package site.exception.springboothello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import site.exception.GirlFriendService;
@SpringBootApplication
public class SpringBootHelloApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootHelloApplication.class, args);
}
@Autowired
private GirlFriendService girlFriendService;
@Override
public void run(String... args) throws Exception {
// 调用打招呼方法
girlFriendService.say();
}
}
启动项目,看看在零配置的情况下,它的输出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.2.RELEASE)
2019-01-30 15:40:48.308 INFO 25320 --- [ restartedMain] s.e.s.SpringBootHelloApplication : Starting SpringBootHelloApplication on DESKTOP-RL6P6LA with PID 25320 (C:\dev\idea_workspace_personal\spring-boot-tutorial\spring-boot-hello\target\classes started by allen in C:\dev\idea_workspace_personal\spring-boot-tutorial)
2019-01-30 15:40:48.314 INFO 25320 --- [ restartedMain] s.e.s.SpringBootHelloApplication : No active profile set, falling back to default profiles: default
2019-01-30 15:40:48.457 INFO 25320 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2019-01-30 15:40:48.458 INFO 25320 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2019-01-30 15:40:51.299 INFO 25320 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-01-30 15:40:51.372 INFO 25320 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-01-30 15:40:51.373 INFO 25320 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.14]
2019-01-30 15:40:51.387 INFO 25320 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\dev\java\jdk1.8\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\dev\java\jdk1.8\bin;C:\dev\java\jdk1.8\jre\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\pycSafefile\x64;C:\dev\java\apache-maven-3.6.0\bin;D:\dev\redis\;C:\dev\git\cmd;C:\Users\allen\AppData\Local\Microsoft\WindowsApps;;C:\Program Files\Docker Toolbox;.]
2019-01-30 15:40:51.628 INFO 25320 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-01-30 15:40:51.629 INFO 25320 --- [ restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3171 ms
2019-01-30 15:40:51.921 INFO 25320 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-01-30 15:40:52.142 INFO 25320 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2019-01-30 15:40:52.204 INFO 25320 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-01-30 15:40:52.207 INFO 25320 --- [ restartedMain] s.e.s.SpringBootHelloApplication : Started SpringBootHelloApplication in 4.56 seconds (JVM running for 6.523)
Girl Friend: Hi, good morning !
可以看到,在没做任何配置的情况下,输出的是默认打招呼信息:Hi, good morning !,接下来,我们手动配置一下新的打招呼内容,看看是否能自动获取到。
在 application.properties
文件中配置如下:
spring.girlfriend.message=I LOVE YOU
重启项目,验证一下,是否输出的还是默认信息:
正确打印我们自己配置的:I LOVE YOU。
GitHub 源码地址
https://github.com/weiwosuoai/spring-boot-tutorial
总结
此文中,我们学习了如何自定义入门级 spring-boot-starter
,这仅仅是个开始,能做的远不止于此,这里也只是抛砖引玉而已,希望您阅读愉快!