一、起源
在Spring時(shí)代,搭建一個(gè) Web 應(yīng)用通常需要在 pom 文件中引入多個(gè) Web 模塊相關(guān)的 Maven 依賴,如:SpringMvc、Tomcat等依賴,而 SpringBoot 則只需引入spring-boot-starter-web依賴即可。這就是SpringBoot 的 Starter特性,用來(lái)簡(jiǎn)化項(xiàng)目初始搭建以及開(kāi)發(fā)過(guò)程,它是一個(gè)功能模塊的所有 Maven 依賴集合體。
SpringBoot 用起來(lái)方便,它默認(rèn)集成了 Java 的主流框架。這也是SpringBoot的一大特色,使用方便,需要什么框架或者技術(shù),只需要引入對(duì)應(yīng)的 starter 即可。
即使官方集成了很多主流框架,但SpringBoot官方也不能囊括我們所有的使用場(chǎng)景,往往我們需要自定義starter,來(lái)簡(jiǎn)化我們對(duì)SpringBoot的使用。
二、概述
2.1 starter示例
SpringBoot 提供了非常多的 Starter,下面列出常用的幾個(gè):
| 名稱 | 功能 |
|---|---|
| spring-boot-starter-web | 支持 Web 開(kāi)發(fā),包括 Tomcat 和 spring-webmvc |
| spring-boot-starter-redis | 支持 Redis 鍵值存儲(chǔ)數(shù)據(jù)庫(kù),包括 spring-redis |
| spring-boot-starter-test | 支持常規(guī)的測(cè)試依賴,包括 JUnit、Hamcrest、Mockito 以及 spring-test 模塊 |
| spring-boot-starter-aop | 支持面向切面的編程即 AOP,包括 spring-aop 和 AspectJ |
| spring-boot-starter-data-elasticsearch | 支持 ElasticSearch 搜索和分析引擎,包括 spring-data-elasticsearch |
| spring-boot-starter-jdbc | 支持JDBC數(shù)據(jù)庫(kù) |
| spring-boot-starter-data-jpa | 支持 JPA ,包括 spring-data-jpa、spring-orm、Hibernate |
這些 Starter 其實(shí)不包含 Java 代碼,核心是它的 pom 文件。我們以 spring-boot-starter-web 為例,來(lái)看看該 Starter 的 pom 文件包含的內(nèi)容。
先在項(xiàng)目中引入以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
然后找到引入的 spring-boot-starter-web 依賴的文件夾位置:
打開(kāi)該 pom 文件進(jìn)行查看:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.3.RELEASE</version>
<name>Spring Boot Web Starter</name>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- 對(duì) json 解析的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- 提供 Tomcat 容器。通過(guò)這可以看到 ServletWeb 默認(rèn)的容器是 Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- hibernate 的校驗(yàn)框架 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
<scope>compile</scope>
</dependency>
<!-- 提供了核心 HTTP 集成,用于集成其它 web 框架的基礎(chǔ)結(jié)構(gòu) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- 提供了對(duì) Spring MVC 的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.7.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
可以看到,在該 pom 文件中已經(jīng)定義好了 Web 模塊需要的各個(gè)組件。之后,引入的 Starter 依賴可以與 SpringBoot 的自動(dòng)裝配特性、外部化配置特性進(jìn)行無(wú)縫銜接,來(lái)達(dá)到快速開(kāi)發(fā)的目的。
可以看到這些 Starter 的名稱都是以spring-boot-starter為開(kāi)頭,后面跟著具體的模塊名,所有官方的 Starter 遵循相似的命名模式。
2.2 starter命名規(guī)范
starter的命名規(guī)則,命名規(guī)則分為兩種,一種是官方的命名規(guī)則,另一種就是我們自己制作的starter命名規(guī)則。
2.2.1 官方命名規(guī)則
前綴:spring-boot-starter-
規(guī)則:spring-boot-starter-模塊名
舉例:spring-boot-starter-web、spring-boot-starter-jdbc
2.2.2 自定義命名規(guī)則
后綴:-spring-boot-starter
規(guī)則:模塊-spring-boot-starter
舉例:hello-spring-boot-starter
三、實(shí)例剖析
比如需要在Spring Boot中使用Rabbit MQ,我們只需要引入依賴以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
然后在application.properties或application.yml中添加以下數(shù)據(jù)庫(kù)配置信息:
spring:
rabbitmq:
addresses: ${RABBIT_ADDRESS:192.168.0.100:5672}
username: ${RABBIT_USERNAME:guest}
password: ${RABBIT_PASSWORD:guest}
然后我們就可以自動(dòng)注入RabbitTemplate了。
@Service
public class MessageService {
@Autowired
RabbitTemplate rabbitTemplate;
}
這一切的核心,就是自動(dòng)裝配。
3.1 Spring Boot 自動(dòng)裝配原理
Spring Boot依賴spring-boot-autoconfigure,這個(gè)jar包是幫助spring boot自動(dòng)裝配所有需要的依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
創(chuàng)建一個(gè)spring boot工程,系統(tǒng)會(huì)自動(dòng)引入這個(gè)jar包,查看jar包下的META-INF --> spring.factories文件,會(huì)發(fā)現(xiàn)默認(rèn)自動(dòng)添加了許多模塊。比如我們上面講到的RabbitAutoConfiguration。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ # 注意看這里...
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
# 其它的省略
重要的事情說(shuō)三遍,以下內(nèi)容為:重點(diǎn),重點(diǎn),重點(diǎn)!
Spring Boot在啟動(dòng)時(shí),會(huì)解析這些配置文件。我們看到,org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration這個(gè)類被加進(jìn)去了,我們以這個(gè)作為列子講解。
展開(kāi)spring-boot-autoconfigure下的org.springframework.boot.autoconfigure --> amqp,找到RabbitAutoConfiguration,打開(kāi)其源代碼:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class }) // 參見(jiàn)下邊注釋1
@EnableConfigurationProperties(RabbitProperties.class) // 參見(jiàn)下邊注釋2
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
// 其它代碼省略...
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate();
configurer.configure(template, connectionFactory);
return template;
}
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
@ConditionalOnMissingBean // 參見(jiàn)下邊注釋3
public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
}
}
可以看到,在這個(gè)類里邊,我們@Bean注解了RabbitTemplate和AmqpAdmin讓Spring管理這兩個(gè)實(shí)例,這就是我們可以在自己的代碼中自動(dòng)注入這兩個(gè)類實(shí)列的原因。
這里需要解析幾個(gè)注解:
-
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
這個(gè)注解的意思是,如果Spring Boot應(yīng)用程序中包含RabbitTemplate和Channel這兩個(gè)類,RabbitAutoConfiguration配置文件才生效,否則不解析配置文件。所以當(dāng)我們引入spring-boot-starter-amqp依賴的時(shí)候,這個(gè)配置就生效了,Spring Boot就會(huì)幫我們創(chuàng)建RabbitTemplate的實(shí)例。 -
@EnableConfigurationProperties(RabbitProperties.class)
這個(gè)注解就是注入RabbitProperties實(shí)例,這是一個(gè)讀取配置文件的的配置類,代碼如下:
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
private static final int DEFAULT_PORT = 5672;
private static final int DEFAULT_PORT_SECURE = 5671;
/**
* RabbitMQ host. Ignored if an address is set.
*/
private String host = "localhost";
/**
* RabbitMQ port. Ignored if an address is set. Default to 5672, or 5671 if SSL is
* enabled.
*/
private Integer port;
/**
* Login user to authenticate to the broker.
*/
private String username = "guest";
// 省略其它代碼...
這個(gè)類就是從我們的yml配置文件中讀取Rabbit 連接等配置信息。
-
@ConditionalOnMissingBean
這是放在創(chuàng)建Bean的方法注解,意思是如果Amqpadmin這個(gè)類存在,就不創(chuàng)建了。這給用戶靈活的選擇,可以創(chuàng)建自己的Ampqadmin來(lái)管理Rabbit MQ,也可以不創(chuàng)建,使用系統(tǒng)默認(rèn)的。
重點(diǎn)結(jié)束,到這里,是不是理解了Spring Boot自動(dòng)裝配的原理了。
四、創(chuàng)建自己的starter
4.1 應(yīng)用場(chǎng)景
為什么要?jiǎng)?chuàng)建一個(gè)spring boot starter項(xiàng)目呢,目的是為了使用方便和代碼復(fù)用。舉個(gè)例子,假如你有個(gè)監(jiān)控系統(tǒng)叫monitor-center,很多系統(tǒng)都需要調(diào)用這個(gè)監(jiān)控系統(tǒng)獲取監(jiān)控?cái)?shù)據(jù)。這個(gè)監(jiān)控系統(tǒng)使用OAuth 2.0進(jìn)行授權(quán)的,在使用監(jiān)控系統(tǒng)前,需要先使用賬戶密碼登錄安全中心,獲取token,然后使用token訪問(wèn)監(jiān)控系統(tǒng)。如果token過(guò)期,又需要訪問(wèn)安全中心,刷新token。之后是訪問(wèn)監(jiān)控系統(tǒng)獲取監(jiān)控?cái)?shù)據(jù)。
上邊細(xì)節(jié)還挺繁瑣的,如果每個(gè)監(jiān)控應(yīng)用都實(shí)現(xiàn)一遍,工作任務(wù)就太大了,于是我們考慮做一個(gè)monitor-boot-starter,包裝所有細(xì)節(jié)。然后提供一個(gè)MonitorService給客戶使用。
4.2 創(chuàng)建項(xiàng)目
下面開(kāi)始創(chuàng)建項(xiàng)目:
4.2.1 第一步,新建starter項(xiàng)目
創(chuàng)建一個(gè)Spring boot工程,取名monitor-boot-starter,引入如下依賴:
<?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.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.erbadagang.demo</groupId>
<artifactId>monitor-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>monitor-boot-starter</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-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
這里需要注意兩個(gè)細(xì)節(jié):
- 命名規(guī)范,Spring官方的starter,都叫spring-boot-starter-<模塊名稱>,我們自己定義的start,都命名為 <模塊名稱>-boot-starter。
- 添加了另一個(gè)依賴spring-boot-configuration-processor,并且在plugin的configuration中排除它,這在后邊會(huì)講到。
4.2.2 第二步,創(chuàng)建一個(gè)config包
添加以下三個(gè)類:
package com.erbadagang.demo.monitor.autoconfig;
/**
* 這是自動(dòng)裝配類,初始化monitor-boot-starter中所有需要用到的bean。
*/
@Configuration
@ConditionalOnClass(MonitorService.class)
@EnableConfigurationProperties(MonitorProperties.class)
public class MonitorAutoConfiguration {
@Autowired
private MonitorProperties properties;
@Bean
@ConditionalOnMissingBean
public MonitorService monitorService() {
return new MonitorService(properties);
}
}
/**
* 這是配置類,用于從application.yml或application.properties中讀取配置信息
*/
@ConfigurationProperties(prefix = "demo.monitor")
public class MonitorProperties {
private String loginUrl;
private String username;
private String password;
private String serverUrl;
// 省略getter,setter方法...
}
/**
* 這是我們這個(gè)應(yīng)用的核心類,應(yīng)該在自動(dòng)裝配類中創(chuàng)建。
*/
public class MonitorService {
private MonitorProperties properties;
public MonitorService(MonitorProperties properties) {
this.properties = properties;
}
public void subscribe(String url, Consumer<String> callback) {
System.out.println("login to security center:");
System.out.println("loginUrl=" + properties.getLoginUrl());
System.out.println("username=" + properties.getUsername());
System.out.println("password=" + properties.getPassword());
System.out.println("connect to monitor:");
System.out.println("serverUrl=" + properties.getServerUrl());
System.out.println("receive monitor data");
callback.accept("current time:" + new Date().toString());
callback.accept("current time:" + new Date().toString());
callback.accept("current time:" + new Date().toString());
}
public void unsubscribe(String url) {
System.out.println("unsubscribe:" + url);
}
}
每個(gè)類的功能和作用,都做了注釋。
4.2.3 第三步,配置spring.factories
在resources目錄下,創(chuàng)建META-INF文件夾,然后創(chuàng)建文件spring.factories,添加以下內(nèi)容。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.erbadagang.demo.monitor.autoconfig.MonitorAutoConfiguration
Spring boot starter啟動(dòng)的時(shí)候,這個(gè)文件的的內(nèi)容會(huì)被合并到spring-boot-autoconfigure的spring.factories中。這就告訴Spring Boot,系統(tǒng)啟動(dòng)時(shí),除了掃描默認(rèn)的自動(dòng)裝配類,也掃描com.erbadagang.demo.monitor.autoconfig.MonitorAutoConfiguration這個(gè)類。
這個(gè)項(xiàng)目已經(jīng)創(chuàng)建結(jié)束了,現(xiàn)在只需要打包安裝就可以使用了。使用IDE或者執(zhí)行以下maven命令:mvn clean compile install。
五、使用自定義Spring Boot starter
我們來(lái)創(chuàng)建一個(gè)項(xiàng)目,使用剛才我們自己創(chuàng)建的monitor-boot-starter。
5.1 第一步,創(chuàng)建Spring Boot工程
取名demo,添加以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.erbadagang.demo</groupId>
<artifactId>monitor-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
5.2 第二步,在application.yml中,添加以下內(nèi)容:
demo:
monitor:
server-url: https://www.demo-monitor.com
username: MessiLoveRidingBike
password: 123456
當(dāng)你在application.yml中輸入demo的時(shí)候,是不是還帶自動(dòng)提示功能的,這是咋整的?后續(xù)分析... ...
5.3 第三步,使用MonitorService
在DemoApplication.java類中,注入MonitorService并調(diào)用subscribe方法。
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private MonitorService monitorService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
monitorService.subscribe("abc", data -> System.out.println("receive data:" + data));
}
}
運(yùn)行程序,得到輸入如下:
login to security center:
loginUrl=https://www.demo-login.com
username=MessiLoveRidingBike
password=123456
connect to monitor:
serverUrl=https://www.demo-monitor.com
receive monitor data
receive data:current time:Fri Sep 11 13:53:05 CST 2020
receive data:current time:Fri Sep 11 13:53:05 CST 2020
receive data:current time:Fri Sep 11 13:53:05 CST 2020
六、Spring Boot Configuration Metadata
剛才我們?cè)谑褂胢onitor-boot-starter的時(shí)候,居然還帶自動(dòng)提示。這是為什么呢,現(xiàn)在來(lái)解釋一下。
在我們工程monitor-boot-starter的pom.xml中,我們添加了pring-boot-configuration-processor這個(gè)依賴,這個(gè)依賴的作用是,在編譯時(shí),為所有的添加了@ConfigurationProperties的類添加元數(shù)據(jù),這些元數(shù)據(jù)是可以被IDE讀到的,我們?cè)赼pplication.yml中輸入demo的時(shí)候,就會(huì)自動(dòng)提示demo.monitor.login-url。
<?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.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.erbadagang.demo</groupId>
<artifactId>monitor-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>monitor-boot-starter</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-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
當(dāng)然根據(jù)官方的要求,需要在spring-boot-maven-plugin中排除掉spring-boot-configuration-processor。具體請(qǐng)參見(jiàn)Configuring the Annotation Processor
Configuration Metadata還可以做的更加智能,不但給出智能提示,還把可選項(xiàng)都可以列出來(lái)。
七、總結(jié)
如果要?jiǎng)?chuàng)建一個(gè)spring boot starter,可以參考以下步驟:
- 首先需要引入spring-boot-autoconfigure和spring-boot-configuration-processor依賴,后邊這個(gè)依賴是用來(lái)產(chǎn)生配置文件元數(shù)據(jù)的。
- 創(chuàng)建AutoConguration類,這里需要理解兩個(gè)注解,
@ConditionalOnClass(MonitorService.class)和@ConditionalOnMissingBean,第一個(gè)注解是放在自動(dòng)裝配類上的,表示只有當(dāng)MonitorService類存在的時(shí)候才執(zhí)行自動(dòng)裝配。第二個(gè)類是放在創(chuàng)建Bean的方法上,表示只有當(dāng)前類實(shí)例不存在的時(shí)候才創(chuàng)建,這給用戶很大的靈活性??梢宰约簞?chuàng)建,也可以使用系統(tǒng)默認(rèn)的Bean。 - 創(chuàng)建配置文件類,從application.yml中讀取配置文件信息。
- 在resources目錄下,創(chuàng)建META-INF文件夾,并在該文件夾下創(chuàng)建spring.factories,添加需要自動(dòng)裝配的類。Spring Boot會(huì)在啟動(dòng)時(shí),合并spring.factories中的內(nèi)容。
- 最后就是打包安裝,然后使用啦。
八、AutoConguration的另類用法
以前,為了實(shí)現(xiàn)parent基礎(chǔ)項(xiàng)目的某個(gè)service被其他業(yè)務(wù)類項(xiàng)目復(fù)用,使用了類似starter的解決方案,實(shí)現(xiàn)公用邏輯可以在被其他項(xiàng)目通過(guò)@Autowired自動(dòng)裝配。
方案是按照自己想法寫(xiě)的沒(méi)有遵循Spring Boot Starter規(guī)范,但是達(dá)到了同樣的目的,可以說(shuō)是輕量級(jí)的starter,供大家以后工作中參考。
業(yè)務(wù)邏輯:parent項(xiàng)目——
pay_common編寫(xiě)郵件發(fā)送邏輯,后續(xù)各個(gè)項(xiàng)目使用時(shí)直接POM引入pay_common依賴,配置yml后通過(guò)@Autowired使用,來(lái)避免各個(gè)項(xiàng)目都造輪子。
8.1 parent項(xiàng)目——pay_common
編寫(xiě)一個(gè)普通的@Service類來(lái)實(shí)現(xiàn)郵件發(fā)送功能
package com.erbadagang.pay.common.service;
import com.erbadagang.pay.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.messaging.MessagingException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.mail.internet.MimeMessage;
/**
* @ClassName: MailService
* @Description:郵件發(fā)送service
* @author: 郭秀志 jbcode@126.com
* @date: 2020年1月18日 下午1:37:09
* @Copyright:
*/
@Service //這里使用了Service注解,沒(méi)有使用starter的標(biāo)準(zhǔn)用法注解。
@Slf4j
public class MailService {
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String from;
@Value("${spring.mail.toUser}")
private String toUser;
@Value("${spring.mail.ccUsers}")
private String ccUsers;
@Value("${spring.mail.bccUsers}")
private String bccUsers;
@Async
public void sendMail(String subject, String text) throws MessagingException, javax.mail.MessagingException {
log.info("開(kāi)始發(fā)送郵件通知,郵件內(nèi)容為:[{}]", text);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper mMessageHelper = new MimeMessageHelper(message, true, "UTF-8");
// 發(fā)件人郵箱
mMessageHelper.setFrom(from);
// 收件人郵箱
mMessageHelper.setTo(toUser);
// 郵件的主題也就是郵件的標(biāo)題
mMessageHelper.setSubject(subject);
mMessageHelper.setText(text, true);
if (StringUtils.isNotEmpty(ccUsers)) {
mMessageHelper.setCc(ccUsers.split(","));
}
if (StringUtils.isNotEmpty(bccUsers)) {
mMessageHelper.setBcc(bccUsers.split(","));
}
mailSender.send(message);
log.info("發(fā)送郵件通知成功,郵件內(nèi)容為:[{}]", text);
}
}
說(shuō)明:
沒(méi)有寫(xiě)AutoConfiguration類,直接寫(xiě)Service了。所以沒(méi)有下面的AutoConfiguration注解:
@Configuration
@ConditionalOnClass(MailService.class) //正常我們依賴MailService觸發(fā)自動(dòng)裝配
@EnableConfigurationProperties(MonitorProperties.class) //直接在Service類里面使用@Value注解讀取配置文件了。
8.2 配置AutoConfiguration
編寫(xiě)spring.factories (也是Properties)文件。

