当前位置:   article > 正文

springboot源码剖析-自定义Start实现接口频率控制@RepeatSubmit_repeat-submit-spring-boot-starter

repeat-submit-spring-boot-starter

目录

SpringBoot starter机制

为什么要自定义starter

自定义starter的案例

自定义starter的命名规则

自定义starter代码实现

效果展示


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,导入最重要的依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-autoconfigure</artifactId>
  5. <version>2.2.9.RELEASE</version>
  6. </dependency>
  7. </dependencies>

完整的导入

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. <version>2.5.1</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>cn.hutool</groupId>
  9. <artifactId>hutool-all</artifactId>
  10. <version>5.4.0</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-data-redis</artifactId>
  15. <version>2.5.1</version>
  16. <!-- 排除lettuce包,使用 jedis 代替-->
  17. <exclusions>
  18. <exclusion>
  19. <groupId>io.lettuce</groupId>
  20. <artifactId>lettuce-core</artifactId>
  21. </exclusion>
  22. </exclusions>
  23. </dependency>
  24. <dependency>
  25. <groupId>redis.clients</groupId>
  26. <artifactId>jedis</artifactId>
  27. <version>2.9.0</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-autoconfigure</artifactId>
  32. <version>2.2.9.RELEASE</version>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-aop</artifactId>
  37. <version>2.5.1</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <optional>true</optional>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.projectlombok</groupId>
  46. <artifactId>lombok</artifactId>
  47. <version>1.18.20</version>
  48. <scope>compile</scope>
  49. </dependency>
  50. </dependencies>

(2)编写自定义注解

  1. /**
  2. * 不能重复提交标签
  3. *
  4. * @author Administrator
  5. * @since: 2021/3/16 14:35
  6. */
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface RepeatSubmit {
  10. /**
  11. * 指定时间内,不能重复提交
  12. *
  13. * @return 超时时间,单位毫秒
  14. */
  15. long timeout() default 5000;
  16. /**
  17. * key生成方式
  18. *
  19. * @return 结果
  20. */
  21. Class<? extends KeyGenerator> keyGenerator() default RedisKeyGenerator.class;
  22. }

timeout 自定义间隔时间,keyGenerator自定义校验规则 也就是redis的key的生成规则。

​​​​​(3)KeyGenerator生成key的接口类

  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 15:09
  4. */
  5. public interface KeyGenerator {
  6. /**
  7. * 生成key
  8. *
  9. * @param request 请求
  10. * @param method 接口方法
  11. * @return 结果
  12. */
  13. String generate(HttpServletRequest request, Method method);
  14. }

(4)KeyGenerator生成key的接口类的默认实现类RedisKeyGenerator

  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 15:23
  4. */
  5. public class RedisKeyGenerator implements KeyGenerator {
  6. private static final String TEMPLATE = "{}_{}";
  7. @Override
  8. public String generate(HttpServletRequest request, Method method) {
  9. // 当前请求的ip
  10. String ip = ServletUtil.getClientIP(request);
  11. // 请求的类的全路径
  12. String className = method.getDeclaringClass().getName();
  13. // 请求方法的名称
  14. String methodName = method.getName();
  15. String signature = StrUtil.format(TEMPLATE, className, methodName);
  16. int hashCode = Math.abs(signature.hashCode());
  17. return StrUtil.format(TEMPLATE, ip, hashCode);
  18. }
  19. }
