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
2 org.springframework.boot
3 spring-boot-autoconfigure-processor
4 true
5
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
2 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 4.0.0
5
6 org.springframework.boot
7 spring-boot-starter-parent
8 2.1.5.RELEASE
9
10
11 com.example
12 hello-spring-boot-starter-autoconfigure
13 0.0.1-SNAPSHOT
14 hello-spring-boot-starter-autoconfigure
15 Demo project for Spring Boot
16
17
18 1.8
19
20
21
22
23 org.springframework.boot
24 spring-boot-starter
25
26
27 org.springframework.boot
28 spring-boot-autoconfigure
29
30
31
32 org.springframework.boot
33 spring-boot-configuration-processor
34 true
35
36
37
38
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
2 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 4.0.0
5 com.example
6 hello-spring-boot-starter
7 0.0.1-SNAPSHOT
8 hello-spring-boot-starter
9
10
11 1.8
12
13
14
15
16 com.example
17 hello-spring-boot-starter-autoconfigure
18 0.0.1-SNAPSHOT
19
20
21
22
至此,我们的hello-spring-boot-starter开发完了
接下来,我们在demo中引用它
2.3. demo
1
2 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 4.0.0
5
6 org.springframework.boot
7 spring-boot-starter-parent
8 2.1.5.RELEASE
9
10
11 com.example
12 demo
13 0.0.1-SNAPSHOT
14 demo
15
16
17 1.8
18 UTF-8
19
20
21
22
23 org.springframework.boot
24 spring-boot-starter-web
25
26
27
28 com.example
29 hello-spring-boot-starter
30 0.0.1-SNAPSHOT
31
32
33
34 org.springframework.boot
35 spring-boot-starter-test
36 test
37
38
39
40
41
42
43 org.springframework.boot
44 spring-boot-maven-plugin
45
46
47
48
49
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
2 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 4.0.0
5
6 org.springframework.boot
7 spring-boot-starter-parent
8 2.1.5.RELEASE
9
10
11 com.example
12 hello-spring-boot-starter-autoconfigure
13 0.0.1-SNAPSHOT
14 hello-spring-boot-starter-autoconfigure
15 Demo project for Spring Boot
16
17
18 1.8
19
20
21
22
23 org.springframework.boot
24 spring-boot-starter
25
26
27 org.springframework.boot
28 spring-boot-autoconfigure
29
30
31 org.springframework.boot
32 spring-boot-starter-web
33 true
34
35
36 org.projectlombok
37 lombok
38 true
39
40
41 com.alibaba
42 fastjson
43 1.2.58
44 true
45
46
47 org.springframework.boot
48 spring-boot-configuration-processor
49 true
50
51
52
53
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