我们都知道Spring Boot开发相当的便利,对于开发人员来说是一个福音,其实它就是通过引入各种的Spring Boot Starter包可以快速搭建出一个项目的脚手架。

目前提供的Spring Boot Starter包有很多,比如:

  • spring-boot-starter-web : 快速构建基于Spring MVC的Web项目,使用Tomcat做默认嵌入式容器。
  • spring-boot-starter-data-redis : 操作Redis。
  • spring-boot-starter-data-mongodb : 操作mongodb。
  • spring-boot-starter-data-jpa : 操作MySQL
  • spring-boot-starter-activemq : 操作Activemq
  • ……

自动配置虽然非常方便,但是自动配置麻烦的是出现错误时,排查问题的难度上升了。自动配置的逻辑都在Spring Boot Starter中,要想快速定位问题,就必须得了解Spring Boot Starter的内部原理。接下来我们自己手动实现一个Spring Boot Starter。

1. Spring Boot Starter项目创建

创建一个项目springboot-starter-demo,pom.xml配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

创建一个配置类,用于在属性文件中配置值,代码如下:

1
2
3
4
5
6
7
8
9
10
package com.self.springbootstarterdemo.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "spring.user")
public class UserProperties {
private String name;
}

再定义一个Client,里面定一个方法,用于获取配置中的值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.self.springbootstarterdemo.client;

import com.self.springbootstarterdemo.properties.UserProperties;

public class UserClient {

private UserProperties userProperties;

public UserClient(){

}

public UserClient(UserProperties p){
this.userProperties = p;
}

public String getName(){
return userProperties.getName();
}
}

2. 自动创建客户端

以上就是最基本的一个Starter包定义,但目前肯定是不能使用UserClient,因为我们没有自动构建UserClient的实例。接下来开始构建UserClient,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.self.springbootstarterdemo.config;

import com.self.springbootstarterdemo.client.UserClient;
import com.self.springbootstarterdemo.properties.UserProperties;
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;

@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class UserAutoConfiguration {

@Bean
@ConditionalOnProperty(prefix = "spring.user",value = "enabled",havingValue = "true")
public UserClient userClient(UserProperties userProperties){
return new UserClient(userProperties);
}
}

在resources下创建一个META-INF文件夹,然后在META-INF文件夹中创建spring.factories文件,文件中指定自动配置的类:

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.self.springbootstarterdemo.config.UserAutoConfiguration

【注意:注意spring.factories文件内容的格式】

至此,Spring Boot启动时会去读取spring.factories文件,然后根据配置激活对应的配置类,这样,一个简单的Starter包就实现了。

3. 使用Starter

可以在其他项目中引入这个Starter包进行测试一下,代码如下:

1
2
3
4
5
<dependency>
<groupId>com.self</groupId>
<artifactId>springboot-starter-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

引入之后就直接可以使用UserClient,UserClient在项目启动的时候已经自动初始化好,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.self.springboot_item.controller;

import com.self.springbootstarterdemo.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

@Autowired
public UserClient userClient;

@GetMapping("/user/name")
public String getUsername(){
return userClient.getName();
}
}

然后在属性文件中配置name的值和开启UserClient :

1
2
spring.user.name=xiaozeng
spring.user.enabled=true

访问/user/home 就可以返回我们配置的xiaozeng。

4. 使用注解开启Starter自动构建

很多时候我们不想在引入Starter包时就执行初始化的逻辑,而是想要由用户来指定是否要开启Starter包的自动配置功能,比如常用的@EnableAsync这个注解就是用于开启调用方法执行异步执行的功能。

同样地,我们也可以通过注解的方式来开启是否自动配置,如果用注解的方式,那么spring.factories就不需要编写了,下面就来看一下怎么定义启用自动配置的注解,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.self.springbootstarterdemo.config;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({UserAutoConfigure.class})
public @interface EnableUserClient {

}

这段代码的核心就是@Import({UserAutoConfigure.class}),通过导入的方式实现把UserAutoConfigure实例加入SpringIOC容器中,这样就能开启自动配置了。

使用方式就是在启动类上加上该注解,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.self.springboot_item;

import com.self.springboot_item.config.StartCommand;
import com.self.springbootstarterdemo.config.EnableUserClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableUserClient
public class SpringbootItemApplication {

public static void main(String[] args) {

new StartCommand(args);
SpringApplication.run(SpringbootItemApplication.class, args);
}
}

5. 使用配置开启Starter自动构建

在某些场景下,UserAutoConfigure中会配置多个对象,对于这些对象,如果不想全部配置,或是想让用户指定需要开启配置时候再去构建对象,这时候我们可以通过@ConditionalOnProperty 来指定是否开启配置的功能,代码如下:

1
2
3
4
5
@Bean
@ConditionalOnProperty(prefix = "spring.user",value = "enabled",havingValue = "true")
public UserClient userClient(UserPorperties userPorperties) {
return new UserClient(userPorperties);
}

通过上面的配置,只有当启动类加了@EnableUserClient并且配置文件中spring.user.enabled = true 的时候才会自动配置UserClient。

6. 配置Starter内容提示

在自定义Starter包的过程中,还有一点比较重要,就是对配置的内容项进行提示。

定义提示内容需要在META-INF 中创建一个spring-configuration-metadata.json文件,代码如下:

1
2
3
4
5
6
7
8
9
10
{
"properties": [ {
"name": "spring.user.name",
"defaultValue": "cxytinadi"
},
{
"name": "spring.user.enabled",
"type": "java.lang.Boolean", "defaultValue": false
} ]
}
  • name : 配置名
  • type :配置的数据类型
  • defaultValue :默认值