关于Starter
Spring Boot秉承“约定大于配置”的开发方式,使得我们基于Spring Boot开发项目的效率变得十分高。相信使用过Spring Boot的小伙伴都会发现,当我们要用到某个Spring提供的组件时,只需要在
pom.xml
文件中添加该组件的starter依赖就能集成到项目中。例如,在
pom.xml
文件中添加spring-boot-starter-web
依赖,就能让项目整合Spring MVC的功能。并且在最简使用下几乎不需要进行任何的配置,而以往想要集成Spring MVC,不仅要添加一堆类似于spring-web
、spring-webmvc
等相关依赖包,以及完成许多繁杂的配置才能够实现集成。这是因为starter里已经帮我们整合了各种依赖包,避免了依赖包缺失或依赖包之间出现版本冲突等问题。以及完成了许多基础配置和自动装配,让我们可以在最简使用下,跳过绝大部分的配置,从而达到开箱即用的效果。这也是Spring Boot实现“约定大于配置”的核心之一。
动手开发一个Starter
通过以上的描述,我们可以简单地将starter看作是对一个组件功能粒度较大的模块化封装,包括了所需依赖包的整合及基础配置和自动装配等。
除了Spring官方提供的starter外,我们自己也可以根据业务开发一个starter。例如,当项目积累到一定程度时,我们可以将一些通用功能下沉为一个starter。而开发一个starter也很简单,只需要以下步骤:
- 新建一个Maven项目,在pom.xml文件中定义好所需依赖;
- 新建配置类,写好配置项和默认值,使用
@ConfigurationProperties
指明配置项前缀; - 新建自动装配类,使用
@Configuration
和@Bean
来进行自动装配; - 新建
spring.factories
文件,用于指定自动装配类的路径; - 将starter安装到maven仓库,让其他项目能够引用;
接下来,以封装一个用于操作redis的starter为例,一步步展示这些步骤的具体实现过程。首先是第一步,新建一个maven项目,完整的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.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.axin</groupId>
<artifactId>spring-boot-redission-lock-starter</artifactId>
<version>1.0.0</version>
<name>spring-boot-redission-lock-starter</name>
<description>Spring boot redission lock starter</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-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.26.0</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-32</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>3.26.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
第二步,新建切面类RedissonDistributedLockAspectConfiguration
,配置切面;
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class RedissonDistributedLockAspectConfiguration implements ApplicationContextAware {
private final Logger logger = LoggerFactory.getLogger(RedissonDistributedLockAspectConfiguration.class);
@Autowired
private RedissonClient redissonClient;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Pointcut("@annotation(com.axin.redission.lock.LockAction)")
private void lockPoint() {
}
@Around("lockPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
LockAction lockAction = method.getAnnotation(LockAction.class);
String key = lockAction.value();
Object[] args = pjp.getArgs();
key = parse(key, method, args);
RLock lock = getLock(key, lockAction);
if (!lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.unit())) {
logger.debug("get lock failed [{}]", key);
return null;
}
//得到锁,执行方法,释放锁
logger.debug("get lock success [{}]", key);
try {
return pjp.proceed();
} catch (Exception e) {
logger.error("execute locked method occured an exception", e);
} finally {
lock.unlock();
logger.debug("release lock [{}]", key);
}
return null;
}
/**
* @param key 表达式
* @param method 方法
* @param args 方法参数
* @return
* @description 解析spring EL表达式
* @author handsometong
* @date 2022年11月9日 上午10:41:01
* @version 1.0.0
*/
private String parse(String key, Method method, Object[] args) {
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
return applicationContext.getEnvironment().getProperty("spring.application.name").toUpperCase() + parser.parseExpression(key).getValue(context, String.class);
}
private RLock getLock(String key, LockAction lockAction) {
switch (lockAction.lockType()) {
case REENTRANT_LOCK:
return redissonClient.getLock(key);
case FAIR_LOCK:
return redissonClient.getFairLock(key);
case READ_LOCK:
return redissonClient.getReadWriteLock(key).readLock();
case WRITE_LOCK:
return redissonClient.getReadWriteLock(key).writeLock();
default:
throw new RuntimeException("do not support lock type:" + lockAction.lockType().name());
}
}
}
第三步,在项目的resources
目录下新建一个META-INF
目录,并在该目录下新建spring.factories
文件。如下图所示:
在spring.factories
文件里指定自动装配类的路径:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.axin.redission.RedissonDistributedLockAspectConfiguration
若需要指定多个自动装配类的路径,则使用逗号分隔。如下示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.axin.redission.autoconfigure.DemoConfiguration,\
com.axin.redission.autoconfigure.RedissonDistributedLockAspectConfiguration
最后install这个maven项目,命令如下:
mvn clean install
如果使用的开发工具是IDEA的话就比较简单,只需要双击一下install即可:
使用Starter
在任意一个Spring Boot项目的pom.xml
文件中添加如下依赖:
<dependency>
<groupId>com.axin</groupId>
<artifactId>spring-boot-redission-lock-starter</artifactId>
<version>1.0.0</version>
</dependency>
在项目的application.yml
中添加如下配置项来覆盖默认配置,若默认配置已符合需求则可以省略这一步:
spring:
redis:
redisson:
singleServerConfig:
# 空闲链接超时
idleConnectionTimeout: 10000
# 链接超时
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: null
subscriptionsPerConnection: 5
clientName: null
address: "redis://192.168.0.10:6379"
# 最小空闲订阅连接数
subscriptionConnectionMinimumIdleSize: 1
# 订阅连接池大小
subscriptionConnectionPoolSize: 50
# 最小空闲连接数
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: "!<org.redisson.codec.Kryo5Codec> {}"
transportMode: "NIO"
lockWatchdogTimeout: 1000
发布到公共仓库
- 首先把对应maven项目上传到gitee(或者GitHub)
- 打开
https://www.jitpack.io/
网站,输入对应项目的git地址
- 根据
jitpack
介绍,在项目中配置
<!-- 配置仓库 -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://www.jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.gitee.sunsjgitee</groupId>
<artifactId>spring-boot-redission-lock-starter</artifactId>
<version>Tag</version>
</dependency>
<dependencies>
- 然后reload项目就可以了