com.erbadagang.pay.common.service.MailService ,來(lái)讓依賴這個(gè)項(xiàng)目的其他Springboot項(xiàng)目自動(dòng)掃描MailService進(jìn)行自動(dòng)裝配:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.erbadagang.pay.common.autoconfig.schedlock.ShedlockConfig,\
com.erbadagang.pay.common.service.MailService
8.3 使用方引入依賴
業(yè)務(wù)項(xiàng)目的pom.xml引入parent項(xiàng)目——pay_common的依賴。
<dependency>
<groupId>com.pay.parent</groupId>
<artifactId>pay_common</artifactId>
</dependency>
8.4 @Autowired MailService
業(yè)務(wù)項(xiàng)目使用MailServic的邏輯如下:
package com.erbadagang.pay.controller;
import com.erbadagang.pay.common.message.JsonResult;
import com.erbadagang.pay.common.service.MailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessagingException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: RestTemplateController
* @Description:mail郵件發(fā)送。
* @author: 郭秀志 jbcode@126.com
* @date: 2020年1月14日 下午12:44:08
* @Copyright:
*/
@RestController
@Slf4j
@RequestMapping(value = "/mail")
public class MailController {
@Autowired
private MailService mailService;
@RequestMapping(value = "/send")
public JsonResult<String> sendMail() throws MessagingException, javax.mail.MessagingException {
mailService.sendMail("郵件發(fā)送by springboot ", "郵件正文,通過(guò)spring-boot-starter-mail");
log.info("發(fā)送郵件完成");
System.out.println();
return JsonResult.of("發(fā)送郵件完成", true, "成功調(diào)用");
}
}
8.5 配置信息
業(yè)務(wù)項(xiàng)目在yml文件配置Mail的信息
spring:
application:
name: erbadagang-payment-settlement
mail:
host: ${MAIL_HOST:mail.guo.com.cn}
username: ${MAIL_USERNAME:guoxiuzhi}
password: ${MAIL_PASSWORD:!QA12s32t4LKJ45f0}
toUser: jbcode@126.com
ccUsers:
bccUsers:
protocol: smtp
properties:
mail:
smtp:
starttls:
enable: true
8.6 結(jié)論
綜上自己“發(fā)明”了一個(gè)簡(jiǎn)單的Spring Boot Starter,并使用之。