day125-aop

发布时间 2023-08-05 16:49:52作者: 北海之上

aop

简介

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

相关术语

  1. 横切关注点

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

  1. 通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

前置通知:在被代理的目标方法执行

返回通知:在被代理的目标方法成功结束后执行(寿终正寝

异常通知:在被代理的目标方法异常结束后执行(死于非命

后置通知:在被代理的目标方法最终结束后执行(盖棺定论

环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

  1. 切面

封装通知方法的类。

  1. 目标

被代理的目标对象

  1. 代理

向目标对象应用通知之后创建的对象

  1. 连接点

这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。

  1. 切入点

定位连接点的方式。

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。

如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。

Spring 的 AOP 技术可以通过切入点定位到特定的连接点。

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

aop作用

简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

基于注解的AOP

技术说明

动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。

AspectJ:本质上是静态代理,将代理逻辑*“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

实现

public interface Calculator {
    int add(int i,int j);
​
    int sub(int i,int j);
​
    int mul(int i,int j);
​
    int div(int i,int j);
}
@Component
public class CalculatorImpl implements Calculator {
​
    @Override
    public int add(int i, int j){
        int result = i+j;
        System.out.println("inner" +result);
        return result;
    }
​
    @Override
    public int sub(int i, int j) {
        int result = i-j;
        System.out.println("inner" +result);
        return result;
    }
​
    @Override
    public int mul(int i, int j) {
        int result = i*j;
        System.out.println("inner" +result);
        return result;
    }
​
    @Override
    public int div(int i, int j) {
        int result = i/j;
        System.out.println("inner" +result);
        return result;
    }
}
​

 

创建切面类

@Component
@Aspect //通过@Aspect将此类标识为切面
public class LoggerAspect {
    //1. 在切面中,要通过指定的注解将方法标识为通知方法
    //@Before 前置通知,在目标对象方法执行前执行
    //@After 后置通知,在目标对象方法finally中执行
    //@AfterReturning 返回通知,在目标对象方法返回值之后执行,对应try中执行有错误不执行此方法
    //@AfterThrowing 异常通知,在目标对象方法catch中执行
//2. 切入点表达式:设置在标识通知的注解value属性中
    //execution(public int com.gu.spring.annotation.CalculatorImpl.add(int ,int ))
    //execution(* com.gu.spring.annotation.CalculatorImpl.*(..))
    //第一个* 表示任意访问修饰符和返回值类型
    //第二个* 表示其中任意的方法
    // .. 表示任意的参数列表
    //类的地方也可以使用* ,表示包下所有的类
//3. 获取连接点的信息
    // 在通知方法的参数位置,设置joinPoint的参数,就可以获取连接点所对应的方法的信息
    //    //获取连接点对应方法的签名信息
    //    Signature signature = joinPoint.getSignature();
    //    //获取连接点对应方法的参数
    //    Object[] args = joinPoint.getArgs();
​
​
    //4.重用切入点表达式
    //@Pointcut("execution(* com.gu.spring.annotation.CalculatorImpl.*(..))")声明一个公共的切入点表达式
    //使用方式:@Before("pointCut()")
​
​
    //5. 切面的优先级
    // 可以通过@Order注解的value属性设置优先级,默认值为integer的最大值
    //@Order的value属性值越小优先级越高
    @Pointcut("execution(* com.gu.spring.annotation.CalculatorImpl.*(..))")
    public void pointCut(){
​
    }
​
​
    //@Before("execution(public int com.gu.spring.annotation.CalculatorImpl.add(int ,int ))")
    //@Before("execution(* com.gu.spring.annotation.CalculatorImpl.*(..))")
    @Before("pointCut()")
    public void brforeAdviceMethod(JoinPoint joinPoint){
        //获取连接点对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        //获取连接点对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect,方法: "+signature.getName()+"参数: "+ Arrays.toString(args));
        System.out.println(signature);
        System.out.println(Arrays.toString(args));
​
    }
​
    @After("pointCut()")
    public void afterAdviceMethod(JoinPoint joinPoint){
        //获取连接点对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        //获取连接点对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect :"+signature.getName()+" okk");
    }
​
    //在返回通知中,若要获取目标对象方法的返回值,只需要通过@AfterReturning 注解的returning属性
    //就可以将通知方法的某个参数指定为接受目标方法的返回值的参数
    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
        //获取连接点对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        //获取连接点对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect :"+signature.getName()+" result: "+result);
    }
​
    //在异常通知中,若要获取目标对象方法的异常值,只需要通过@AfterThrowing 注解的throwing属性
    //就可以将通知方法的某个参数指定为接受目标方法的异常值的参数
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void afterThrowAdviceMethod(JoinPoint joinPoint,Throwable ex){
        //获取连接点对应方法的签名信息
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect : "+signature.getName()+" 异常: "+ex);
    }
​
​
    @Around("pointCut()")
    //环绕通知的方法的返回值一定要和目标对象的方法的返回值一致
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
        Object result = null;
        //表示目标对象方法的执行
        try {
            System.out.println("环绕通知--前置通知");
            result = joinPoint.proceed();
            System.out.println("环绕通知--返回通知");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("环绕通知--异常通知");
        } finally {
            System.out.println("环绕通知--后置通知");
        }
        return result;
    }
}

 

配置文件

<!--    
    AOP的注意事项:
    切面类和目标类都需要交给IOC容器管理
    @Aspect:切面类必须通过@Aspect注解标识为一个切面
    在spring配置文件中设置aop:aspectj-autoproxy
-->
<context:component-scan base-package="com.gu.spring.annotation"></context:component-scan><!--  开启基于注解的AOP  -->
<aop:aspectj-autoproxy />

 

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

优先级高的切面:外面

优先级低的切面:里面

使用@Order注解可以控制切面的优先级:

@Order(较小的数):优先级高

@Order(较大的数):优先级低

基于xml的AOP

实现

<!--  扫描组件  -->
    <context:component-scan base-package="com.gu.spring.xml"></context:component-scan>
    
    <aop:config>
<!--    设置一个公共的切入点表达式    -->
        <aop:pointcut id="pointCut" expression="execution(* com.gu.spring.xml.CalculatorImpl.*(..))"/>
<!--    将ioc容器中某个bean设置为切面    -->
        <aop:aspect ref="loggerAspect">
            <aop:before method="brforeAdviceMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowAdviceMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>

 

over