赞
踩
目录
SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进 starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并 启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。 SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提 供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着 约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
简而言之,starter就是一个外部的项目,我们需要使用它的时候就可以在当前springboot项目中 引入它。
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定 的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集 成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter, 复用的时候只需要将其在pom中引用依赖即可,再由SpringBoot为我们完成自动装配,就非常轻松了。
SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。
官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供 的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高薪训练营;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。