AOP的介绍和使用
切面作用
利用AOP(面向切面编程),我们可以在不改变原有逻辑的情况下,增加额外的功能。AOP思想将系统的功能分为两个部分,从而分离各种关注点,降低了代码的耦合性,减少了代码侵入性。通过AOP,我们能够统一处理横切逻辑,这使得添加和删除横切逻辑变得更加方便。
AOP里面常见的概念
横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点,比如 权限认证、日志、事物。
通知 Advice
在特定的切入点上执行的增强处理做什么? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用,比如重复提交判断逻辑
@Before前置通知,在执行目标方法之前运行
@After后置通知,在目标方法运行结束之后
@AfterReturning返回通知,在目标方法正常返回值后运行
@AfterThrowing异常通知,在目标方法出现异常后运行
@Around环绕通知,在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执joinPoint.procced()
连接点 JointPoint
要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,一般是方法的调用前后,全部方法都可以是连接点。只是概念,没啥特殊
切入点 Pointcut
不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法,在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点,过滤出相应的 Advice 将要发生的joinpoint地方
切面 Aspect
通常是一个类,里面定义切入点+通知, 定义在什么地方; 什么时间点、做什么事情,通知 advice指明了时间和做的事情(前置、后置等),切入点 pointcut 指定在什么地方干这个事情,web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
目标 target
目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
织入 Weaving
把切面(某个类)应用到目标函数的过程称为织入
// 目标类
BookOrderService{
//新增订单;
addOrder(){};
//查询订单;
findOrderById();
//删除订单;
deleteOrderById();
//更新订单
updateOrder(){};
}
JoinPoint连接点:addOrder、findOrderById、deleteOrderById、updateOrder;
PointCut切入点:过滤出哪些JoinPoint连接点中哪些函数进行切入;
Advice通知:在切入点的函数上执行的动作,如权限校验,日志记录等等;
Aspect切面:由PointCut切入点和Advice通知组合而成,定义通知应用到哪些切入点;
Weaving织入:把切面的代码,应用到目标函数的过程;
具体代码
/**
* 定义一个切面类
*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 要在哪里执行该方法
* 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
* 方式二:execution:一般用于指定方法的执行
*/
@Pointcut("@annotation(repeatSumbit)")
public void pointCutNoRepeatSubmit(RepeatSumbit repeatSumbit) {
}
/**
* 环绕通知, 围绕着方法执行
*
* @param joinPoint
* @param noRepeatSubmit
* @return
* @throws Throwable
* @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
* <p>
* 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
* 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个)
* <p>
* <p>
* 两种方式
* 方式一:加锁 固定时间内不能重复提交
* <p>
* 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交
*/
@Around("pointCutNoRepeatSubmit(noRepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, RepeatSumbit noRepeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
//用于记录成功或者失败
boolean res = false;
/**
* 防重提交类型
*/
String type = noRepeatSubmit.limitType().name();
if (type.equalsIgnoreCase(RepeatSumbit.Type.PARAM.name())) {
//方式1,参数形式防重提交 TODO
long lockTime = noRepeatSubmit.lockTime();
String ipAddr = 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", ipAddr, className, method, accountNo));
//加锁
// res = redisTemplate.opsForValue().setIfAbsent(key, "1", lockTime, TimeUnit.SECONDS);
RLock lock = redissonClient.getLock(key);
// 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义]
res = lock.tryLock(0, lockTime, TimeUnit.SECONDS);
} else {
//方式2,令牌形式防重提交 TODO
String requestToken = request.getHeader("request-token");
if (StringUtils.isBlank(requestToken)) {
throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
}
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
/**
* 提交表单的token key,根据删除知道它是成功还是失败
* 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断
* 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
*/
res = redisTemplate.delete(key);
}
if (!res) {
// throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
log.error("请求重复提交");
return null;
}
log.info("环绕通知执行前");
Object obj = joinPoint.proceed();
log.info("环绕通知执行后");
return obj;
}
}
防重提交业务流程
Token令牌校验
下单前获取一个token,使用一次后失效,不可重复使用,对业务有一定侵入性,需在下单业务获取token,并将token存储到页面中,提交订单时,连同token一并提交;
/**
* 下单前获取令牌用于防重提交
* @return
*/
@GetMapping("token")
public JsonData getOrderToken() {
long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
String token = CommonUtil.getStringNumRandom(32);
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, token);
//令牌有效时间是30分钟
redisTemplate.opsForValue().set(key, String.valueOf(Thread.currentThread().getId()), 30, TimeUnit.MINUTES);
return JsonData.buildSuccess(token);
}
@Data
public class ConfirmOrderRequest {
/**
* 订单类型
*/
private Long productId;
/**
* 购买数量
*/
private Integer buyNum;
/**
* 终端类型
*/
private String clientType;
/**
* 支付类型,微信-银行-支付宝
*/
private String payType;
/**
* 订单总金额
*/
private BigDecimal totalAmount;
/**
* 订单实际支付价格
*/
private BigDecimal payAmount;
/**
* 防重令牌
*/
private String token;
/**
* 发票类型:0->不开发票;1->电子发票;2->纸质发票
*/
private String billType;
/**
* 发票抬头
*/
private String billHeader;
/**
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/f9f51b6830cd426c83b6b91007ffe2e6.png)
![img](https://img-blog.csdnimg.cn/img_convert/2d4ae5081229b5ad8920ddd12c23e963.png)
![img](https://img-blog.csdnimg.cn/img_convert/6358ef9a46d283365a215bf2106f435e.png)
![img](https://img-blog.csdnimg.cn/img_convert/a552be9a40d7afad344b8e971490b6ec.png)
![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)
![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**
62fe4e9.png)
![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**
<img src="https://img-community.csdnimg.cn/images/fd6ebf0d450a4dbea7428752dc7ffd34.jpg" alt="img" style="zoom:50%;" />