1,自动配置项目—代替xml
META-INF/spring.factories 做好配置类指定,此时可以不需要配置属性文件,用业务中的即可
@ConfigurationProperties 没有属性配置就是空—相当于懒加载配置,@value没有配置就是异常
2,自动依赖项目,依赖自动装配项目,集成一个小功能的所有依赖(会使用到自动装配)—例如切面记录日志
3,业务项目,依赖自动依赖项目,上层的配置文件会被底层依赖的包配置全局使用
实际的做法的1,2都合并为starter 所以会出现一旦引入starter就需要,属性中配置,不配置会出现为空的情况
程序中的1 @Configuration框架可以自动识别加载,依赖包中的需要spring-factories中指定
示例:
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. 源码
chengjiansheng (chengjiansheng) / Repositories · GitHub
https://github.com/chengjiansheng/hello-spring-boot-starter-autoconfigure.git
https://github.com/chengjiansheng/starter-demo.git