1. 准备:了解基本概念、规划实战内容;

2. 实战:开发自定义starter,并在demo工程中使用它;

3. 深入:从spring和spring boot源码层面分析starter的原理;

RequestContextHolder.setRequestAttributes(new 
ServletRequestAttributes(req)); 
//MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是直接从控制器 
获取 
//类级别的 
System.out.println( 
fromController(UserController.class).build().toString() 
); 
//方法级别的 
System.out.println( 
fromMethodName(UserController.class, "view", 
1L).build().toString() 
); 
//通过Mock方法调用得到 
System.out.println( 
fromMethodCall(on(UserController.class).getUser(2L)).build() 
); 
}

}本章内容概览

1. 查看官方资料;

2. 设定实战目标;

3. 学习spring cloud的starter,作为实战参考;

4. 实战内容的具体设计;

版本信息

本次实战的版本信息:

1. java:1.8.0_144

2. spring boot:1.5.16.RELEASE

3. spring cloud:Edgware.RELEASE

官方资料

为了有个初始印象,我们从spring官方文档看起吧:

1. 官网:https://spring.io/docs/reference,点击下图红框位置:2. 在弹出的列表中选择1.5.16版本的reference,如下图红框:

3. 在打开的文档目录中很容易找到starter的章节,地址是:https://docs.spring.io/spring-boot/doc

s/1.5.16.RELEASE/reference/htmlsingle/#using-boot-starter,内容如下图:

我的理解:

第一. 在应用中可以用starter将依赖库问题变得简单,如果你想依赖Spring和JPA,只需在应用中依赖

spring-boot-starter-data-jpa即可;

第二. 常用库的官方starter,其artifactId的格式类似"spring-boot-starter-*", 对于非官方的starter,

建议将业务名称放在"spring-boot-starter"前面,例如"acme-spring-boot-starter";

第三. 已列举常用的官方starter,可用来参考;设定实战目标

本次实战的目标如下:

1. A应用提供加法计算的服务;

2. B应用提供减法计算的服务;

3. C应用要使用加法计算和减法计算的服务,并且减法服务可以通过配置来实现是否支持负数;

学习spring cloudstarter

目标已定下,但是先不急着编码,我们去看下spring cloud的设计,用来作为借鉴参考;

回顾一下我们使用Spring cloud的时候,如果要把一个应用作为Eureka client注册到Eureka server,只

需在应用的pom.xml中添加如下依赖:

注册到Eureka server的工作,是由CloudEurekaClient类完成的,该类属于模块spring-cloud-netflix

eureka-client,因此我们要弄清楚以下两点:

1. 为什么不需要应用的pom.xml中依赖spring-cloud-netflix-eureka-client?

2. 为什么应用能自动注册到Eureka sever?

如何建立对spring-cloud-netflix-eureka-client模块的依赖

打开spring-cloud-starter-netflix-eureka-client模块的pom.xml文件就一目了然了,如下图,原来在这

个pom.xml文件中已经依赖了spring-cloud-netflix-eureka-client模块,因此,我们的应用只需依赖

spring-cloud-starter-netflix-eureka-client模块,就能间接依赖到spring-cloud-netflix-eureka-client模

块:

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

</dependency>再看看上图中其他的依赖,可以发现的确如官方文档所说,starter处理了复杂的依赖关系,我们只需要

依赖starter即可,官方文档中还有一段话需要注意,如下图:上图红框中说明starter是个空的jar,其作用就是用来提供必要的模块依赖的,来看看spring-cloud

starter-netflix-eureka-client模块是否遵守此规则,如下图,只有配置文件,没有任何class:

为什么应用能自动注册到Eureka sever

作为Eureka client的应用,在启动后就自动注册到Eureka server了,作为应用开发者的我们除了在

pom.xml中依赖spring-cloud-starter-netflix-eureka-client模块,没有做其他设置,这是如何实现的

呢?

1. 注册到Eureka server的工作,是CloudEurekaClient类在其父类的构造方法中完成的,搜索源码发

现此类的在EurekaClientAutoConfiguration中被注册到spring容器,如下图红框所示:

