31.从零开始学springboot-再谈切面“AOP”

前言

说起Java,就不得不提Spring,提到Spring,就不得不提IOC(控制反转)和AOP(切面), 本章就详细介绍一下AOP(切面)思想以及它在Spring中的应用.


31.从零开始学springboot-再谈切面“AOP”_第1张图片
WechatIMG2.jpeg

概念

我们先看看百度此条对AOP的解释

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。

这样的描述还是过于空泛,我们简单的举个例子,帮助大家理解.

案例讲解

假设我们需要实现一个日志记录的功能.大家会怎么实现呢?
在AOP之前无非是两种:
方案一:
在每个业务的节点加入日志记录的代码.

//实例
业务一
public String ServiceA(){
    //记录日志的实现1
}

业务二
public String ServiceB(){
    //记录日志的实现2
}
……

方案二:
写一个通用的日志模块,在每个需要用到的业务节点调用该日志功能.

//实例

//日志模块
public class LogUtils {
    public void log(String data){
        //记录日志
    }
}

//业务一
public String ServiceA(){
    LogUtils.log();
}

//业务二
public String ServiceB(){
    LogUtils.log();
}

我们仔细看方案一和二,不难看出,方案一就是面向过程的一种编程思想,而方案二就是OOP(面向对象)的一种编程思想.
方案一的做法会使得代码非常冗余,而且后续日志调整,每处都要调整,十分不好维护.
方法二比方法一好了一点,至少调整日志时只需调整一处,但是还是避免不了代码过于分散的问题,比如,需要在新的业务节点加上日志,就不得不添加新的调用代码.不过,方案二的这种把“公用”方法提取出来作为一个单独模块的思想,已经有了点AOP的思想了.

那么,有没有更好的做法呢 ?

有没有一种可以不增加新的“调用代码”(无需修改代码),就能在新的业务节点上输出日志的方法呢?

下面,我们来仔细聊聊AOP究竟是怎么一回事.

几个概念

为了更好的理解AOP,我们先抛出几个概念.

  • Advice(增强)
    Advice定义了在PointCut里面定义的程序点具体要做的操作,它通过Before、After和Around来区别是在每个JoinPoint之前、之后还是代替执行的代码。
    通知定义了切面是什么以及何时调用,何时调用包含以下几种类型

    • Before 在方法被调用之前调用通知
    • After 在方法完成之后调用通知,无论方法执行是否成功
    • After-returning 在方法成功执行之后调用通知
    • After-throwing 在方法抛出异常后调用通知
    • Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

总结: Advice就是你想要的功能,可以是日志/验证等等,同时Advice可以定义在PointCut的什么位置触发.

  • JoinPoint(连接点)
    JoinPoint就是程序执行的某个特定的位置,如:类开始初始化前、类初始化后、类的某个方法调用前、类的某个方法调用后、方法抛出异常后等.Spring只支持类的方法前、后、抛出异常后的连接点.

总结: JoinPoint就是允许你使用Advice的地方.每个类中的每个方法都是一个JoinPoint,假设一个类有3个方法,那它就有3个JoinPoint.

  • PointCut(切点)
    表示一组JoinPoint,这些JoinPoint或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice将要发生的地方。

总结: PointCut是用来筛选JoinPoint的(你可以假设成mysql的查询条件). 假设你的一个类里,有增删改查4个方法,那就有4个连接点,但是你并不想在所有方法都使用Advice,你只是想让其中几个,比如只想在insert方法执行的前后进行日志记录等Advice操作,那么就用PointCut来定义这几个方法,让PointCut来筛选JoinPoint,选中那几个你想要的方法。

  • Aspect(切面)
    Aspect声明类似于Java中的类声明,在Aspect中会包含着一些PointCut以及相应的Advice。
  • Target(目标对象)
    织入Advice的目标对象。

总结:Target就是我们需要对它进行Advice的业务类,如果没有AOP,那么该业务类就得自己实现需要的功能。

  • Weaving(织入)
    将Advice添加到Target类具体JoinPoint上的过程

通过以上几个概念,我想大家对AOP已经有了一下感悟,下面,我们就这些概念结合实例再来归纳一下.

实例讲解

假设我们需要对业务上增加一个日志记录的功能.
假设我们有两个业务类ServiceA和ServiceB

ServiceA

package com.aopdemo.service;
import java.util.List;
@Service
public class ServiceA{
    Long insert(Order order){

    }
    int delete(Long id){

    }
    int update(Order order){

    }
    List query(Condition condition){

    }
}

ServiceB

package com.aopdemo.service;
import java.util.List;
@Service
public class ServiceB{
    Long insert(PayLog payLog){

    }
    int delete(Long id){

    }
    int update(PayLog payLog){

    }
    List query(Condition condition){

    }
}

我们可以看到,ServiceA和ServiceB都有对应的增删改查方法.假设,我们现在需要对每个业务的插入方法的前后做一个日志记录的功能.

我们就需要定义一个AOP类


//Spring中的声明该类为AOP的注解
@Aspect
@Component
public class LogAop {

    //PointCut切点,用来过滤JoinPoint连接点,这句的意思即为:
    //该日志Advice只对com.aopdemo.service下的所有service类中的insert方法生效
    @Pointcut("execution(public * com.aopdemo.service.*.insert(..))")
    public void writeLog() {
        //记录日志
    }

    //前置通知,在某切入点@Pointcut之前的通知
    @Before("writeLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("writeLog()")
    public void after(JoinPoint jp) {
        System.out.println("方法最后执行.....");
    }

    //返回后通知,方法执行return之后,可以对返回的数据做加工处理
    @AfterReturning(returning = "ret", pointcut = "writeLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    //抛出异常通知,程序出错跑出异常会执行该通知方法
    @AfterThrowing("writeLog()")
    public void throwss(JoinPoint jp) {
        System.out.println("方法异常时执行.....");
    }

    //环绕增强,在方法的调用前、后执行,相当于MethodInterceptor
    @Around("writeLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        System.out.println("方法环绕start.....");
        try {
            Object o = pjp.proceed();
            System.out.println("方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

总结

AOP就是在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。

小结

一句话总结IOC: Spring容器管理对象,不用自己“new”对象.

请关注我的订阅号

31.从零开始学springboot-再谈切面“AOP”_第2张图片
订阅号.png

你可能感兴趣的:(31.从零开始学springboot-再谈切面“AOP”)