自定义Starter

引言

spring boot 的便利体现在,它简化了很多繁琐的配置,这对开发人员来说是一个福音。只要引入框架的starter就可以使用框架的功能了。不过当出现错误时,排查问题的难度就上升了。因为自动配置的逻辑都在spring boot starter中,想要快速定位问题,就必须了解spring boot starter的内部原理。下面我们自己动手来实现一个spring boot starter

——《spring cloud 微服务 入门、进阶与实战》25页

spring boot starter 项目创建

创建一个项目 spring-starter-demo。

注意!!!

这里创建的不是spring boot ,也不是 maven web。因为试过,即使 maven install 成功生成了一个starter的 jar 包,但在spring boot 项目中却不能正常注入。经查多篇博客发现这里创建的是maven quick start 项目(有的说是 j2ee - simple 我还没检验 )。

image-20200427161948084.png

项目最终目录结构如下:

image-20200427163018487.png

创建好后包(自定义的包名)com.study 下面原本只有一个 App类(这个App.class 在这个starter 项目中没有任何作用,可以删除掉,不删掉也不影响)。

其它类是后面添加的。resources 资源文件夹也是后面自己添加的,创建完是没有的。


pom配置如下:(手动添加的已做标注,其它的都是自动生成的)

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<!-- idea 创建时默认生成的 -->
<?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.study</groupId>
<artifactId>spring-starter-demo</artifactId>
<version>1.0-SNAPSHOT</version>

<name>spring-starter-demo</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<!-- 自己添加的 start -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<!-- 自己添加的 end -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source><!-- 这里可以根据自己的情况修改 -->
<maven.compiler.target>1.7</maven.compiler.target><!-- 这里可以根据自己的情况修改 -->
</properties>

<dependencies>
<!-- 自己添加的 start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 自己添加的 end -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

  • UserProperties

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    @ConfigurationProperties("spring.user")
    public class UserProperties {
    private String username;
    private String pwd;
    //set get 方法省略。。。
    }
  • UserClient

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class UserClient {
    private UserProperties userProperties;

    public UserClient() {

    }

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

    public String getName() {
    return userProperties.getUsername();
    }
    public String getPwd() {
    return userProperties.getPwd();
    }
    }
  • UserAutoConfigure

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    @EnableConfigurationProperties(UserProperties.class)
    public class UserAutoConfigure {

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

    }
  • spring.factories

    1
    2
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.study.UserAutoConfigure

maven打包

这里用maven clean; maven install.(如果已经配置好的话。直接点击idea右边的maven命令即可)

那么这个 starter 就制作好了

引入stater

在springboot 中引入:

1
2
3
4
5
<dependency>
<groupId>com.study</groupId>
<artifactId>spring-starter-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

我这里引入是提示没找到。没有关系,

image-20200427163601006.png

只要这里注入时没有提示错误就可以

1
2
3
4
5
6
7
8
@Autowired
private UserClient userClient;

@GetMapping("/user/name")
public String getUserName() {
logger.info("lalalalalalallalala" + userClient.toString());
return userClient.getName();
}

在spring boot 项目配置文件中加入:

1
2
3
spring.user.username=WuZhiYong
spring.user.pwd=123456
spring.user.enabled=true

启动项目,请求测试地址得到返回:

image-20200427164403776.png

使用注解开启自动构建

starter项目里建立一个类(我是放在同包下,这个位置应该没什么关系)

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(UserAutoConfigure.class)
public @interface EnableUserClient {
}

这里可以把resource 下 META-INF 里边的spring.factories 删掉了。然后在spring boot项目 的启动类上加入注解

1
@EnableUserClient

启动spring boot 访问测试地址 同样也能得到想要的结果

使用配置开启自动构建

这一点其实已经结合在前面的配置中了

在类UserAutoConfigure里注解@ConditionalOnProperty,就表示必须配置文件中指定,spring.user.enabled=true

这里才会构建这个bean

1
@ConditionalOnProperty(prefix = "spring.user",value = "enabled",havingValue = "true")

例如在springboot 中我们把spring.user.enabled=true 注释掉再启动项目会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Field userClient in com.example.springbootbase.controller.HelloController required a bean of type 'com.study.UserClient' that could not be found.

The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)

The following candidates were found but could not be injected:
- Bean method 'userClient' in 'UserAutoConfigure' not loaded because @ConditionalOnProperty (spring.user.enabled=true) did not find property 'enabled'


Action:

Consider revisiting the entries above or defining a bean of type 'com.study.UserClient' in your configuration.


Process finished with exit code 0

在实际的场景中我们自定义的starter 功能肯定不会这么单一,在UserAutoConfigure中定会有多个bean。这些bean 我们根据功能模块的不同进行封装后,通过如上@ConditionalOnProperty的配置。在实际应用时,我们可通过配置 不同的enable=true来加载不同的功能。

这就是使用配置开启自动构建。

配置starter内容提示

在starter项目里 resources 的META-INF下建立spring-configuration-metadata.json文件

写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"properties": [
{
"name": "spring.user.username",
"defaultValue": "wu_zhi_yong999"
},
{
"name": "spring.user.pwd",
"defaultValue": "lalalalalla"
},
{
"name": "spring.user.enabled",
"type": "java.lang.Boolean",
"defaultValue": false
}
]
}

重新 clean install

在spring boot 项目里我们配置spring.user. 会有自动提示 :

image-20200428102410838.png

后续

spring 自定义 starter 还有很多值得深究的地方。我在抄写代码时抄错了(抄都能抄错 哎 以后只能 CV了) 后来造成不能配置使用starter 。在网上看了很多博客。虽然最后发现了是自己的问题(抄错了)。但也发现了很不错的文章 这里分享下:

https://blog.csdn.net/qq_31445987/article/details/105152837?utm_source=distribute.pc_relevant.none-task-blog-baidujs-2

https://blog.csdn.net/qq_25863973/article/details/99343187?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

这几天都是在参照《spring cloud 微服务 入门、进阶与实战》这本书在学习。接下来终于要开始spring cloud 了


参考:

《spring cloud 微服务 入门、进阶与实战》