SpringBoot自定义Starter
1. 创建自己的Starter
一个完整的Spring Boot Starter可能包含以下组件:
- autoconfigure模块:包含自动配置的代码
- starter模块:提供对autoconfigure模块的依赖,以及一些其它的依赖
(PS:如果你不需要区分这两个概念的话,也可以将自动配置代码模块与依赖管理模块合并成一个模块)
简而言之,starter应该提供使用该库所需的一切
1.1. 命名
- 模块名称不能以spring-boot开头
- 如果你的starter提供了配置keys,那么请确保它们有唯一的命名空间。而且,不要用Spring Boot用到的命名空间(比如:server, management, spring 等等)
举个例子,假设你为“acme”创建了一个starter,那么你的auto-configure模块可以命名为acme-spring-boot-autoconfigure,starter模块可以命名为acme-spring-boot-starter。如果你只有一个模块包含这两部分,那么你可以命名为acme-spring-boot-starter。
1.2. autoconfigure模块
建议在autoconfigure模块中包含下列依赖:
1 <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-autoconfigure-processor</artifactId>
4 <optional>true</optional>
5 </dependency>
1.3. starter模块
事实上,starter是一个空jar。它唯一的目的是提供这个库所必须的依赖。
你的starter必须直接或间接引用核心的Spring Boot starter(spring-boot-starter)
2. Hello Starter
接下来,作为入门,写一个Spring Boot版的Hello World
2.1. hello-spring-boot-starter-autoconfigure
新建一个Maven项目命名为hello-spring-boot-starter-autoconfigure
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.5.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.example</groupId>
12 <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>hello-spring-boot-starter-autoconfigure</name>
15 <description>Demo project for Spring Boot</description>
16
17 <properties>
18 <java.version>1.8</java.version>
19 </properties>
20
21 <dependencies>
22 <dependency>
23 <groupId>org.springframework.boot</groupId>
24 <artifactId>spring-boot-starter</artifactId>
25 </dependency>
26 <dependency>
27 <groupId>org.springframework.boot</groupId>
28 <artifactId>spring-boot-autoconfigure</artifactId>
29 </dependency>
30
31 <dependency>
32 <groupId>org.springframework.boot</groupId>
33 <artifactId>spring-boot-configuration-processor</artifactId>
34 <optional>true</optional>
35 </dependency>
36 </dependencies>
37
38 </project>
HelloProperties.java
1 package com.example.hello;
2
3 import org.springframework.boot.context.properties.ConfigurationProperties;
4
5 /**
6 * @author ChengJianSheng
7 * @date 2019-05-26
8 */
9 @ConfigurationProperties("my.hello")
10 public class HelloProperties {
11
12 /**
13 * 姓名
14 */
15 private String name;
16
17 /**
18 * 年龄
19 */
20 private Integer age;
21
22 /**
23 * 家乡
24 */
25 private String hometown;
26
27 public String getName() {
28 return name;
29 }
30
31 public void setName(String name) {
32 this.name = name;
33 }
34
35 public Integer getAge() {
36 return age;
37 }
38
39 public void setAge(Integer age) {
40 this.age = age;
41 }
42
43 public String getHometown() {
44 return hometown;
45 }
46
47 public void setHometown(String hometown) {
48 this.hometown = hometown;
49 }
50
51 @Override
52 public String toString() {
53 return "HelloProperties{" +
54 "name='" + name + '\'' +
55 ", age=" + age +
56 ", hometown='" + hometown + '\'' +
57 '}';
58 }
59 }
HelloService.java
1 package com.example.hello;
2
3 /**
4 * @author ChengJianSheng
5 * @date 2019-05-26
6 */
7 public class HelloService {
8
9 /**
10 * 姓名
11 */
12 private String name;
13
14 /**
15 * 年龄
16 */
17 private Integer age;
18
19 /**
20 * 家乡
21 */
22 private String hometown;
23
24 public HelloService(String name, Integer age, String hometown) {
25 this.name = name;
26 this.age = age;
27 this.hometown = hometown;
28 }
29
30 public String sayHello(String name) {
31 return "Hello, " + name;
32 }
33
34 public String helloWorld() {
35 return String.format("[name=%s, age=%d, hometown=%s]", this.name, this.age, this.hometown);
36 }
37
38 }
HelloServiceAutoConfiguration.java
1 package com.example.hello;
2
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 /**
9 * @author ChengJianSheng
10 * @date 2019-05-26
11 */
12 @Configuration
13 @EnableConfigurationProperties(HelloProperties.class)
14 public class HelloServiceAutoConfiguration {
15
16 private final HelloProperties helloProperties;
17
18 public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
19 this.helloProperties = helloProperties;
20 }
21
22 @Bean
23 @ConditionalOnMissingBean
24 public HelloService helloService() {
25 return new HelloService(this.helloProperties.getName(),
26 this.helloProperties.getAge(),
27 this.helloProperties.getHometown());
28 }
29 }
META-INF/spring.factories
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 com.example.hello.HelloServiceAutoConfiguration
mvn clean install
2.2. hello-spring-boot-starter
在hello-spring-boot-starter中引用该autoconfigure模块
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <groupId>com.example</groupId>
6 <artifactId>hello-spring-boot-starter</artifactId>
7 <version>0.0.1-SNAPSHOT</version>
8 <name>hello-spring-boot-starter</name>
9
10 <properties>
11 <java.version>1.8</java.version>
12 </properties>
13
14 <dependencies>
15 <dependency>
16 <groupId>com.example</groupId>
17 <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
18 <version>0.0.1-SNAPSHOT</version>
19 </dependency>
20 </dependencies>
21
22 </project>
至此,我们的hello-spring-boot-starter开发完了
接下来,我们在demo中引用它
2.3. demo
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.5.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.example</groupId>
12 <artifactId>demo</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>demo</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
19 </properties>
20
21 <dependencies>
22 <dependency>
23 <groupId>org.springframework.boot</groupId>
24 <artifactId>spring-boot-starter-web</artifactId>
25 </dependency>
26
27 <dependency>
28 <groupId>com.example</groupId>
29 <artifactId>hello-spring-boot-starter</artifactId>
30 <version>0.0.1-SNAPSHOT</version>
31 </dependency>
32
33 <dependency>
34 <groupId>org.springframework.boot</groupId>
35 <artifactId>spring-boot-starter-test</artifactId>
36 <scope>test</scope>
37 </dependency>
38 </dependencies>
39
40 <build>
41 <plugins>
42 <plugin>
43 <groupId>org.springframework.boot</groupId>
44 <artifactId>spring-boot-maven-plugin</artifactId>
45 </plugin>
46 </plugins>
47 </build>
48
49 </project>
application.properties
1 my.hello.name=程同学
2 my.hello.age=28
3 my.hello.hometown=湖北省随州市
DemoController.java
1 package com.example.demo.controller;
2
3 import com.example.hello.HelloService;
4 import org.springframework.web.bind.annotation.GetMapping;
5 import org.springframework.web.bind.annotation.PathVariable;
6 import org.springframework.web.bind.annotation.RequestMapping;
7 import org.springframework.web.bind.annotation.RestController;
8
9 import javax.annotation.Resource;
10
11 /**
12 * @author ChengJianSheng
13 * @date 2019-05-26
14 */
15 @RestController
16 @RequestMapping("/demo")
17 public class DemoController {
18
19 @Resource
20 private HelloService helloService;
21
22 @GetMapping("/hello/{name}")
23 public String hello(@PathVariable("name") String name) {
24 return helloService.sayHello(name);
25 }
26
27 @GetMapping("/info")
28 public String info() {
29 return helloService.helloWorld();
30 }
31
32 }
3. 升级版的Hello World
上面的例子中演示了我们引入自定义的starter,然后调用其提供的HelloService服务。感觉好像并没有什么用,下面在此基础上做个升级,再写一个记录日志的服务。
3.1. hello-spring-boot-starter-autoconfigure
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.5.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.example</groupId>
12 <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>hello-spring-boot-starter-autoconfigure</name>
15 <description>Demo project for Spring Boot</description>
16
17 <properties>
18 <java.version>1.8</java.version>
19 </properties>
20
21 <dependencies>
22 <dependency>
23 <groupId>org.springframework.boot</groupId>
24 <artifactId>spring-boot-starter</artifactId>
25 </dependency>
26 <dependency>
27 <groupId>org.springframework.boot</groupId>
28 <artifactId>spring-boot-autoconfigure</artifactId>
29 </dependency>
30 <dependency>
31 <groupId>org.springframework.boot</groupId>
32 <artifactId>spring-boot-starter-web</artifactId>
33 <optional>true</optional>
34 </dependency>
35 <dependency>
36 <groupId>org.projectlombok</groupId>
37 <artifactId>lombok</artifactId>
38 <optional>true</optional>
39 </dependency>
40 <dependency>
41 <groupId>com.alibaba</groupId>
42 <artifactId>fastjson</artifactId>
43 <version>1.2.58</version>
44 <optional>true</optional>
45 </dependency>
46 <dependency>
47 <groupId>org.springframework.boot</groupId>
48 <artifactId>spring-boot-configuration-processor</artifactId>
49 <optional>true</optional>
50 </dependency>
51 </dependencies>
52
53 </project>
MyLog.java
1 package com.example.log;
2
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 /**
9 * @author ChengJianSheng
10 * @date 2019-05-26
11 */
12 @Target(ElementType.METHOD)
13 @Retention(RetentionPolicy.RUNTIME)
14 public @interface MyLog {
15
16 /**
17 * 方法描述
18 */
19 String desc() default "";
20 }
MyLogInterceptor.java
1 package com.example.log;
2
3 import com.alibaba.fastjson.JSON;
4 import lombok.extern.slf4j.Slf4j;
5 import org.springframework.web.method.HandlerMethod;
6 import org.springframework.web.servlet.ModelAndView;
7 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
8
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import java.lang.reflect.Method;
12
13 /**
14 * @author ChengJianSheng
15 * @date 2019-05-26
16 */
17 @Slf4j
18 public class MyLogInterceptor extends HandlerInterceptorAdapter {
19
20 private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
21
22 @Override
23 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
24 HandlerMethod handlerMethod = (HandlerMethod) handler;
25 Method method = handlerMethod.getMethod();
26 MyLog myLog = method.getAnnotation(MyLog.class);
27 if (null != myLog) {
28 // 设置开始时间
29 long startTime = System.currentTimeMillis();
30 startTimeThreadLocal.set(startTime);
31 }
32 return true;
33 }
34
35 @Override
36 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
37 HandlerMethod handlerMethod = (HandlerMethod) handler;
38 Method method = handlerMethod.getMethod();
39 MyLog myLog = method.getAnnotation(MyLog.class);
40 if (null != myLog) {
41 // 获取开始时间
42 long startTime = startTimeThreadLocal.get();
43 long endTime = System.currentTimeMillis();
44 long expendTime = endTime - startTime;
45
46 // 打印参数
47 String requestUri = request.getRequestURI();
48 String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
49 String methodDesc = myLog.desc();
50 String parameters = JSON.toJSONString(request.getParameterMap());
51
52 log.info("\n描述:{}\n路径: {}\n方法: {}\n参数:{}\n耗时:{}", methodDesc, requestUri, methodName, parameters, expendTime);
53 }
54 }
55 }
MyLogAutoConfiguration.java
1 package com.example.log;
2
3 import org.springframework.context.annotation.Configuration;
4 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
5 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6
7 /**
8 * @author ChengJianSheng
9 * @date 2019-05-26
10 */
11 @Configuration
12 public class MyLogAutoConfiguration implements WebMvcConfigurer {
13
14 @Override
15 public void addInterceptors(InterceptorRegistry registry) {
16 registry.addInterceptor(new MyLogInterceptor());
17 }
18 }
META-INF/spring.factories
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 com.example.hello.HelloServiceAutoConfiguration,\
3 com.example.log.MyLogAutoConfiguration
3.2. demo
ProductController.java
1 package com.example.demo.controller;
2
3 import com.example.demo.domain.ProductVO;
4 import com.example.log.MyLog;
5 import org.springframework.web.bind.annotation.*;
6
7 /**
8 * @author ChengJianSheng
9 * @date 2019-05-26
10 */
11 @RestController
12 @RequestMapping("/product")
13 public class ProductController {
14
15 @MyLog(desc = "查询商品")
16 @GetMapping("/list")
17 public String list() {
18 System.out.println("查询商品");
19 return "ok";
20 }
21
22 @MyLog(desc = "添加商品")
23 @PostMapping("/save")
24 public String save(@RequestBody ProductVO productVO) {
25 System.out.println("添加商品");
26 return "ok";
27 }
28
29 @MyLog(desc = "删除商品")
30 @GetMapping("/delete")
31 public String delete(@RequestParam("productId") Long productId) {
32 System.out.println("删除商品");
33 return "ok";
34 }
35
36 @MyLog(desc = "获取商品详情")
37 @GetMapping("/detail/{productId}")
38 public String detail(@PathVariable("productId") Long productId) {
39 System.out.println("商品详情");
40 return "ok";
41 }
42
43 }
查看控制台
(PS:这里就打印日志这个功能没有使用AOP,因为这意味着在自动配置的代码中就要定义切面、切点这些东西,而且在项目启动的时候还要扫描切面,感觉比较麻烦)
4. 工程结构
5. 源码
https://github.com/chengjiansheng?tab=repositories
https://github.com/chengjiansheng/hello-spring-boot-starter-autoconfigure.git
https://github.com/chengjiansheng/starter-demo.git