所以,现在问题就变成了如何让EurekaClientAutoConfiguration类被实例化?

1. 在spring-cloud-netflix-eureka-client模块的spring.factories文件中,找到了

EurekaClientAutoConfiguration:模块名称

作用

备注

customizeapi 
包含 
了接口 
和异常 
的定义 
实现和调用服务时用到的接口和异常都在此工程中 
addservice 
提供加 
法服务 
普通的maven工程,里面加法接口的实现类 
minusservice 
提供减 
法服务 
普通的maven工程,里面有两个减法接口的实现类,一 
个支持负数,另一个不支持 
customizeservicestarter 
自定义 
starter 
模块 
pom.xml中依赖了customizeapi、addservice、 
minusservice,自身有个Configuration类,通过@Bean 
注解向spring容器注册AddService和MinusService的实 
例 
这是个spring boot的扩展配置,在此文件中配置的bean都会被实例化,然后注册到spring容器,具体的 
细节,我们会在第三章结合spring boot源码详细分析,本章只要知道用法即可; 
此处小结Eureka client自动注册到Eureka server的过程: 
第一、spring-cloud-netflix-eureka-client模块的spring.factories文件中配置了 
EurekaClientAutoConfiguration,因此EurekaClientAutoConfiguration会被实例化并注册到Spring容 
器中; 
第二、EurekaClientAutoConfiguration中配置了CloudEurekaClient,因此CloudEurekaClient会实例 
化,在构造方法中执行了注册; 
实战的设计 
参考了spring cloud的starter设计后,接下来的实战被设计成两个maven工程:customizestarter和 
customizestartertestdemo; 
1. 工程customizestarter里面包含了四个模块,每个模块功能如下所示:1. 工程customizestartertestdemo在pom.xml中依赖了上述的customizeservicestarter模块,提供 
的web服务会用到addservice和minusservice的服务,并且在应用启动时设置环境变量来选择使用 
的减法服务是否支持负数; 
至此,准备工作已经完成了,对基本原理和开发设计都已经清楚,接下来的章节我们来一起开发上述五 
个工程; 
自定义spring boot starter三部曲之二:实战开发 
本文是《自定义spring boot starter三部曲》的第二篇,上一篇中我们通过学习spring cloud的starter, 
对spring boot的starter有了初步了解,也设计好了实战内容,今天就来一起实现; 
本章内容概述 
1. 创建工程customizestarter; 
2. 创建模块customizeapi; 
3. 创建模块addservice; 
4. 创建模块minusservice; 
5. 创建模块customizeservicestarter; 
6. 构建工程customizestarter,并安装到本地maven仓库; 
7. 创建工程customizestartertestdemo; 
8. 构建工程customizestartertestdemo,得到jar包; 
9. 启动customizestartertestdemo工程的jar包,并带上一个启动参数,验证支持负数的减法服务; 
10. 启动customizestartertestdemo工程的jar包,验证不支持服务的减法服务; 
创建工程customizestarter 
1. 创建一个名为customizestarter的maven工程,以spring-boot-starter-parent作为父工程,同时自 
身又是后续几个模块的父工程,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> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizestarter</artifactId> 
<packaging>pom</packaging> 
<version>0.0.1-SNAPSHOT</version> 
<properties> 
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
</properties> 
<parent> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-parent</artifactId>创建模块customizeapi 
1. 在工程customizestarter下创建模块customizeapi,这是个java工程,里面是加法和减法服务的接 
口,和一个业务异常的定义; 
2. customizeapi的pom.xml内容如下,很简单,只有基本定义: 
1. 异常定义类: 
1. 加法服务的接口类AddService: 
<version>1.5.9.RELEASE</version> 
<relativePath/> 
</parent> 
<modules> 
<!--加法服务--> 
<module>addservice</module> 
<!--减法服务--> 
<module>minusservice</module> 
<!--接口和异常定义--> 
<module>customizeapi</module> 
<!--启动器--> 
<module>customizeservicestarter</module> 
</modules> 
</project> 
<?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"> 
<parent> 
<artifactId>customizestarter</artifactId> 
<groupId>com.bolingcavalry</groupId> 
<version>0.0.1-SNAPSHOT</version> 
<relativePath>../</relativePath> 
</parent> 
<modelVersion>4.0.0</modelVersion> 
<artifactId>customizeapi</artifactId> 
</project> 
package com.bolingcavalry.api.exception; 
/** 
* @author wilzhao 
* @description 执行减法服务时抛出的异常 
* @email zq2599@gmail.com 
* @time 2018/10/13 14:20 
*/ 
public class MinusException extends Exception{ 
public MinusException(String message) { 
super(message); 
} 
}1. 减法服务定义类,注意减法API声明了异常抛出,因为如果已经配置了不支持负数的减法服务,那 
么被减数如果小于减数就抛出异常: 
创建模块addservice 
1. 在工程customizestarter下创建模块addservice,这是个java工程,里面包含了加法相服务的实 
现,pom.xml内容如下,注意由于要实现加法接口,因此需要依赖模块customizeapi: 
package com.bolingcavalry.api.service; 
/** 
* @author wilzhao 
* @description 加法服务对应的接口 
* @email zq2599@gmail.com 
* @time 2018/10/13 10:07 
*/ 
public interface AddService { 
/** 
* 普通加法 
* @param a 
* @param b 
* @return 
*/ 
int add(int a, int b); 
} 
package com.bolingcavalry.api.service; 
import com.bolingcavalry.api.exception.MinusException; 
/** 
* @author wilzhao 
* @description 减法服务 
* @email zq2599@gmail.com 
* @time 2018/10/13 12:07 
*/ 
public interface MinusService { 
/** 
* 普通减法 
* @param minuend 减数 
* @param subtraction 被减数 
* @return 差 
*/ 
int minus(int minuend, int subtraction) throws MinusException; 
} 
<?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"> 
<parent> 
<artifactId>customizestarter</artifactId> 
<groupId>com.bolingcavalry</groupId> 
<version>0.0.1-SNAPSHOT</version> 
<relativePath>../</relativePath>1. 加法接口的实现类AddServiceImpl很简单,如下: 
创建模块minusservice 
1. 在工程customizestarter下创建模块minusservice,这是个java工程,里面包含了减法相服务的实 
现,pom.xml内容如下,注意由于要实现减法接口,因此需要依赖模块customizeapi: 
</parent> 
<artifactId>addservice</artifactId> 
<modelVersion>4.0.0</modelVersion> 
<dependencies> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizeapi</artifactId> 
<version>${project.version}</version> 
</dependency> 
</dependencies> 
</project> 
package com.bolingcavalry.addservice.service.impl; 
import com.bolingcavalry.api.service.AddService; 
/** 
* @author wilzhao 
* @description 加法服务的实现 
* @email zq2599@gmail.com 
* @time 2018/10/13 10:59 
*/ 
public class AddServiceImpl implements AddService { 
public int add(int a, int b) { 
return a + b; 
} 
} 
<?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"> 
<parent> 
<artifactId>customizestarter</artifactId> 
<groupId>com.bolingcavalry</groupId> 
<version>0.0.1-SNAPSHOT</version> 
<relativePath>../</relativePath> 
</parent> 
<modelVersion>4.0.0</modelVersion> 
<artifactId>minusservice</artifactId> 
<dependencies> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizeapi</artifactId> 
<version>${project.version}</version> 
</dependency> 
</dependencies></project> 
1. 一共有两个减法接口的实现类,第一个不支持负数结果,如果被减数小于减数就抛出异常 
MinusException: 
package com.bolingcavalry.minusservice.service.impl; 
import com.bolingcavalry.api.exception.MinusException; 
import com.bolingcavalry.api.service.MinusService; 
/** 
* @author wilzhao 
* @description 减法服务的实现,不支持负数 
* @email zq2599@gmail.com 
* @time 2018/10/13 14:24 
*/ 
public class MinusServiceNotSupportNegativeImpl implements MinusService { 
/** 
* 减法运算,不支持负数结果,如果被减数小于减数,就跑出MinusException 
* @param minuend 被减数 
* @param subtraction 减数 
* @return 
* @throws MinusException 
*/ 
public int minus(int minuend, int subtraction) throws MinusException { 
if(subtraction>minuend){ 
throw new MinusException("not support negative!"); 
} 
return minuend-subtraction; 
} 
} 
1. 第二个减法接口的实现类支持负数返回: 
package com.bolingcavalry.minusservice.service.impl; 
import com.bolingcavalry.api.exception.MinusException; 
import com.bolingcavalry.api.service.MinusService; 
/** 
* @author wilzhao 
* @description 支持负数结果的减法服务 
* @email zq2599@gmail.com 
* @time 2018/10/13 14:30 
*/ 
public class MinusServiceSupportNegativeImpl implements MinusService { 
/** 
* 减法实现,支持负数 
* @param minuend 减数 
* @param subtraction 被减数 
* @return 
* @throws MinusException 
*/创建模块customizeservicestarter 
1. 在工程customizestarter下创建模块customizeservicestarter,这是个java工程,里面需要依赖 
spring boot配置相关的库,由于要在配置中实例化加法和减法服务的实现,因此customizeapi、 
addservice、minusservice这些模块都要在pom.xml中声明依赖,如下: 
1. 创建配置类CustomizeConfiguration,注意getSupportMinusService和 
getNotSupportMinusService这两个方法上的注解配置,如果环境变量 
com.bolingcavalry.supportnegative存在并且等于true,那么getSupportMinusService方法就返 
回了MinusService接口的实例,如果当前环境没有MinusService接口的实例,就由 
getNotSupportMinusService方法就返回一个,并且有会在控制台打印创建了哪种实现: 
public int minus(int minuend, int subtraction) throws MinusException { 
return minuend - subtraction; 
} 
} 
<?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"> 
<parent> 
<artifactId>customizestarter</artifactId> 
<groupId>com.bolingcavalry</groupId> 
<version>0.0.1-SNAPSHOT</version> 
<relativePath>../</relativePath> 
</parent> 
<artifactId>customizeservicestarter</artifactId> 
<modelVersion>4.0.0</modelVersion> 
<dependencies> 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-autoconfigure</artifactId> 
<!--仅编译时才需要--> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizeapi</artifactId> 
<version>${project.version}</version> 
</dependency> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>addservice</artifactId> 
<version>${project.version}</version> 
</dependency> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>minusservice</artifactId> 
<version>${project.version}</version> 
</dependency> 
</dependencies> 
</project>package com.bolingcavalry.customizeservicestarter; 
import com.bolingcavalry.addservice.service.impl.AddServiceImpl; 
import com.bolingcavalry.api.service.AddService; 
import com.bolingcavalry.api.service.MinusService; 
import 
com.bolingcavalry.minusservice.service.impl.MinusServiceNotSupportNegativeImpl; 
import 
com.bolingcavalry.minusservice.service.impl.MinusServiceSupportNegativeImpl; 
import org.springframework.beans.factory.annotation.Qualifier; 
import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
/** 
* @author wilzhao 
* @description 一句话介绍 
* @email zq2599@gmail.com 
* @time 2018/10/13 14:36 
*/ 
@Configuration 
public class CustomizeConfiguration { 
@Bean 
public AddService getAddService(){ 
System.out.println("create addService"); 
return new AddServiceImpl(); 
} 
/** 
* 如果配置了com.bolingcavalry.supportnegative=true, 
* 就实例化MinusServiceSupportNegativeImpl 
* @return 
*/ 
@Bean 
@ConditionalOnProperty(prefix="com.bolingcavalry",name = "supportnegative", 
havingValue = "true") 
public MinusService getSupportMinusService(){ 
System.out.println("create minusService support minus"); 
return new MinusServiceSupportNegativeImpl(); 
} 
/** 
* 如果没有配置com.bolingcavalry.supportnegative=true, 
* 就不会实例化MinusServiceSupportNegativeImpl, 
* 这里的条件是如果没有MinusService类型的bean,就在此实例化一个 
* @return 
*/ 
@Bean 
@ConditionalOnMissingBean(MinusService.class) 
public MinusService getNotSupportMinusService(){ 
System.out.println("create minusService not support minus"); 
return new MinusServiceNotSupportNegativeImpl(); 
}


}1. 在src\main\resources目录下创建一个目录META-INF,里面创建一个文件spring.factories,内容

