AOP切面初探和使用

一、AOP概念以及什么时候会用到

  1. AOP就是java中经常说的切面编程,是一种思想或者说程序设计。
  2. AOP:Aspect-Oriented Programming
  3. AOP是spring或者springboot框架中特有的一种编程思想,有时候我们业务中会有一些开始前的校验或者结束后的其他发送通知记录日志等这样的需求,虽然在自己本身的业务中也可以写,但是,比较啰嗦以及代码杂乱,因为它不是我们的主要业务逻辑。这个时候,就可以用AOP思想来将这些校验或者记录日志等这样的非主要逻辑做成一个切面,哪里想用就直接用这个切面就OK。
  4. 某种程度上,我认为,切面就像一个函数一样,函数是抽象出一段逻辑,而切面其实也是抽象了一段逻辑,但是它比函数从项目整体架构来讲,更加的解耦。
二、AOP在spring/springboot框架中处于的地位
  1. spring或者springboot框架中,最核心的思想有两个,一个是IOC控制反转,一个就是我们AOP切面编程。
  2. IOC:项目中,我们经常是多个对象协作配合实现我们的逻辑,IOC思想是将对象的创建、管理、销毁等交给spring容器来操作,而不是对象自己内部去创建或者管理这些对象,相当于找了一个中介或者代理来管理对象。-----字面意思就是将对象控制管理的责任反转给spring容器或者代理了。【对象解耦】
  3. AOP:将非主要非功能性的代码抽象成一个切面,从架构上进行代码解耦、复用。【代码解耦】
  4. 开发中,我们主要创建和操作的是不同的对象,对象多了就尝试将对象解耦,IOC派上用场,接着,我们在具体的书写业务逻辑的时候,非功能性或者非主要逻辑越来越多的时候,尝试将这块代码抽象,可以抽象成函数或者aop切面,根据我们自己的业务进行考虑,防止主要业务逻辑中太多if else判断。
三、AOP核心知识点
备注:思考实现一个切面都需要哪些步骤? (1)肯定需要写具体的切面业务逻辑(对应aspect) (2)那写好逻辑后什么时候执行?是主要逻辑前还是主要逻辑后?(对应advice,执行时机) (3)需要定义一个在哪里使用切面,比如某个insert方法要使用切面,校验insert之前的一些null或者空字符串(对应join point) (4)不是所有方法都用到这个切面,怎么控制?(通过point cut来控制和匹配)

  • 连接点
    join point,程序的一个执行点,类似类的一个方法,方法里边一个代码块
  • 切入点
    Point cut,捕获连接点的代码结构,定一个代码逻辑用来捕获某个连接点的实现逻辑
  • 切面
    aspect,具体被执行的代码逻辑,就是我们想要的那些校验、记录日志、发送短信通知等这些逻辑实现代码
  • 通知
    advice,定义在连接点什么时机来执行aspect,包含before(主要逻辑之前执行)、after(主要逻辑之后执行)、around(前、后都执行)
四、简单实现一个AOP(据说有四种常见的实现方式,这里只看springboot注解方式)
假如想要提交一个请假申请,在申请单有性别、请假时间等,创建申请单之前校验提交的参数(sex、startDate、endDate)不能为空,将这个校验逻辑使用切面来实现。
  1. 自定义一个注解ApplyAnnotation
    import java.lang.annotation.*; @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApplyAnnotation { }

  1. 创建aspect类
    package portal.aop; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Objects; /** * 申请单插入前校验的切面 * */ @Slf4j @Aspect @Component public class ApplyAspect {/** * 校验:对sex、startDate、endEnd参数进行校验 * @param joinPoint * @throws RRException */ @Before("@annotation(ApplyAnnotation)") public void before(JoinPoint joinPoint)throws RRException { if(joinPoint.getArgs()!=null && joinPoint.getArgs().length>0) { //获取参数使用joinPoint.getArgs String json = JSONObject.toJSONString(joinPoint.getArgs()[0]); // 将参数转换成一个entity对象 UpdateApplyForm form = objectMapper.convertValue(joinPoint.getArgs()[0], UpdateApplyForm.class); if (Objects.isNull(form.getSex())) { throw new Exception("参数错误,请检查参数sex是否传递"); }if (Objects.isNull(form.getStartDate())) { throw new Exception("参数错误,请检查参数startDate是否传递"); }if (Objects.isNull(form.getEndDate())) { throw new Exception("参数错误,请检查参数endDate是否传递"); }} }@After("@annotation(ApplyViewAnnotation)") public void after(JoinPoint joinPoint) { // 可以在主要逻辑之后执行一些 记录日志或者发送短信通知等逻辑 }}

  1. 【AOP切面初探和使用】在想要使用的地方添加注解
    /** * 在controller的createApply方法使用注解 * 创建一条请假申请,加上ApplyAnnotation注解后,会在执行createApply方法之前去调用切面的before方法进行校验 * @param form * @return * @throws Exception */ @PostMapping("/create-apply") @ApplyAnnotation public R createApply(@RequestBody UpdateApplyForm form) throws Exception { // 主要业务逻辑 portalLogic.createApply(form); return R.ok(); }

体验完上述的实例之后,会发现,我们的主要逻辑portalLogic.createApply(form)没有混入一些奇奇怪怪的if else进行校验,而是直接用一个注解就将这些校验逻辑实现了,从代码层次看,让代码更加的简洁,从架构上看,将校验逻辑和主要逻辑进行解耦。
五、AOP的实现原理
  1. 我们的AOP到底是怎么实现的?怎么让我们的主要逻辑代码执行前或者后 去执行了切面逻辑呢?
  2. 实际上AOP充分利用了代理来将切面的逻辑 “织入” 了我们自己的主要业务代码中,表面看不到,是springboot框架通过代理来实现的。(实际上代理分为静态代理和动态代理)
  3. 代理,我理解的就是一个中介,将想要做的事情交给一个中介/代理来干,所有的操作都让这个中介干和实现。
  4. AOP实现用到的代理有两种方式,jdk动态代理和cglib代理,需要提前理解这两种代理模式和异同点。
  5. 详细的可以查看:aop核心源码原理详解

    推荐阅读