自定义Spring|自定义Spring Boot Starter
一、起源
在Spring
时代,搭建一个 Web 应用通常需要在 pom 文件中引入多个 Web 模块相关的 Maven 依赖,如:SpringMvc、Tomcat
等依赖,而 SpringBoot 则只需引入spring-boot-starter-web
依赖即可。这就是SpringBoot 的 Starter
特性,用来简化项目初始搭建以及开发过程,它是一个功能模块的所有 Maven 依赖集合体。
SpringBoot
用起来方便,它默认集成了 Java 的主流框架。这也是SpringBoot
的一大特色,使用方便,需要什么框架或者技术,只需要引入对应的 starter 即可。
即使官方集成了很多主流框架,但SpringBoot
官方也不能囊括我们所有的使用场景,往往我们需要自定义starter
,来简化我们对SpringBoot
的使用。
二、概述
2.1 starter示例
SpringBoot 提供了非常多的 Starter,下面列出常用的几个:
名称 | 功能 |
---|---|
spring-boot-starter-web | 支持 Web 开发,包括 Tomcat 和 spring-webmvc |
spring-boot-starter-redis | 支持 Redis 键值存储数据库,包括 spring-redis |
spring-boot-starter-test | 支持常规的测试依赖,包括 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数据库 |
spring-boot-starter-data-jpa | 支持 JPA ,包括 spring-data-jpa、spring-orm、Hibernate |
先在项目中引入以下依赖:
org.springframework.boot
spring-boot-starter-web
2.0.3.RELEASE
然后找到引入的 spring-boot-starter-web 依赖的文件夹位置:
文章图片
打开该 pom 文件进行查看:
4.0.0 org.springframework.boot
spring-boot-starters
2.0.3.RELEASE
org.springframework.boot
spring-boot-starter-web
2.0.3.RELEASE
Spring Boot Web Starter ...
org.springframework.boot
spring-boot-starter
2.0.3.RELEASE
compile
org.springframework.boot
spring-boot-starter-json
2.0.3.RELEASE
compile
org.springframework.boot
spring-boot-starter-tomcat
2.0.3.RELEASE
compile
org.hibernate.validator
hibernate-validator
6.0.10.Final
compile
org.springframework
spring-web
5.0.7.RELEASE
compile
org.springframework
spring-webmvc
5.0.7.RELEASE
compile
可以看到,在该 pom 文件中已经定义好了 Web 模块需要的各个组件。之后,引入的 Starter 依赖可以与 SpringBoot 的自动装配特性、外部化配置特性进行无缝衔接,来达到快速开发的目的。
可以看到这些 Starter 的名称都是以
spring-boot-starter
为开头,后面跟着具体的模块名,所有官方的 Starter 遵循相似的命名模式。2.2 starter命名规范 starter的命名规则,命名规则分为两种,一种是官方的命名规则,另一种就是我们自己制作的starter命名规则。
2.2.1 官方命名规则
前缀:
spring-boot-starter-
规则:
spring-boot-starter-模块名
举例:
spring-boot-starter-web、spring-boot-starter-jdbc
2.2.2 自定义命名规则
后缀:
-spring-boot-starter
规则:
模块-spring-boot-starter
举例:
hello-spring-boot-starter
三、实例剖析 比如需要在Spring Boot中使用Rabbit MQ,我们只需要引入依赖以下依赖:
org.springframework.boot
spring-boot-starter-amqp
然后在
application.properties
或application.yml
中添加以下数据库配置信息:spring:
rabbitmq:
addresses: ${RABBIT_ADDRESS:192.168.0.100:5672}
username: ${RABBIT_USERNAME:guest}
password: ${RABBIT_PASSWORD:guest}
然后我们就可以自动注入RabbitTemplate了。
@Service
public class MessageService {
@Autowired
RabbitTemplate rabbitTemplate;
}
这一切的核心,就是自动装配。
3.1 Spring Boot 自动装配原理 Spring Boot依赖
spring-boot-autoconfigure
,这个jar包是帮助spring boot自动装配所有需要的依赖。
org.springframework.boot
spring-boot-autoconfigure
创建一个spring boot工程,系统会自动引入这个jar包,查看jar包下的
META-INF --> spring.factories
文件,会发现默认自动添加了许多模块。比如我们上面讲到的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,\
# 其它的省略
重要的事情说三遍,以下内容为:重点,重点,重点!
Spring Boot在启动时,会解析这些配置文件。我们看到,
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
这个类被加进去了,我们以这个作为列子讲解。展开
spring-boot-autoconfigure
下的org.springframework.boot.autoconfigure --> amqp
,找到RabbitAutoConfiguration
,打开其源代码:@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class }) // 参见下边注释1
@EnableConfigurationProperties(RabbitProperties.class) // 参见下边注释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// 参见下边注释3
public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}}
}
可以看到,在这个类里边,我们@Bean注解了RabbitTemplate和AmqpAdmin让Spring管理这两个实例,这就是我们可以在自己的代码中自动注入这两个类实列的原因。
这里需要解析几个注解:
-
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
这个注解的意思是,如果Spring Boot应用程序中包含RabbitTemplate和Channel这两个类,RabbitAutoConfiguration配置文件才生效,否则不解析配置文件。所以当我们引入spring-boot-starter-amqp依赖的时候,这个配置就生效了,Spring Boot就会帮我们创建RabbitTemplate的实例。 -
@EnableConfigurationProperties(RabbitProperties.class)
这个注解就是注入RabbitProperties实例,这是一个读取配置文件的的配置类,代码如下:
@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";
// 省略其它代码...
这个类就是从我们的yml配置文件中读取Rabbit 连接等配置信息。
-
@ConditionalOnMissingBean
这是放在创建Bean的方法注解,意思是如果Amqpadmin这个类存在,就不创建了。这给用户灵活的选择,可以创建自己的Ampqadmin来管理Rabbit MQ,也可以不创建,使用系统默认的。
四、创建自己的starter 4.1 应用场景 为什么要创建一个spring boot starter项目呢,目的是为了使用方便和代码复用。举个例子,假如你有个监控系统叫monitor-center,很多系统都需要调用这个监控系统获取监控数据。这个监控系统使用OAuth 2.0进行授权的,在使用监控系统前,需要先使用账户密码登录安全中心,获取token,然后使用token访问监控系统。如果token过期,又需要访问安全中心,刷新token。之后是访问监控系统获取监控数据。
上边细节还挺繁琐的,如果每个监控应用都实现一遍,工作任务就太大了,于是我们考虑做一个monitor-boot-starter,包装所有细节。然后提供一个MonitorService给客户使用。
4.2 创建项目 下面开始创建项目:
4.2.1 第一步,新建starter项目
创建一个Spring boot工程,取名monitor-boot-starter,引入如下依赖:
4.0.0 org.springframework.boot
spring-boot-starter-parent
2.3.3.RELEASE
com.erbadagang.demo
monitor-boot-starter
0.0.1-SNAPSHOT
monitor-boot-starter
Demo project for Spring Boot 1.8
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-configuration-processor
org.springframework.boot
spring-boot-maven-plugin
org.springframework.boot
spring-boot-configuration-processor
这里需要注意两个细节:
- 命名规范,Spring官方的starter,都叫spring-boot-starter-<模块名称>,我们自己定义的start,都命名为 <模块名称>-boot-starter。
- 添加了另一个依赖spring-boot-configuration-processor,并且在plugin的configuration中排除它,这在后边会讲到。
添加以下三个类:
package com.erbadagang.demo.monitor.autoconfig;
/**
* 这是自动装配类,初始化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方法...
}
/**
* 这是我们这个应用的核心类,应该在自动装配类中创建。
*/
public class MonitorService {
private MonitorProperties properties;
public MonitorService(MonitorProperties properties) {
this.properties = properties;
}public void subscribe(String url, Consumer 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);
}
}
每个类的功能和作用,都做了注释。
4.2.3 第三步,配置spring.factories
在resources目录下,创建
META-INF
文件夹,然后创建文件spring.factories
,添加以下内容。# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.erbadagang.demo.monitor.autoconfig.MonitorAutoConfiguration
Spring boot starter启动的时候,这个文件的的内容会被合并到spring-boot-autoconfigure的spring.factories中。这就告诉Spring Boot,系统启动时,除了扫描默认的自动装配类,也扫描
com.erbadagang.demo.monitor.autoconfig.MonitorAutoConfiguration
这个类。这个项目已经创建结束了,现在只需要打包安装就可以使用了。使用IDE或者执行以下maven命令:
mvn clean compile install
。五、使用自定义Spring Boot starter 我们来创建一个项目,使用刚才我们自己创建的monitor-boot-starter。
5.1 第一步,创建Spring Boot工程 取名demo,添加以下依赖:
org.springframework.boot
spring-boot-starter
com.erbadagang.demo
monitor-boot-starter
0.0.1-SNAPSHOT
5.2 第二步,在application.yml中,添加以下内容:
demo:
monitor:
server-url: https://www.demo-monitor.com
username: MessiLoveRidingBike
password: 123456
当你在
application.yml
中输入demo
的时候,是不是还带自动提示功能的,这是咋整的?后续分析... ...5.3 第三步,使用MonitorService 在DemoApplication.java类中,注入MonitorService并调用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));
}
}
运行程序,得到输入如下:
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 刚才我们在使用monitor-boot-starter的时候,居然还带自动提示。这是为什么呢,现在来解释一下。
在我们工程monitor-boot-starter的pom.xml中,我们添加了
pring-boot-configuration-processor
这个依赖,这个依赖的作用是,在编译时,为所有的添加了@ConfigurationProperties的类添加元数据,这些元数据是可以被IDE读到的,我们在application.yml中输入demo的时候,就会自动提示demo.monitor.login-url
。4.0.0 org.springframework.boot
spring-boot-starter-parent
2.3.3.RELEASE
com.erbadagang.demo
monitor-boot-starter
0.0.1-SNAPSHOT
monitor-boot-starter
Demo project for Spring Boot 1.8
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-configuration-processor
org.springframework.boot
spring-boot-maven-plugin
org.springframework.boot
spring-boot-configuration-processor
当然根据官方的要求,需要在spring-boot-maven-plugin中排除掉spring-boot-configuration-processor。具体请参见Configuring the Annotation Processor
Configuration Metadata还可以做的更加智能,不但给出智能提示,还把可选项都可以列出来。
七、总结 如果要创建一个spring boot starter,可以参考以下步骤:
- 首先需要引入spring-boot-autoconfigure和spring-boot-configuration-processor依赖,后边这个依赖是用来产生配置文件元数据的。
- 创建AutoConguration类,这里需要理解两个注解,
@ConditionalOnClass(MonitorService.class)和@ConditionalOnMissingBean
,第一个注解是放在自动装配类上的,表示只有当MonitorService类存在的时候才执行自动装配。第二个类是放在创建Bean的方法上,表示只有当前类实例不存在的时候才创建,这给用户很大的灵活性。可以自己创建,也可以使用系统默认的Bean。 - 创建配置文件类,从application.yml中读取配置文件信息。
- 在resources目录下,创建META-INF文件夹,并在该文件夹下创建spring.factories,添加需要自动装配的类。Spring Boot会在启动时,合并spring.factories中的内容。
- 最后就是打包安装,然后使用啦。
@Autowired
自动装配。方案是按照自己想法写的没有遵循Spring Boot Starter规范,但是达到了同样的目的,可以说是轻量级的starter,供大家以后工作中参考。
业务逻辑:parent项目——8.1 parent项目——pay_common
编写邮件发送逻辑,后续各个项目使用时直接POM引入pay_common
依赖,配置yml后通过@Autowired
使用,来避免各个项目都造轮子。
pay_common
编写一个普通的@Service类来实现邮件发送功能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:邮件发送service
* @author: 郭秀志 jbcode@126.com
* @date: 2020年1月18日 下午1:37:09
* @Copyright:
*/
@Service //这里使用了Service注解,没有使用starter的标准用法注解。
@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("开始发送邮件通知,邮件内容为:[{}]", text);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper mMessageHelper = new MimeMessageHelper(message, true, "UTF-8");
// 发件人邮箱
mMessageHelper.setFrom(from);
// 收件人邮箱
mMessageHelper.setTo(toUser);
// 邮件的主题也就是邮件的标题
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("发送邮件通知成功,邮件内容为:[{}]", text);
}}
说明:
没有写AutoConfiguration类,直接写Service了。所以没有下面的AutoConfiguration注解:
@Configuration
@ConditionalOnClass(MailService.class) //正常我们依赖MailService触发自动装配
@EnableConfigurationProperties(MonitorProperties.class) //直接在Service类里面使用@Value注解读取配置文件了。
8.2 配置AutoConfiguration 编写spring.factories (也是Properties)文件。
文章图片
spring.factories 加上
com.erbadagang.pay.common.service.MailService
,来让依赖这个项目的其他Springboot项目自动扫描MailService进行自动装配:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.erbadagang.pay.common.autoconfig.schedlock.ShedlockConfig,\
com.erbadagang.pay.common.service.MailService
8.3 使用方引入依赖 业务项目的pom.xml引入parent项目——
pay_common
的依赖。
com.pay.parent
pay_common
8.4 @Autowired MailService 业务项目使用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邮件发送。
* @author: 郭秀志 jbcode@126.com
* @date: 2020年1月14日 下午12:44:08
* @Copyright:
*/
@RestController
@Slf4j
@RequestMapping(value = "https://www.it610.com/mail")
public class MailController {@Autowired
private MailService mailService;
@RequestMapping(value = "https://www.it610.com/send")
public JsonResult sendMail() throws MessagingException, javax.mail.MessagingException {
mailService.sendMail("邮件发送by springboot ", "邮件正文,通过spring-boot-starter-mail");
log.info("发送邮件完成");
System.out.println();
return JsonResult.of("发送邮件完成", true, "成功调用");
}}
8.5 配置信息 业务项目在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 结论 【自定义Spring|自定义Spring Boot Starter】综上自己“发明”了一个简单的
Spring Boot Starter
,并使用之。推荐阅读
- Activiti(一)SpringBoot2集成Activiti6
- SpringBoot调用公共模块的自定义注解失效的解决
- python自定义封装带颜色的logging模块
- 解决SpringBoot引用别的模块无法注入的问题
- 列出所有自定义的function和view
- 2018-07-09|2018-07-09 Spring 的DBCP,c3p0
- spring|spring boot项目启动websocket
- Spring|Spring Boot 整合 Activiti6.0.0
- Spring集成|Spring集成 Mina
- springboot使用redis缓存