2.3 Spring Boot Starter自定义

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。

❑ ……

自动配置非常方便,当我们要操作Mongodb的时候,只需要引入spring-boot-starter-data-mongodb的依赖,然后配置Mongodb的链接信息spring.data.mongodb.uri = mongodb://localhost/test就可以使用MongoTemplate来操作数据,MongoTemplate的初始化工作全部交给Starter来完成。

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

2.3.1 Spring Boot Starter项目创建

创建一个项目spring-boot-starter-demo, pom.xml配置如代码清单2-23所示。

代码清单2-23 Spring Boot Starter Maven依赖

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

创建一个配置类,用于在属性文件中配置值,相当于spring.data.mongo这种形式,如代码清单2-24所示。

代码清单2-24 Spring Boot Starter配置类

        import org.springframework.boot.context.properties.ConfigurationProperties;
        import lombok.Data;
        @Data
        @ConfigurationProperties("spring.user")
        public class UserPorperties {
            private String name;
        }

@ConfigurationProperties指定了配置的前缀,也就是spring.user.name=XXX

再定义一个Client,相当于MongoTemplate,里面定一个方法,用于获取配置中的值,如代码清单2-25所示。

代码清单2-25 Spring Boot Starter客户端类

        public class UserClient {
            private UserPorperties userPorperties;
            public UserClient() {
            }
            public UserClient(UserPorperties p) {
                this.userPorperties = p;
            }
            public String getName() {
                return userPorperties.getName();
            }
        }

2.3.2 自动创建客户端

一个最基本的Starter包定义好了,但目前肯定是不能使用UserClient,因为我们没有自动构建UserClient的实例。接下来开始构建UserClient,如代码清单2-26所示。

代码清单2-26 Spring Boot Starter自动配置类

        @Configuration
        @EnableConfigurationProperties(UserPorperties.class)
        public class UserAutoConfigure {
            @Bean
            @ConditionalOnProperty(prefix = "spring.user", value = "enabled", havingValue
    = "true")
            public UserClient userClient(UserPorperties userPorperties) {
                return new UserClient(userPorperties);
            }

        }

Spring Boot会默认扫描跟启动类平级的包,假如我们的Starter跟启动类不在同一个主包下,如何能让UserAutoConfigure生效?

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

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.cxytiandi.demo.UserAutoConfigure

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

2.3.3 使用Starter

现在可以在其他的项目中引入这个Starter包,如代码清单2-27所示。

代码清单2-27 Spring Boot Starter使用Maven配置

        <dependency>
            <groupId>com.cxytiandi</groupId>
            <artifactId>spring-boot-starter-demo</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

引入之后就直接可以使用UserClient, UserClient在项目启动的时候已经自动初始化好,如代码清单2-28所示。

代码清单2-28 Spring Boot Starter客户端使用

        @RestController
        public class UserController {
            @Autowired
            private UserClient userClient;
            @GetMapping("/user/name")
            public String getUserName() {
                return userClient.getName();
            }
        }

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

        spring.user.name=yinjihuan
        spring.user.enabled=true

访问/user/name就可以返回我们配置的yinjihuan。

2.3.4 使用注解开启Starter自动构建

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

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

代码清单2-29 Spring Boot Starter启动注解定义

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

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

使用方式就是在启动类上加上该注解,如代码清单2-30所示。

代码清单2-30 @EnableUserClient使用

        @EnableUserClient
        @SpringBootApplication
        public class SpringBootDemoApplication {
            public static void main(String[] args) {
                SpringApplication.run(SpringBootDemoApplication.class, args);
            }
        }

2.3.5 使用配置开启Starter自动构建

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

代码清单2-31 @ConditionalOnProperty使用

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

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

2.3.6 配置Starter内容提示

在自定义Starter包的过程中,还有一点比较重要,就是对配置的内容项进行提示,需要注意的是,Eclipse中是不支持提示的,Spring Tools 4 for Eclipse中可以提示,如图2-5所示:

图2-5 Spring Boot Starter代码提示

定义提示内容需要在META-INF中创建一个spring-configuration-metadata.json文件,如代码清单2-32所示。

代码清单2-32 Spring Boot Starter代码提示配置

        {
            "properties": [
                {
                    "name": "spring.user.name",
                    "defaultValue": "cxytinadi"
                },
                {
                    "name": "spring.user.enabled",
                    "type": "java.lang.Boolean",
                    "defaultValue": false
                }
            ]
        }

❑ name:配置名

❑ type:配置的数据类型

❑ defaultValue:默认值