是如下,表示如果当前应用支持spring boot的自动配置,就会被spring boot框架实例化并注册到

spring容器内:

构建工程customizestarter

1. 到这里customizestarter工程的编码就结束了,在工程内pom.xml所在目录(也就是

customizestarter内的第一层目录),执行以下命令可以编译构建并安装到本地maven仓库:

1. 如果编译构建和安装都成功了,可以看到类似如下输出:

现在starter已经准备好了,我们做一个spring boot的web应用来验证一下;

创建工程customizestartertestdemo

1. 工程customizestartertestdemo是个简单的spring boot应用,pom.xml如下,可见并无特别之

处,只是多了customizeservicestarter的依赖:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry 
.customizeservicestarter.CustomizeConfiguration 
mvn clean install -Dmaven.test.skip=true -U 
[INFO] Installing 
C:\temp\201810\07\customizestarter\customizeservicestarter\pom.xml to 
C:\Users\12167\.m2\repositor 
y\com\bolingcavalry\customizeservicestarter\0.0.1- 
SNAPSHOT\customizeservicestarter-0.0.1-SNAPSHOT.pom 
[INFO] ------------------------------------------------------------------------ 
[INFO] Reactor Summary: 
[INFO] 
[INFO] customizestarter ................................... SUCCESS [ 0.748 s] 
[INFO] customizeapi ....................................... SUCCESS [ 3.266 s] 
[INFO] addservice ......................................... SUCCESS [ 0.427 s] 
[INFO] minusservice ....................................... SUCCESS [ 0.344 s] 
[INFO] customizeservicestarter ............................ SUCCESS [ 0.495 s] 
[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 5.954 s 
[INFO] Finished at: 2018-10-14T00:17:46+08:00 
[INFO] Final Memory: 29M/221M 
[INFO] ------------------------------------------------------------------------ 
<?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> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizestartertestdemo</artifactId> 
<version>0.0.1-SNAPSHOT</version> 
<packaging>jar</packaging><name>customizestartertestdemo</name> 
<description>Demo project for Spring Boot</description> 
<parent> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-parent</artifactId> 
<version>1.5.9.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> 
<dependencies> 
<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> 
<dependency> 
<groupId>com.bolingcavalry</groupId> 
<artifactId>customizeservicestarter</artifactId> 
<version>0.0.1-SNAPSHOT</version> 
</dependency> 
</dependencies> 
<build> 
<plugins> 
<plugin> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-maven-plugin</artifactId> 
</plugin> 
</plugins> 
</build> 
</project> 
1. 开发一个Controller类,用于调用AddService和MinusService对应的服务: 
package com.bolingcavalry.customizestartertestdemo.controller; 
import com.bolingcavalry.api.exception.MinusException; 
import com.bolingcavalry.api.service.AddService; 
import com.bolingcavalry.api.service.MinusService; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.web.bind.annotation.*;1. 启动类如下:

构建工程customizestartertestdemo

1. 在customizestartertestdemo工程的pom.xml所在目录下执行以下命令即可构建成功:

1. 命令执行成功后,即可在target目录下见到customizestartertestdemo-0.0.1-SNAPSHOT.jar文

件,如下图:

/** 
* @author wilzhao 
* @description 调用加法和减法服务的测试类 
* @email zq2599@gmail.com 
* @time 2018/10/13 16:00 
*/ 
@RestController 
public class CalculateController { 
@Autowired 
private AddService addService; 
@Autowired 
private MinusService minusService; 
@RequestMapping(value = "/add/{added}/{add}", method = RequestMethod.GET) 
public String add(@PathVariable("added") int added, @PathVariable("add") int 
add){ 
return added + " 加 " + add + " 等于 : " + addService.add(added, add); 
} 
@RequestMapping(value = "/minus/{minuend}/{subtraction}", method = 
RequestMethod.GET) 
public String minus(@PathVariable("minuend") int minuend, 
@PathVariable("subtraction") int subtraction) throws MinusException { 
return minuend + " 减 " + subtraction + " 等于 : " + 
minusService.minus(minuend, subtraction); 
} 
} 
package com.bolingcavalry.customizestartertestdemo; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
@SpringBootApplication 
public class CustomizestartertestdemoApplication { 
public static void main(String[] args) { 
SpringApplication.run(CustomizestartertestdemoApplication.class, args); 
} 
}

mvn clean package -Dmaven.test.skip=true现在编码和构建已经全部完成,我们可以来验证了;

验证支持负数的减法服务

1. 在customizeapi模块的CustomizeConfiguration类中,有如下方法和注解:

从上述代码可见,只要环境变量"com.bolingcavalry.supportnegative"等于true,注册到spring容器的

就是MinusServiceSupportNegativeImpl类的实例;

1. customizestartertestdemo-0.0.1-SNAPSHOT.jar文件所在目录下,执行以下命令启动应用:

1. 在控制台中可以看见create minusService support minus,表示注册到spring容器的是

MinusServiceSupportNegativeImpl类的实例,如下所示:

@Bean 
@ConditionalOnProperty(prefix="com.bolingcavalry",name = "supportnegative", 
havingValue = "true") 
public MinusService getSupportMinusService(){ 
System.out.println("create minusService support minus"); 
return new MinusServiceSupportNegativeImpl(); 
} 
java -Dcom.bolingcavalry.supportnegative=true -jar customizestartertestdemo- 
0.0.1-SNAPSHOT.jar 
2018-10-14 12:04:54.233 INFO 16588 --- [ost-startStop-1] 
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' 
to: [/*] 
create addService 
create minusService support minus 
2018-10-14 12:04:54.845 INFO 16588 --- [ main] 
s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: 
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplication 
Context@443b7951: startup date [Sun Oct 14 12:04:50 CST 2018]; root of context

hierarchy1. 在浏览器访问http://localhost:8080/minus/1/2,可见返回计算结果为负数:

验证不支持负数的减法服务

1. 前面已经分析过,CustomizeConfiguration类的getNotSupportMinusService方法执行的条件是

环境变量"com.bolingcavalry.supportnegative"等于true,如果没有这个环境变量,

getNotSupportMinusService方法就不会执行,spring容器中就没有MinusService接口的实例;

2. CustomizeConfiguration类中,有如下方法和注解:

从上述代码可见,spring容器中如果没有MinusService接口的实例,getNotSupportMinusService方法

就会被执行,在spring容器中注册MinusServiceNotSupportNegativeImpl实例;

因此接下来的我们启动的应用如果没有环境变量"com.bolingcavalry.supportnegative",就可以使用到

不支持负数的减法服务了;

\2. 停掉之前启动的应用,然后执行以下命令启动应用:

1. 在控制台中可以看见create minusService not support minus,表示注册到spring容器的是

MinusServiceNotSupportNegativeImpl类的实例,如下所示: 
1. 在浏览器访问http://localhost:8080/minus/1/2,由于MinusServiceNotSupportNegativeImpl实 
例不支持负数减法,会直接抛出异常,如下图: 
@Bean 
@ConditionalOnMissingBean(MinusService.class) 
public MinusService getNotSupportMinusService(){ 
System.out.println("create minusService not support minus"); 
return new MinusServiceNotSupportNegativeImpl(); 
} 
java -jar customizestartertestdemo-0.0.1-SNAPSHOT.jar 
2018-10-14 12:15:05.994 INFO 16608 --- [ost-startStop-1] 
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' 
to: [/*] 
create addService 
create minusService not support minus 
2018-10-14 12:15:06.592 INFO 16608 --- [ main] 
s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: 
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplication 
Context@443b7951: startup date [Sun Oct 14 12:15:02 CST 2018]; root of context

hierarchy至此,自定义spring boot starter的编码实战就完成了,希望本篇可以给您用来作参考,助您做出自己

所需的starter;

下一篇我们一起去看看spring boot的源码,对这个高效的扩展功能做更深入的了解;

自定义spring boot starter三部曲之三:源码分析spring.factories加载过程

版本情况

本文中涉及到的库的版本:

1. Spring boot :1.5.9.RELEASE;

2. JDK :1.8.0_144

初步分析

先回顾customizeservicestarter模块中spring.factories文件的内容:

从上述内容可以确定今天源码学习目标:

1. spring容器如何处理配置类;

2. spring boot配置类的加载情况;

3. spring.factories中的EnableAutoConfiguration配置何时被加载?

4. spring.factories中的EnableAutoConfiguration配置被加载后做了什么处理;

spring容器如何处理配置类

1. ConfigurationClassPostProcessor类的职责是处理配置类; 
2. ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor接口的实现类,它的 
postProcessBeanDefinitionRegistry方法在容器初始化阶段会被调用; 
3. postProcessBeanDefinitionRegistry方法又调用processConfigBeanDefinitions方法处理具体业 
务; 
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry 
.customizeservicestarter.CustomizeConfiguration4. processConfigBeanDefinitions方法中通过ConfigurationClassParser类来处理Configuration注 
解,如下图: 
5. 如上图红框所示,所有被Configuration注解修饰过的类,都会被parser.parse(candidates)处理, 
即ConfigurationClassParser类的parse方法; 
6. parse方法中调用processDeferredImportSelectors方法做处理:找到Configuration类中的 
Import注解,对于Import注解的值,如果实现了ImportSelector接口,就调用其selectImports方 
法,将返回的名称实例化: 
private void processDeferredImportSelectors() { 
//这里就是Configuration注解中的Import注解的值, 
//例如EnableAutoConfiguration注解的源码中,Import注解的值是 
EnableAutoConfigurationImportSelector.class 
List<DeferredImportSelectorHolder> deferredImports = 
this.deferredImportSelectors; 
this.deferredImportSelectors = null; 
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); 
for (DeferredImportSelectorHolder deferredImport : deferredImports) { 
ConfigurationClass configClass = 
deferredImport.getConfigurationClass(); 
try { 
//以EnableAutoConfiguration注解为例,其Import注解的值为 
EnableAutoConfigurationImportSelector.class, 
//那么此处就是在调用EnableAutoConfigurationImportSelector的 
selectImports方法,返回了一个字符串数组 
String[] imports = 
deferredImport.getImportSelector().selectImports(configClass.getMetadata()); 
//字符串数组中的每个字符串都代表一个类,此处做实例化 
processImports(configClass, asSourceClass(configClass), 
asSourceClasses(imports), false); 
} 
catch (BeanDefinitionStoreException ex) { 
throw ex; 
} 
catch (Throwable ex) { 
throw new BeanDefinitionStoreException( 
"Failed to process import candidates for configuration 
class [" + 
configClass.getMetadata().getClassName() + "]", ex); 
} 
} 
}

小结一下spring容器配置类的逻辑:

1. 找出配置类;

2. 找出配置类中的Import注解;3. Import注解的值是class,如果该class实现了ImportSelector接口,就调用其selectImports方法,

将返回的名称实例化;

有了上面的结论就可以结合Spring boot的源码来分析加载了哪些数据了;

spring boot配置类的加载情况

1. 我们的应用使用了SpringBootApplication注解,看此注解的源码,使用了

EnableAutoConfiguration注解: 
2. EnableAutoConfiguration注解中,通过Import注解引入了 
EnableAutoConfigurationImportSelector.class: 
3. 看EnableAutoConfigurationImportSelector的源码: 
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Inherited 
@SpringBootConfiguration 
@EnableAutoConfiguration 
@ComponentScan(excludeFilters = { 
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 
@Filter(type = FilterType.CUSTOM, classes = 
AutoConfigurationExcludeFilter.class) }) 
public @interface SpringBootApplication { 
...... 
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Inherited 
@AutoConfigurationPackage 
@Import(EnableAutoConfigurationImportSelector.class) 
public @interface EnableAutoConfiguration { 
...... 
/** 
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration 
* auto-configuration}. This class can also be subclassed if a custom variant of 
* {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed. 
* 
* @deprecated as of 1.5 in favor of {@link AutoConfigurationImportSelector} 
* @author Phillip Webb 
* @author Andy Wilkinson 
* @author Stephane Nicoll 
* @author Madhura Bhave 
* @since 1.3.0 
* @see EnableAutoConfiguration 
*/ 
@Deprecated 
public class EnableAutoConfigurationImportSelector 
extends AutoConfigurationImportSelector { 
@Override 
protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass().equals(EnableAutoConfigurationImportSelector.class)) { 
return getEnvironment().getProperty( 
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, 
Boolean.class, 
true); 
} 
return true; 
} 
}

