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 模块中包含下列依赖:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-autoconfigure-processor</artifactId>
- <optional>true</optional>
- </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
- <?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>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.1.5.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>hello-spring-boot-starter-autoconfigure</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-autoconfigure</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-configuration-processor</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
- </project>
HelloProperties.java
- package com.example.hello;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @ConfigurationProperties("my.hello")
- public class HelloProperties {
- /**
- * 姓名
- */
- private String name;
- /**
- * 年龄
- */
- private Integer age;
- /**
- * 家乡
- */
- private String hometown;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- public String getHometown() {
- return hometown;
- }
- public void setHometown(String hometown) {
- this.hometown = hometown;
- }
- @Override
- public String toString() {
- return "HelloProperties{" +
- "name='" + name + '\'' +
- ", age=" + age +
- ", hometown='" + hometown + '\'' +
- '}';
- }
- }
HelloService.java
- package com.example.hello;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- public class HelloService {
- /**
- * 姓名
- */
- private String name;
- /**
- * 年龄
- */
- private Integer age;
- /**
- * 家乡
- */
- private String hometown;
- public HelloService(String name, Integer age, String hometown) {
- this.name = name;
- this.age = age;
- this.hometown = hometown;
- }
- public String sayHello(String name) {
- return "Hello," + name;
- }
- public String helloWorld() {
- return String.format("[name=%s, age=%d, hometown=%s]", this.name, this.age, this.hometown);
- }
- }
HelloServiceAutoConfiguration.java
- package com.example.hello;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
- import org.springframework.boot.context.properties.EnableConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @Configuration
- @EnableConfigurationProperties(HelloProperties.class)
- public class HelloServiceAutoConfiguration {
- private final HelloProperties helloProperties;
- public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
- this.helloProperties = helloProperties;
- }
- @Bean
- @ConditionalOnMissingBean
- public HelloService helloService() {
- return new HelloService(this.helloProperties.getName(),
- this.helloProperties.getAge(),
- this.helloProperties.getHometown());
- }
- }
- META-INF/spring.factories
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.example.hello.HelloServiceAutoConfiguration
- mvn clean install
- 2.2. hello-spring-boot-starter
在 hello-spring-boot-starter 中引用该 autoconfigure 模块
- <?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.example</groupId>
- <artifactId>hello-spring-boot-starter</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>hello-spring-boot-starter</name>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>com.example</groupId>
- <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- </dependencies>
- </project>
至此, 我们的 hello-spring-boot-starter 开发完了
接下来, 我们在 demo 中引用它
- 2.3. demo
- <?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>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.1.5.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>demo</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>demo</name>
- <properties>
- <java.version>1.8</java.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-Web</artifactId>
- </dependency>
- <dependency>
- <groupId>com.example</groupId>
- <artifactId>hello-spring-boot-starter</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- application.properties
my.hello.name = 程同学
my.hello.age=28
my.hello.hometown = 湖北省随州市
DemoController.java
- package com.example.demo.controller;
- import com.example.hello.HelloService;
- import org.springframework.Web.bind.annotation.GetMapping;
- import org.springframework.Web.bind.annotation.PathVariable;
- import org.springframework.Web.bind.annotation.RequestMapping;
- import org.springframework.Web.bind.annotation.RestController;
- import javax.annotation.Resource;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @RestController
- @RequestMapping("/demo")
- public class DemoController {
- @Resource
- private HelloService helloService;
- @GetMapping("/hello/{name}")
- public String hello(@PathVariable("name") String name) {
- return helloService.sayHello(name);
- }
- @GetMapping("/info")
- public String info() {
- return helloService.helloWorld();
- }
- }
3. 升级版的 Hello World
上面的例子中演示了我们引入自定义的 starter, 然后调用其提供的 HelloService 服务. 感觉好像并没有什么用, 下面在此基础上做个升级, 再写一个记录日志的服务.
3.1. hello-spring-boot-starter-autoconfigure
pom.xml
- <?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>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.1.5.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>hello-spring-boot-starter-autoconfigure</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-autoconfigure</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-Web</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.58</version>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-configuration-processor</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
- </project>
MyLog.java
- package com.example.log;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyLog {
- /**
- * 方法描述
- */
- String desc() default "";
- }
MyLogInterceptor.java
- package com.example.log;
- import com.alibaba.fastjson.JSON;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.Web.method.HandlerMethod;
- import org.springframework.Web.servlet.ModelAndView;
- import org.springframework.Web.servlet.handler.HandlerInterceptorAdapter;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.lang.reflect.Method;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @Slf4j
- public class MyLogInterceptor extends HandlerInterceptorAdapter {
- private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- Method method = handlerMethod.getMethod();
- MyLog myLog = method.getAnnotation(MyLog.class);
- if (null != myLog) {
- // 设置开始时间
- long startTime = System.currentTimeMillis();
- startTimeThreadLocal.set(startTime);
- }
- return true;
- }
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- Method method = handlerMethod.getMethod();
- MyLog myLog = method.getAnnotation(MyLog.class);
- if (null != myLog) {
- // 获取开始时间
- long startTime = startTimeThreadLocal.get();
- long endTime = System.currentTimeMillis();
- long expendTime = endTime - startTime;
- // 打印参数
- String requestUri = request.getRequestURI();
- String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
- String methodDesc = myLog.desc();
- String parameters = JSON.toJSONString(request.getParameterMap());
- log.info("\n 描述:{}\n 路径: {}\n 方法: {}\n 参数:{}\n 耗时:{}", methodDesc, requestUri, methodName, parameters, expendTime);
- }
- }
- }
MyLogAutoConfiguration.java
- package com.example.log;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.Web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.Web.servlet.config.annotation.WebMvcConfigurer;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @Configuration
- public class MyLogAutoConfiguration implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new MyLogInterceptor());
- }
- }
- META-INF/spring.factories
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.example.hello.HelloServiceAutoConfiguration,\
- com.example.log.MyLogAutoConfiguration
- 3.2. demo
ProductController.java
- package com.example.demo.controller;
- import com.example.demo.domain.ProductVO;
- import com.example.log.MyLog;
- import org.springframework.Web.bind.annotation.*;
- /**
- * @author ChengJianSheng
- * @date 2019-05-26
- */
- @RestController
- @RequestMapping("/product")
- public class ProductController {
- @MyLog(desc = "查询商品")
- @GetMapping("/list")
- public String list() {
- System.out.println("查询商品");
- return "ok";
- }
- @MyLog(desc = "添加商品")
- @PostMapping("/save")
- public String save(@RequestBody ProductVO productVO) {
- System.out.println("添加商品");
- return "ok";
- }
- @MyLog(desc = "删除商品")
- @GetMapping("/delete")
- public String delete(@RequestParam("productId") Long productId) {
- System.out.println("删除商品");
- return "ok";
- }
- @MyLog(desc = "获取商品详情")
- @GetMapping("/detail/{productId}")
- public String detail(@PathVariable("productId") Long productId) {
- System.out.println("商品详情");
- return "ok";
- }
- }
查看控制台
(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
来源: https://www.cnblogs.com/cjsblog/p/10926408.html