目录
- 1、自定义Starter
- 1.1、项目结构
- 1.2、代码实现
- 1.3、测试
- 1.4、打包
- 2、引用Starter
- 2.1、项目结构
- 2.2、引入starter
- 3、重写默认配置
- 4、重写默认实现
- 5、实现`@Enable`注解
- 5.1、starter项目创建注解
- 5.2、业务工程使用注解
- 6、元数据
- 7、参考文章
1、自定义Starter
1.1、项目结构
$ tree
.
├── pom.xml
└── src├── main│ ├── java│ │ └── com│ │ └── example│ │ └── demo│ │ ├── ReadingConfiguration.java│ │ ├── config│ │ │ └── ReadingConfig.java│ │ └── service│ │ ├── ReadingService.java│ │ └── impl│ │ └── ReadingServiceImpl.java│ └── resources│ ├── META-INF│ │ └── spring.factories│ └── application.properties└── test├── java│ └── com│ └── example│ └── demo│ └── ReadingServiceTests.java└── resources└── application.yml
1.2、代码实现
pom.xml maven依赖
<?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 https://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.7.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>thinking-starter</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- lombok依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></dependency><!-- 自动配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><!-- 不会编译测试 --><skipTests>true</skipTests></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><skip>true</skip><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
application.properties 默认值
# 注意:该文件名只能是application.properties
# 实践发现:该文件不能出现,否则业务工程的application.yml文件无法覆盖默认值
# 设置默认的值
reading.type=txt
ReadingConfig.java 配置映射
package com.example.demo.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;/*** 读取application.properties中reading相关的配置*/
@Data
@ConfigurationProperties(prefix = "reading")
public class ReadingConfig {// 类型private String type;
}
ReadingService.java 接口
package com.example.demo.service;public interface ReadingService {void reading();
}
ReadingServiceImpl.java 接口实现类
package com.example.demo.service.impl;import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class ReadingServiceImpl implements ReadingService {/*** reading相关配置类*/private ReadingConfig readingConfig;public ReadingServiceImpl(ReadingConfig readingConfig) {this.readingConfig = readingConfig;}@Overridepublic void reading() {log.info("reading type: {}", this.readingConfig.getType());}
}
ReadingConfiguration.java 自动装配的核心
package com.example.demo;import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import com.example.demo.service.impl.ReadingServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@EnableConfigurationProperties(ReadingConfig.class)
// 当存在reading.enable属性,且其值为true时,才初始化该Bean
@ConditionalOnProperty(name = "reading.enable", havingValue = "true")
public class ReadingConfiguration {@Autowiredprivate ReadingConfig readingConfig;// 若当前IOC容器中没有Reading接口实现时,提供一个默认的Reading实现@Bean@ConditionalOnMissingBean(ReadingService.class)public ReadingService readingService() {return new ReadingServiceImpl(this.readingConfig);}
}
spring.factories 自动装配注册
# 提醒SpringBoot在启动时,自动装配ReadingConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.ReadingConfiguration
1.3、测试
ReadingServiceTests.java
package com.example.demo;import com.example.demo.service.ReadingService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = ReadingConfiguration.class)
class ReadingServiceTests {@Autowiredprivate ReadingService readingService;@Testvoid testReadingService() {readingService.reading();}
}
application.yml
reading:enable: true
1.4、打包
将项目安装到本地maven仓库
mvn install
2、引用Starter
新建一个业务工程
2.1、项目结构
$ tree
.
├── pom.xml
└── src└── main├── java│ └── com│ └── example│ └── demo│ ├── Application.java│ └── controller│ └── IndexController.java└── resources└── application.yml
2.2、引入starter
<!-- 引入自定义starter -->
<dependency><groupId>com.example</groupId><artifactId>thinking-starter</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
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 https://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.7.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</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-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- 引入自定义starter --><dependency><groupId>com.example</groupId><artifactId>thinking-starter</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!-- test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
application.yml
reading:type: jsonenable: true
IndexController.java
package com.example.demo.controller;import com.example.demo.service.ReadingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class IndexController {@Autowired(required = false)private ReadingService readingService;@GetMapping("/")public String index(){if(this.readingService != null){this.readingService.reading();}return "Hello";}
}
Application.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
3、重写默认配置
修改项目配置文件application.yml
reading: enable: true type: json # 重写starter默认配置
4、重写默认实现
默认提供的实现
public class ReadingConfiguration {@Bean@ConditionalOnMissingBean(ReadingService.class)public ReadingService readingService() {return new ReadingServiceImpl(this.readingConfig);}
}
若当前IOC容器中没有Reading接口实现时,提供一个默认的Reading实现
@ConditionalOnMissingBean(ReadingService.class)
可以自行实现ReadingService
package com.example.web.service;import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class MyReadingService implements ReadingService {@Autowiredprivate ReadingConfig readingConfig;@Overridepublic void reading() {log.info("my reading service start reading... type is {}", this.readingConfig.getType());}}
5、实现@Enable
注解
类似的注解
@EnableScheduling
@EnableAsync
@EnableCaching
5.1、starter项目创建注解
创建注解
package com.example.demo.annotation;import com.example.demo.ReadingSelector;
import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Import作用是往SpringIOC中导入一个类,这里即导入ReadingSelector
@Import(ReadingSelector.class)
public @interface EnableReading {}
创建 ReadingSelector类取代 ReadingConfiguration
package com.example.demo;
import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import com.example.demo.service.impl.ReadingServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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;@Configuration
@EnableConfigurationProperties(ReadingConfig.class)
public class ReadingSelector {@Autowiredprivate ReadingConfig readingConfig;// 当SpringIOC容器中不存在Reading实现时,才往Spring中初始化ReadingService作为Reading接口的实现@Bean@ConditionalOnMissingBean(ReadingService.class)public ReadingService readingService() {return new ReadingServiceImpl(this.readingConfig);}}
5.2、业务工程使用注解
修改配置文件 application.yml
reading:# enable: truetype: json
启动类增加注解@EnableReading
package com.example.web;import com.example.demo.annotation.EnableReading;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@EnableReading
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
6、元数据
元数据描述后,开发者便能通过IDE提示看到此配置属性的默认值/值类型等信息
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
重新打包,会自动生成spring-configuration-metadata.json
7、参考文章
- 分分钟!手写一个自己的springboot starter
- 好好地说一次,自定义springboot-starter开发(从配置到单元测试)
项目代码:https://github.com/mouday/spring-boot-demo