上述源码有三处重点需要关注:

第一,EnableAutoConfigurationImportSelector是AutoConfigurationImportSelector的子类; 
第二,EnableAutoConfigurationImportSelector已经被废弃了,不推荐使用; 
第三,文档中已经写明废弃原因:从1.5版本开始,其特性由父类AutoConfigurationImportSelector实 
现; 
4. 查看AutoConfigurationImportSelector的源码,重点关注selectImports方法,该方法的返回值表 
明了哪些类会被实例化: 
@Override 
public String[] selectImports(AnnotationMetadata annotationMetadata) { 
if (!isEnabled(annotationMetadata)) { 
return NO_IMPORTS; 
} 
try { 
//将所有spring-autoconfigure-metadata.properties文件中的键值对保存在 
autoConfigurationMetadata中 
AutoConfigurationMetadata autoConfigurationMetadata = 
AutoConfigurationMetadataLoader 
.loadMetadata(this.beanClassLoader); 
AnnotationAttributes attributes = getAttributes(annotationMetadata); 
//取得所有配置类的名称 
List<String> configurations = 
getCandidateConfigurations(annotationMetadata, 
attributes); 
configurations = removeDuplicates(configurations); 
configurations = sort(configurations, autoConfigurationMetadata); 
Set<String> exclusions = getExclusions(annotationMetadata, 
attributes); 
checkExcludedClasses(configurations, exclusions); 
configurations.removeAll(exclusions); 
configurations = filter(configurations, autoConfigurationMetadata); 
fireAutoConfigurationImportEvents(configurations, exclusions); 
return configurations.toArray(new String[configurations.size()]); 
} 
catch (IOException ex) { 
throw new IllegalStateException(ex); 
} 
}

