目录
SpringBoot starter机制
SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进 starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并 启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。 SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提 供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着 约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
简而言之,starter就是一个外部的项目,我们需要使用它的时候就可以在当前springboot项目中 引入它。
为什么要自定义starter
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定 的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集 成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter, 复用的时候只需要将其在pom中引用依赖即可,再由SpringBoot为我们完成自动装配,就非常轻松了。
自定义starter的案例
自定义starter的命名规则
SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。
官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供 的starter
自定义starter代码实现
给出一个需求:
同一个ip规定秒数内不能重复点击某一接口。
比如10秒内只能点击一次下单接口,否则返回频繁请求。不处理用户的请求,通过注解的方式实现。
分析:
1.首先我们肯定需要自定义一个注解 让其可以标注在方法上。并且能够自主设置请求间隔时间,
2.然后可以自定义校验规则,不局限于只是根据ip校验,比如可以扩展为按照登陆用户来校验。因为在测试的时候 大家都在同一ip下进行测试,如果只是检验ip就无法测试了。这里利用redis自动过期功能。
3.如何在不改变源码的前提下加上这个限制了,肯定是用aop的前置通知塞。在进入方法之前进行检验。
4.自动注入。通过spring boot源码分析时可以知道springboot会默认去加载resources 包下的META-INF文件夹下的spring.factories文件org.springframework.boot.autoconfigure.EnableAutoConfiguration中的所有类全部加载进ioc(满足条件的)。
(1)自定义starter
首先,先完成自定义starter
(1)新建maven jar工程,工程名为repeated-submit-spring-boot-starter,导入最重要的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
</dependencies>
完整的导入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.5.1</version>
<!-- 排除lettuce包,使用 jedis 代替-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>compile</scope>
</dependency>
</dependencies>
(2)编写自定义注解
/**
* 不能重复提交标签
*
* @author Administrator
* @since: 2021/3/16 14:35
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
/**
* 指定时间内,不能重复提交
*
* @return 超时时间,单位毫秒
*/
long timeout() default 5000;
/**
* key生成方式
*
* @return 结果
*/
Class<? extends KeyGenerator> keyGenerator() default RedisKeyGenerator.class;
}
timeout 自定义间隔时间,keyGenerator自定义校验规则 也就是redis的key的生成规则。
(3)KeyGenerator生成key的接口类
/**
* @author Administrator
* @since: 2021/3/16 15:09
*/
public interface KeyGenerator {
/**
* 生成key
*
* @param request 请求
* @param method 接口方法
* @return 结果
*/
String generate(HttpServletRequest request, Method method);
}
(4)KeyGenerator生成key的接口类的默认实现类RedisKeyGenerator
/**
* @author Administrator
* @since: 2021/3/16 15:23
*/
public class RedisKeyGenerator implements KeyGenerator {
private static final String TEMPLATE = "{}_{}";
@Override
public String generate(HttpServletRequest request, Method method) {
// 当前请求的ip
String ip = ServletUtil.getClientIP(request);
// 请求的类的全路径
String className = method.getDeclaringClass().getName();
// 请求方法的名称
String methodName = method.getName();
String signature = StrUtil.format(TEMPLATE, className, methodName);
int hashCode = Math.abs(signature.hashCode());
return StrUtil.format(TEMPLATE, ip, hashCode);
}
}
(4)RepeatSubmitAspect aop实现类
/**
* @author Administrator
* @since: 2021/3/16 14:40
*/
@Aspect
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class RepeatSubmitAspect {
private final Checker checker;
@Around("@annotation(com.my.starter.resubmit.core.annotation.RepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取request对象
HttpServletRequest request = WebUtils.getRequest();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.debug("==> 判断接口重复提交开始");
Result<Void> checkResult = checker.check(request, method).fallback();
if (!checkResult.isSuccess()) {
return checkResult;
}
log.debug("==> 判断接口重复提交结束");
try {
return joinPoint.proceed();
} catch (Exception e) {
log.error("==> 判断接口重复提交出错:", e);
checker.remove(request, method);
throw e;
}
}
}
直接利用环绕通知去扫描具有自定义注解的方法,当请求来临时就会先进这个前置方法。
(5)Checker校验接口以及其实现类RepeatSubmitChecker里面进频率检验
/**
* @author Administrator
* @since: 2021/3/16 14:55
*/
public interface Checker {
/**
* 检查接口能否提交
*
* @param request 请求
* @param method 接口方法
* @return 结果
*/
Result<Void> check(HttpServletRequest request, Method method);
/**
* 移除限制
*
* @param request 请求
* @param method 接口方法
*/
void remove(HttpServletRequest request, Method method);
}
/**
* @author Administrator
* @since: 2021/3/16 15:08
*/
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class RepeatSubmitChecker implements Checker {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public Result<Void> check(HttpServletRequest request, Method method) {
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
KeyGenerator keyGenerator = ReflectUtil.newInstance(repeatSubmit.keyGenerator());
String key = keyGenerator.generate(request, method);
Object value = redisTemplate.opsForValue().get(key);
if (Objects.nonNull(value)) {
return Result.error("您的操作过频繁,请稍后再试");
}
long timeout = repeatSubmit.timeout();
if (timeout < 0) {
timeout = 5000;
}
redisTemplate.opsForValue().set(key, Math.abs(key.hashCode()), timeout, TimeUnit.MILLISECONDS);
return Result.success();
}
@Override
public void remove(HttpServletRequest request, Method method) {
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
KeyGenerator keyGenerator = ReflectUtil.newInstance(repeatSubmit.keyGenerator());
String key = keyGenerator.generate(request, method);
redisTemplate.delete(key);
}
}
(6)RepeatSubmitConfiguration注入配置类。
/**
* @author Administrator
* @since: 2021/3/16 15:40
*/
@ConditionalOnClass(Jedis.class)
@Configuration
public class RepeatSubmitConfiguration {
@Bean
@ConditionalOnMissingBean(Checker.class)
public Checker checker(RedisTemplate redisTemplate) {
return new RepeatSubmitChecker(redisTemplate);
}
@Bean
public RepeatSubmitAspect repeatSubmitAspect(Checker checker) {
return new RepeatSubmitAspect(checker);
}
}
(7)spring.factories编写 将我们定义的配置类的全路径编写进去,springboot加载的时候就能帮我们加载进ioc中。
### 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.my.starter.resubmit.autoconfigure.RepeatSubmitConfiguration
效果展示
导入我们写的starter 记得配置redis的连接哦
<dependency>
<groupId>com.my.starter.resubmit</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
文章内容输出来源:拉勾教育Java高薪训练营;