前言

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

  • ....

如何在 Spring Boot 中自定义启动器 Starter_java

用起来是爽了,但是也有人抱怨不如以前的 SSM 框架那样透明化,虽然配置是烦了点,但是我至少知道我开启了哪些功能,如今用了 Spring Boot 后,出了问题,压根不知道问题根源在哪里,蛋疼!

所以说,了解启动器 starter 默认地自动化配置是如何工作的,还是非常有必要的!本文以手写一个自定义的入门级 starter, 带你了解其工作原理。

实践

需求描述

假设我们有这样一个需求:需要自定义一个女盆友 starter, 她带有一个问候的功能,要如何来实现呢?

新建 maven 项目

如何在 Spring Boot 中自定义启动器 Starter_java_02

为其取名为: 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 文件内容如下:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0"

  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  5.    <modelVersion>4.0.0</modelVersion>


  6.    <parent>

  7.        <groupId>org.springframework.boot</groupId>

  8.        <artifactId>spring-boot-starter-parent</artifactId>

  9.        <version>2.1.2.RELEASE</version>

  10.        <relativePath/> <!-- lookup parent from repository -->

  11.    </parent>

  12.    <groupId>site.exception</groupId>

  13.    <artifactId>girl-friend-spring-boot-starter</artifactId>

  14.    <version>1.0-SNAPSHOT</version>


  15.    <properties>

  16.        <java.version>1.8</java.version>

  17.    </properties>


  18.    <dependencies>

  19.        <!-- 自动化配置依赖,自定义 starter 核心依赖包 -->

  20.        <dependency>

  21.            <groupId>org.springframework.boot</groupId>

  22.            <artifactId>spring-boot-autoconfigure</artifactId>

  23.        </dependency>

  24.    </dependencies>


  25. </project>

新增功能类

新增下面三个类:


  • GirlFriendServiceProperties(配置类);



  • GirlFriendService (女盆友接口);



  • GirlFriendServiceImpl(女盆友接口实现);


上源码:

GirlFriendServiceProperties.java:

  1. package site.exception;


  2. import org.springframework.boot.context.properties.ConfigurationProperties;


  3. /**

  4. * @author www.excpetion.site(exception 教程网)

  5. * @date 2019/1/30

  6. * @time 11:22

  7. * @discription

  8. **/

  9. @ConfigurationProperties(prefix = "spring.girlfriend")

  10. public class GirlFriendServiceProperties {


  11.    /** 默认输出 */

  12.    private String message = "Hi, good morning !";


  13.    public String getMessage() {

  14.        return message;

  15.    }


  16.    public void setMessage(String message) {

  17.        this.message = message;

  18.    }

  19. }

@ConfigurationProperties注解能够自动获取 application.properties 配置文件中前缀为 spring.girlfriend 节点下 message属性的内容,这里我们给了它一个默认值: Hi,good morning!

GirlFriendService.java:

  1. package site.exception;


  2. /**

  3. * @author www.excpetion.site(exception 教程网)

  4. * @date 2019/1/30

  5. * @time 11:07

  6. * @discription

  7. **/

  8. public interface GirlFriendService {

  9.    /**

  10.     * 打招呼

  11.     * @return

  12.     */

  13.    void say();

  14. }

GirlFriendServiceImpl.java:

  1. package site.exception;


  2. import org.springframework.beans.factory.annotation.Autowired;


  3. /**

  4. * @author www.excpetion.site(exception 教程网)

  5. * @date 2019/1/30

  6. * @time 11:07

  7. * @discription

  8. **/

  9. public class GirlFriendServiceImpl implements GirlFriendService {


  10.    @Autowired

  11.    private GirlFriendServiceProperties girlFriendServiceProperties;


  12.    /**

  13.     * 打招呼

  14.     *

  15.     */

  16.    @Override

  17.    public void say() {

  18.        String message = girlFriendServiceProperties.getMessage();

  19.        System.out.println("Girl Friend: " + message);

  20.    }

  21. }

GirlFriendServiceImpl 实现了 GirlFriendService 接口的 say() 方法,它会获取 GirlFriendServiceProperties配置类 message 内容,并打印到控制台。

硬菜:新增自动配置类

创建类 GirlFriendAutoConfiguration.java 来实现自动配置化功能:

  1. package site.exception;


  2. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

  3. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

  4. import org.springframework.boot.context.properties.EnableConfigurationProperties;

  5. import org.springframework.context.annotation.Bean;

  6. import org.springframework.context.annotation.Configuration;


  7. /**

  8. * @author www.excpetion.site(exception 教程网)

  9. * @date 2019/1/30

  10. * @time 11:11

  11. * @discription

  12. **/

  13. @Configuration

  14. @ConditionalOnClass(GirlFriendService.class)

  15. @EnableConfigurationProperties(GirlFriendServiceProperties.class)

  16. public class GirlFriendAutoConfiguration {


  17.    @Bean

  18.    @ConditionalOnMissingBean

  19.    public GirlFriendService girlFriendService() {

  20.        return new GirlFriendServiceImpl();

  21.    }

  22. }

接下来,对上面相关注解说明一下:

  • @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:\Users\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-friend-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() 方法:

  1. package site.exception.springboothello;


  2. import org.springframework.beans.factory.annotation.Autowired;

  3. import org.springframework.boot.CommandLineRunner;

  4. import org.springframework.boot.SpringApplication;

  5. import org.springframework.boot.autoconfigure.SpringBootApplication;

  6. import site.exception.GirlFriendService;


  7. @SpringBootApplication

  8. public class SpringBootHelloApplication implements CommandLineRunner {


  9.    public static void main(String[] args) {

  10.        SpringApplication.run(SpringBootHelloApplication.class, args);

  11.    }


  12.    @Autowired

  13.    private GirlFriendService girlFriendService;


  14.    @Override

  15.    public void run(String... args) throws Exception {

  16.        // 调用打招呼方法

  17.        girlFriendService.say();

  18.    }

  19. }

启动项目,看看在零配置的情况下,它的输出:

  1.  .   ____          _            __ _ _

  2. /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

  3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

  4. \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

  5.  '  |____| .__|_| |_|_| |_\__, | / / / /

  6. =========|_|==============|___/=/_/_/_/

  7. :: Spring Boot ::        (v2.1.2.RELEASE)


  8. 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)

  9. 2019-01-30 15:40:48.314  INFO 25320 --- [  restartedMain] s.e.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default

  10. 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

  11. 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'

  12. 2019-01-30 15:40:51.299  INFO 25320 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

  13. 2019-01-30 15:40:51.372  INFO 25320 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

  14. 2019-01-30 15:40:51.373  INFO 25320 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.14]

  15. 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;.]

  16. 2019-01-30 15:40:51.628  INFO 25320 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext

  17. 2019-01-30 15:40:51.629  INFO 25320 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3171 ms

  18. 2019-01-30 15:40:51.921  INFO 25320 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'

  19. 2019-01-30 15:40:52.142  INFO 25320 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729

  20. 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 ''

  21. 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)

  22. Girl Friend: Hi, good morning !

如何在 Spring Boot 中自定义启动器 Starter_java_03

可以看到,在没做任何配置的情况下,输出的是默认打招呼信息:Hi, good morning !,接下来,我们手动配置一下新的打招呼内容,看看是否能自动获取到。

application.properties 文件中配置如下:

spring.girlfriend.message=I LOVE YOU

重启项目,验证一下,是否输出的还是默认信息:

如何在 Spring Boot 中自定义启动器 Starter_java_04

正确打印我们自己配置的:I LOVE YOU