(4)RepeatSubmitAspect aop实现类
  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 14:40
  4. */
  5. @Aspect
  6. @Slf4j
  7. @RequiredArgsConstructor(onConstructor_ = @Autowired)
  8. public class RepeatSubmitAspect {
  9. private final Checker checker;
  10. @Around("@annotation(com.my.starter.resubmit.core.annotation.RepeatSubmit)")
  11. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  12. // 获取request对象
  13. HttpServletRequest request = WebUtils.getRequest();
  14. // 获取方法签名
  15. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  16. Method method = signature.getMethod();
  17. log.debug("==> 判断接口重复提交开始");
  18. Result<Void> checkResult = checker.check(request, method).fallback();
  19. if (!checkResult.isSuccess()) {
  20. return checkResult;
  21. }
  22. log.debug("==> 判断接口重复提交结束");
  23. try {
  24. return joinPoint.proceed();
  25. } catch (Exception e) {
  26. log.error("==> 判断接口重复提交出错:", e);
  27. checker.remove(request, method);
  28. throw e;
  29. }
  30. }
  31. }

直接利用环绕通知去扫描具有自定义注解的方法,当请求来临时就会先进这个前置方法。

(5)Checker校验接口以及其实现类RepeatSubmitChecker里面进频率检验

  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 14:55
  4. */
  5. public interface Checker {
  6. /**
  7. * 检查接口能否提交
  8. *
  9. * @param request 请求
  10. * @param method 接口方法
  11. * @return 结果
  12. */
  13. Result<Void> check(HttpServletRequest request, Method method);
  14. /**
  15. * 移除限制
  16. *
  17. * @param request 请求
  18. * @param method 接口方法
  19. */
  20. void remove(HttpServletRequest request, Method method);
  21. }
  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 15:08
  4. */
  5. @RequiredArgsConstructor(onConstructor_ = @Autowired)
  6. public class RepeatSubmitChecker implements Checker {
  7. private final RedisTemplate<String, Object> redisTemplate;
  8. @Override
  9. public Result<Void> check(HttpServletRequest request, Method method) {
  10. RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
  11. KeyGenerator keyGenerator = ReflectUtil.newInstance(repeatSubmit.keyGenerator());
  12. String key = keyGenerator.generate(request, method);
  13. Object value = redisTemplate.opsForValue().get(key);
  14. if (Objects.nonNull(value)) {
  15. return Result.error("您的操作过频繁,请稍后再试");
  16. }
  17. long timeout = repeatSubmit.timeout();
  18. if (timeout < 0) {
  19. timeout = 5000;
  20. }
  21. redisTemplate.opsForValue().set(key, Math.abs(key.hashCode()), timeout, TimeUnit.MILLISECONDS);
  22. return Result.success();
  23. }
  24. @Override
  25. public void remove(HttpServletRequest request, Method method) {
  26. RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
  27. KeyGenerator keyGenerator = ReflectUtil.newInstance(repeatSubmit.keyGenerator());
  28. String key = keyGenerator.generate(request, method);
  29. redisTemplate.delete(key);
  30. }
  31. }

(6)RepeatSubmitConfiguration注入配置类。

  1. /**
  2. * @author Administrator
  3. * @since: 2021/3/16 15:40
  4. */
  5. @ConditionalOnClass(Jedis.class)
  6. @Configuration
  7. public class RepeatSubmitConfiguration {
  8. @Bean
  9. @ConditionalOnMissingBean(Checker.class)
  10. public Checker checker(RedisTemplate redisTemplate) {
  11. return new RepeatSubmitChecker(redisTemplate);
  12. }
  13. @Bean
  14. public RepeatSubmitAspect repeatSubmitAspect(Checker checker) {
  15. return new RepeatSubmitAspect(checker);
  16. }
  17. }

(7)spring.factories编写 将我们定义的配置类的全路径编写进去,springboot加载的时候就能帮我们加载进ioc中。

  1. ### 自动配置
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. com.my.starter.resubmit.autoconfigure.RepeatSubmitConfiguration

效果展示

导入我们写的starter  记得配置redis的连接哦

  1. <dependency>
  2. <groupId>com.my.starter.resubmit</groupId>
  3. <artifactId>zdy-spring-boot-starter</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. </dependency>

源码地址

文章内容输出来源:拉勾教育Java高薪训练营;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/234405?site
推荐阅读
相关标签
  

闽ICP备14008679号