大家好,我是工藤学编程 🦉一个正在努力学习的小博主,期待你的关注作业侠系列最新文章😉Java实现聊天程序SpringBoot实战系列🐷【【SpringBoot实战系列】AOP+自定义注解-接口防重提交多场景设计实战环境搭建大集合环境搭建大集合(持续更新)
在本栏中,我们之前已经完成了:
【SpringBoot实战系列】之发送短信验证码
【SpringBoot实战系列】之从Async组件应用实战到ThreadPoolTaskExecutor⾃定义线程池
【SpringBoot实战系列】之图形验证码开发并池化Redis6存储
【SpringBoot实战系列】阿里云OSS接入上传图片实战
【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战
【SpringBoot实战系列】RabbitMQ实现消息发送并实现邮箱发送异常监控报警实战
AOP简介及好处
Aspect Oriented Program ⾯向切⾯编程, 在不改变原有逻辑上增加额外的功能AOP思想把功能分两个部分,分离系统中的各种关注点
好处
- 减少代码侵⼊,解耦
- 可以统⼀处理横切逻辑
- ⽅便添加和删除横切逻辑
Spring⾥⾯的AOP常⻅概念
- 横切关注点 对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关注点 ⽐如 权限认证、⽇志、事物
- 通知 Advice 在特定的切⼊点上执⾏的增强处理 做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤的模块,需要的地⽅直接调⽤ ⽐如重复提交判断逻辑 类型
- @Before前置通知 在执⾏⽬标⽅法之前运⾏
- @After后置通知 在⽬标⽅法运⾏结束之后
- @AfterReturning返回通知 在⽬标⽅法正常返回值后运⾏
- @AfterThrowing异常通知 在⽬标⽅法出现异常后运⾏
- @Around环绕通知 在⽬标⽅法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,⽇志等都是环绕通知,注意编程中核⼼是⼀个ProceedingJoinPoint,需要⼿动执⾏ joinPoint.procced()
- 连接点 JointPoint 要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的 具体位置,⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点只是概念,没啥特殊
- 切⼊点 Pointcut 不能全部⽅法都是连接点,通过特定的规则来筛选连接点,就是Pointcut,选中那⼏个你想要的⽅法在程序中主要体现为书写切⼊点表达式(通过通配、正则 表达式)过滤出特定的⼀组 JointPoint连接点过滤出相应的 Advice 将要发⽣的joinpoint地⽅
- 切⾯ Aspect 通常是⼀个类,⾥⾯定义 切⼊点+通知 , 定义在什么地⽅; 什么时间点、做什么事情 通知 advice指明了时间和做的事情(前置、后置等)切⼊点 pointcut 指定在什么地⽅⼲这个事情web接⼝设计中,web层->⽹关层->服务层->数据层,每⼀层之间也是⼀个切⾯,对象和对象,⽅法和⽅法之间都是⼀个个切⾯
- ⽬标 target ⽬标类,真正的业务逻辑,可以在⽬标类不知情的条件下,增加新的功能到⽬标类的链路上
- 织⼊ Weaving 把切⾯(某个类)应⽤到⽬标函数的过程称为织⼊
java核心知识-⾃定义注解
- Annotation(注解) 从JDK 1.5开始, Java增加了对元数据(MetaData)的⽀持,也就是 Annotation(注解)。 注解其实就是代码⾥的特殊标记,它⽤于替代配置⽂件常⻅的很多 @Override、@Deprecated等
- 什么是元注解 注解的注解,⽐如当我们需要⾃定义注解时会需要⼀些元注解(meta-annotation),如@Target和@Retention
- java内置4种元注解 @Target 表示该注解⽤于什么地⽅
- ElementType.CONSTRUCTOR ⽤在构造器
- ElementType.FIELD ⽤于描述域-属性上
- ElementType.METHOD ⽤在⽅法上
- ElementType.TYPE ⽤在类或接⼝上
- ElementType.PACKAGE ⽤于描述包
- @Retention 表示在什么级别保存该注解信息
- RetentionPolicy.SOURCE 保留到源码上
- RetentionPolicy.CLASS 保留到字节码上
- RetentionPolicy.RUNTIME 保留到虚拟机运⾏时(最多,可通过反射获取)
- @Documented 将此注解包含在 javadoc 中
- @Inherited 是否允许⼦类继承⽗类中的注解
- @interface ⽤来声明⼀个注解,可以通过default来声明参数的默认值⾃定义注解时,⾃动继承了java.lang.annotation.Annotation接⼝通过反射可以获取⾃定义注解
防重提交自定义注解实战
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceRepeatSubmit{enumType{PARAM,TOKEN}TypelimitType()defaultType.PARAM;longlockTime()default5;}
分布式锁
redission依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.10.1</version></dependency>
配置类
@ConfigurationpublicclassRedissionConfiguration{@Value("${spring.redis.host}")privateString redisHost;@Value("${spring.redis.port}")privateString redisPort;@Value("${spring.redis.password}")privateString redisPwd;/**
* 配置分布式锁的redisson
*
* @return
*/@BeanpublicRedissonClientredissonClient(){Config config =newConfig();//单机⽅式
config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+ redisHost +":"+ redisPort);//集群RedissonClient redissonClient =Redisson.create(config);return redissonClient;}/**
* 集群模式
* 备注:可以⽤"rediss://"来启⽤SSL连接
*//*@Bean
public RedissonClient redissonClusterClient() {
Config config = new Config();
config.useClusterServers().setScanInterval(2000) //
集群状态扫描间隔时间,单位是毫秒
.addNodeAddress("redis://127.0.0.1:7000")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson =
Redisson.create(config);
return redisson;
}*/}
切面开发:
@Aspect@Component@Slf4jpublicclassRepeatSubmitAspect{@AutowiredprivateStringRedisTemplate redisTemplate;@AutowiredprivateRedissonClient redissonClient;/**
* 定义 @Pointcut注解表达式,
* ⽅式⼀:@annotation:当执⾏的⽅法上拥有指定的注解时
⽣效(我们采⽤这)
* ⽅式⼆:execution:⼀般⽤于指定⽅法的执⾏
*
* @param repeatSubmit
*/@Pointcut("@annotation(repeatSubmit)")publicvoidpointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){}/**
* 环绕通知, 围绕着⽅法执⾏
* @Around 可以⽤来在调⽤⼀个具体⽅法前和调⽤后来完成⼀些具体的任务。
*
* ⽅式⼀:单⽤ @Around("execution(*net.xdclass.controller.*.*(..))")可以
* ⽅式⼆:⽤@Pointcut和@Around联合注解也可以(我们采⽤这个)
*
*
* 两种⽅式
* ⽅式⼀:加锁 固定时间内不能᯿复提交
* <p>
* ⽅式⼆:先请求获取token,这边再删除token,删除成功则是第⼀次提交
*
* @param joinPoint
* @param noRepeatSubmit
* @return
* @throws Throwable
*/@Around("pointCutNoRepeatSubmit(repeatSubmit)")publicObjectaround(ProceedingJoinPoint joinPoint,RepeatSubmit repeatSubmit)throwsThrowable{HttpServletRequest request =((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();Long accountNo =LoginInterceptor.threadLocal.get().getAccountNo();boolean res =false;String type = repeatSubmit.limitType().name();if(type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())){long lockTime = repeatSubmit.lockTime();String ippAddr =CommonUtil.getIpAddr(request);MethodSignature methodSignature =(MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();String className = method.getDeclaringClass().getName();String key ="order-server-repeat-submit:"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ippAddr, className, method, accountNo));//res=redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);RLock lock = redissonClient.getLock(key);
res = lock.tryLock(0, lockTime,TimeUnit.SECONDS);}else{String requestToken = request.getHeader("request-token");if(StringUtils.isBlank(requestToken)){thrownewBizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);}String key =String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
res = redisTemplate.delete(key);}if(!res){
log.error("订单重复提交");returnnull;}
log.info("环绕通知前:{}",CommonUtil.getCurrentTimestamp());Object obj = joinPoint.proceed();
log.info("环绕通知后:{}",CommonUtil.getCurrentTimestamp());return obj;}}
将自定义的注解加在对应想要防重提交的方法上即可
@PostMapping("page")@RepeatSubmitpublicJsonDatapage(@RequestBodyOrderPageRequest orderPageRequest){Map<String,Object>pageResult = productOrderService.page(orderPageRequest);returnJsonData.buildSuccess(pageResult);}
访问对应接口
本篇完!
版权归原作者 工藤学编程 所有, 如有侵权,请联系我们删除。