5. 通过上述代码可以发现,getCandidateConfigurations方法的调用是个关键,它返回的字符串都是

即将被实例化的类名,来看此方法源码:protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,

AnnotationAttributes attributes) { 
List<String> configurations = SpringFactoriesLoader.loadFactoryNames( 
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); 
Assert.notEmpty(configurations, 
"No auto configuration classes found in META
INF/spring.factories. If you " 
+ "are using a custom packaging, make sure that file is 
correct."); 
return configurations; 
} 
6. getCandidateConfigurations方法中,调用了静态方法 
SpringFactoriesLoader.loadFactoryNames,上面提到的 
SpringFactoriesLoader.loadFactoryNames方法是关键,看看官方文档对此静态方法的描述,如 
下图红框所示,该方法会在spring.factories文件中寻找指定接口对应的实现类的全名(包名+实现 
类): 
7. 在getCandidateConfigurations方法中,调用SpringFactoriesLoader.loadFactoryNames的时候 
传入的指定类型是getSpringFactoriesLoaderFactoryClass方法的返回值: 
protected Class<?> getSpringFactoriesLoaderFactoryClass() { 
return EnableAutoConfiguration.class; 
}

现在可以梳理一下了:

1. spring boot应用启动时使用了EnableAutoConfiguration注解;

2. EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实

例化,并且将其selectImports方法返回的类名实例化后注册到spring容器;

3. EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自spring.factories

文件内的配置信息,这些配置信息的key等于EnableAutoConfiguration;

现在真相大白了:只要我们在spring.factories文件内配置了EnableAutoConfiguration,那么对于的类

就会被实例化后注册到spring容器;

至此,《自定义spring boot starter三部曲》系列就完结了,希望实战加源码分析的三篇文章,能帮助

您理解和实现自定义starter这种简单快捷的扩展方式;