Spring—AOP

  |   0 评论   |   0 浏览

Spring—AOP

1.官方文档

离线文档下载

链接:https://pan.baidu.com/s/1Yw0aBcCSbNAZlYbtuYZrog
提取码:0cc9

1.AOP参考离线文档

spring-framework-5.3.8/docs/reference/html/core.html#aop

spring-framework-5.3.8/docs/reference/html/core.html#aop-api

2.动态代理-精致小案例

1.需求说明

● 需求说明

  1. 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
  2. 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后 有统一的输出

2.解决方案-传统解决方式

● 解决方案 1-代码实现

  1. 传统的解决思路,在各个方法的[前,执行过程, 后]输出日志提示信息
  2. 代码实现

交通工具—接口

public interface Vehicle {
    public void run();

    public String fly(int height);
}

Car

public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("汽车在公路上跑");
    }

    @Override
    public String fly(int height) {
        System.out.println("汽车在天上飞,高度="+height);
        return "汽车在天上飞,高度="+height;
    }
}

Ship

public class Ship implements Vehicle{
    @Override
    public void run() {
        System.out.println("船在水上跑");
    }

    @Override
    public String fly(int height) {
        System.out.println("船在天上飞,高度="+height);
        return "船车在天上飞,高度="+height;
    }
}

测试

public static void main(String[] args) {
    Vehicle vehicle = new Car();
    vehicle.run();
}

3.解决方案-动态代理方式

注意:JDK动态代理是必要基于接口实现

参考:https://blog.csdn.net/weixin_38192427/article/details/123459352

VehicleProxyProvider—返回一个代理对象

public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    public Vehicle getProxy() {

        //得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();


        //创建InvocationHandler 对象
        //因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象
        /**
         *
         * public interface InvocationHandler {
         *  public Object invoke(Object proxy, Method method, Object[] args)
         *         throws Throwable;
         * }
         * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
         *
         */

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
             * @param o 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
             * @param args : 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果.
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                //这里通过反射+动态绑定机制,就会执行到被代理对象的方法
                //执行完毕就返回
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了....");
                return result;
            }
        };

        /*

          public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

          1. Proxy.newProxyInstance() 可以返回一个代理对象
          2. ClassLoader loader: 类的加载器.
          3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
          4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}
@Test
public void proxyRun() {
    //创建Car对象
    Vehicle vehicle = new Car();

    //创建VehicleProxyProvider对象, 并且我们传入的要代理的对象
    VehicleProxyProvider vehicleProxyProvider =
            new VehicleProxyProvider(vehicle);

    //获取代理对象, 该对象可以代理执行方法
    //1. porxy 编译类型 Vehicle
    //2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9
    Vehicle proxy = vehicleProxyProvider.getProxy();

    System.out.println("proxy的编译类型是 Vehicle");
    System.out.println("proxy的运行类型是 " + proxy.getClass());
    //proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
    //所以当执行run方法时,会执行到 代理对象的invoke
    String result = proxy.fly(10000);
    System.out.println("result=" + result);
}

3.动态代理-打印日志

接口

public interface SmartAnimalable {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);
}

实现类

public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        //System.out.println("日志-方法名-getSum-参数 " + i + " " + j);
        float result = i + j;
        System.out.println("方法内部打印result = " + result);
        //System.out.println("日志-方法名-getSum-结果result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        //System.out.println("日志-方法名-getSub-参数 " + i + " " + j);
        float result = i - j;
        System.out.println("方法内部打印result = " + result);
        //System.out.println("日志-方法名-getSub-结果result= " + result);
        return result;
    }
}

MyProxyProvider返回一个动态代理对象

public class MyProxyProvider {

    //定义我们要执行的目标对象, 该对象需要实现SmartAnimalable
    private SmartAnimalable target_obj;

    //构造器
    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }

    //方法, 可以返回代理对象,该代理对象可以执行目标对象
    public SmartAnimalable getProxy() {

        //1. 先到的类加载器/对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2. 得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 创建InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("方法执行前-日志-方法名-" + method.getName() + "-参数 "
                            + Arrays.asList(args)); //这里从AOP看,就是一个横切关注点-前置通知
                    //使用反射调用方法
                    result = method.invoke(target_obj, args);
                    System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
                            + result);//从AOP看, 也是一个横切关注点-返回通知

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射执行方法时,出现异常,就会进入到catch{}
                    System.out.println("方法执行异常-日志-方法名-" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
                } finally {//不管你是否出现异常,最终都会执行到finally{}
                    //从AOP的角度看, 也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }

                return result;
            }
        };

        //创建代理对象
        SmartAnimalable proxy =
                (SmartAnimalable)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

测试

public class AopTest {

    /**
     * 传统方式
     */
    @Test
    public void smartDogTest() {

        SmartAnimalable smartAnimalable = new SmartDog();
        smartAnimalable.getSum(10, 2);
        System.out.println("=======================");
        smartAnimalable.getSub(10, 2);
    }

    /**
     * 动态dialing
     */
    @Test
    public void smartDogTestByProxy() {

        SmartAnimalable smartAnimalable = new SmartDog();

        MyProxyProvider myProxyProvider =
                new MyProxyProvider(smartAnimalable);

        SmartAnimalable proxy =
                myProxyProvider.getProxy();

        proxy.getSum(10, 2);
        System.out.println("====================");
        proxy.getSub(10, 2);

    }
}

4.问题再次出现

● 问题提出

  1. 在 MyProxyProvider.java 中, 我们的输出语句功能比较弱,在实际开发中,我们希望是以一个方法的形式,嵌入到真正执行的目标方法前,怎么办?
  2. 如图分析

image-20220521105542808

3.执行之前、执行之后抽取方法

耦合度高

public class MyProxyProvider {

    //定义我们要执行的目标对象, 该对象需要实现SmartAnimalable
    private SmartAnimalable target_obj;

    //构造器
    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }

    public void before(Method method, Object[] args) {
        System.out.println("方法执行前-日志-方法名-" + method.getName() + "-参数 "
                + Arrays.asList(args)); //这里从AOP看,就是一个横切关注点-前置通知
    }

    public void after(Method method, Object result) {
        System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
                + result);//从AOP看, 也是一个横切关注点-返回通知
    }

    //方法, 可以返回代理对象,该代理对象可以执行目标对象
    public SmartAnimalable getProxy() {

        //1. 先到的类加载器/对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2. 得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 创建InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    before(method, args);
                    //使用反射调用方法
                    result = method.invoke(target_obj, args);
                    after(method, result);

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射执行方法时,出现异常,就会进入到catch{}
                    System.out.println("方法执行异常-日志-方法名-" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
                } finally {//不管你是否出现异常,最终都会执行到finally{}
                    //从AOP的角度看, 也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }

                return result;
            }
        };

        //创建代理对象
        SmartAnimalable proxy =
                (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

4.创建一个简单的AOP类

public class LLPAOP {

    public static void before(Method method, Object[] args) {
        System.out.println("方法执行前-日志-方法名-" + method.getName() + "-参数 "
                + Arrays.asList(args)); //这里从AOP看,就是一个横切关注点-前置通知
    }

    public static void after(Method method, Object result) {
        System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
                + result);//从AOP看, 也是一个横切关注点-返回通知
    }

}
public class MyProxyProvider {

    //定义我们要执行的目标对象, 该对象需要实现SmartAnimalable
    private SmartAnimalable target_obj;

    //构造器
    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }


    //方法, 可以返回代理对象,该代理对象可以执行目标对象
    public SmartAnimalable getProxy() {

        //1. 先到的类加载器/对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2. 得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 创建InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    LLPAOP.before(method, args);
                    //使用反射调用方法
                    result = method.invoke(target_obj, args);
                    LLPAOP.after(method, result);

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射执行方法时,出现异常,就会进入到catch{}
                    System.out.println("方法执行异常-日志-方法名-" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
                } finally {//不管你是否出现异常,最终都会执行到finally{}
                    //从AOP的角度看, 也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }

                return result;
            }
        };

        //创建代理对象
        SmartAnimalable proxy =
                (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

5.AOP 的基本介绍

● 什么是 AOP

AOP 的全称(aspect oriented programming) ,面向切面编程

● 2 张示意图说明 AOP 的相关概念

1)一张简易图说明 AOP

image-20220521123436247

2)一张详细图说明 AOP

image-20220521123458382

● AOP 实现方式

  1. 基于动态代理的方式[内置 aop 实现]
  2. 使用框架 aspectj 来实现

6.AOP 编程快速入门

1.基本说明

● 说明

  1. 需要引入核心的 aspect 包
  2. 在切面类中声明通知方法
  1. 前置通知:@Before 目标方法执行之前增强
  2. 返回通知:@AfterReturning 目标方法执行成功之后的增强
  3. 异常通知:@AfterThrowing 目标方法执行异常之后的增强
  4. 后置通知(最终通知):@After 目标方法执行之后 成功异常都执行的增强
  5. 环绕通知:@Around :可以将上面的四个通知合并管理
  1. 五种通知和前面写的动态代理类方法的对应关系

image-20220521123812640

2.快速入门实例

● 需求说明

我们使用 aop 编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例

● 代码实现步骤

1.导入 AOP 编程需要的包

image-20220521124021276

2.代码实现

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  
    <!--包扫描-->
    <context:component-scan base-package="com.llp.spring.aop.aspectj"/>

    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy/>
</beans>

接口

public interface SmartAnimalable {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);
}

接口实现类

//使用@Component 当spring容器启动时,将 SmartDog注入到容器
@Component
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        float result = i + j;
        //result = 1 / 0; //模拟一个算术异常
        System.out.println("方法内部打印result = " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i - j;
        System.out.println("方法内部打印result = " + result);
        return result;
    }


}

切面类

/**
 * 切面类 , 类似于之前的MyProxyProvider,但是功能强大很多
 */
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

    /**
     * 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行
     * 2. value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float)
     * 指定切入到哪个类的哪个方法  形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
     * 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定  比如:showBeginLog
     * 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象
     * , 通过该方法,程序员可以获取到 相关信息
     * @param joinPoint
     */
    @Before(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))")
    public void showBeginLog(JoinPoint joinPoint) {
        //通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
                + Arrays.asList(joinPoint.getArgs()));
    }

    //返回通知:即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
    //1. 如果我们希望把目标方法执行的结果,返回给切入方法
    //2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res"
    //3. 同时在切入方法增加 Object res
    //4. 注意: returning = "res" 和 Object res 的 res名字一致
    @AfterReturning(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
    }



    //异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
    @AfterThrowing(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
    }

    //最终通知:即把showFinallyEndLog方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
    @After(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
    }
}

测试

@Test
public void smartDogTestByProxy() {

    //得到spring容器
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans08.xml");
    //这里我们需要通过接口类型来获取到注入的SmartDog对象-就是代理对象
    SmartAnimalable smartAnimalable =
            ioc.getBean(SmartAnimalable.class);

    //SmartAnimalable smartAnimalable =
    //        (SmartAnimalable)ioc.getBean("smartDog");

    smartAnimalable.getSum(10, 2);

    System.out.println("smartAnimalable运行类型="
            + smartAnimalable.getClass());

    System.out.println("=============================");
    smartAnimalable.getSub(100, 20);
}

3.细节说明

  1. 关于切面类方法命名可以自己规范一下, 比如 showBeginLog() . showSuccessEndLog() showExceptionLog(), showFinallyEndLog()
  2. 切入表达式的更多配置,比如使用模糊配置@Before(value="execution(* com.llp.aop.proxy.SmartDog.*(..))") 表示执行任意修饰符com.llp.aop.proxy.SmartDog类下的任意方法
  3. 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法 @Before(value="execution(* .(..))")
  4. 当 spring 容器开启了 < aop:aspectj-autoproxy/> , 我们获取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型 了!

补充:Phone和camera都有一个beginWork方法示例如下

@Before(value = "execution(* com.llp.spring.aop.aspectj.*.beginWork())")
public void work(JoinPoint joinPoint){
    Signature signature = joinPoint.getSignature();
    System.out.println("切面类work()-方法执行成功/异常-日志-方法名-"+signature.getName()+"-参数-"+ Arrays.asList(joinPoint.getArgs()));
}
@Test
public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
    //报错:有两个类型
    //UsbInterface usbInterface = ioc.getBean(UsbInterface.class);
    //在beanfactory的SingleObjects中包含 phone 和 camera对象 bean的id为phone、camera
    UsbInterface phone = (UsbInterface) ioc.getBean("phone");
    UsbInterface camera = (UsbInterface) ioc.getBean("camera");
    phone.beginWork();
    camera.beginWork();
}

7. AOP-切入表达式

1.具体使用

image-20220521174848918

image-20220521174904259

image-20220521174924873

2.注意事项和细节

  1. 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
  2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
  3. 切入表达式也可以对没有实现接口的类,进行切入(Sping的CGlib)
@Test
public void test3() {
    //得到spring容器
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans08.xml");
    Car car = ioc.getBean(Car.class);
    //说明: car对象仍然是代理对象
    //car的运行类型=class com.llp.spring.aop.aspectj.Car$$EnhancerBySpringCGLIB$$e19aa2bc
    System.out.println("car的运行类型=" + car.getClass());
    car.run();
}
  1. 补充: 动态代理 jdk 的 Proxy 与 Spring 的 CGlib https://www.cnblogs.com/threeAgePie/p/15832586.html

8.AOP-JoinPoint

1.应用实例

● 通过 JoinPoint 可以获取到调用方法的签名

● 应用实例需求说明: 在调用前置通知获取到调用方法的签名, 和其它相关信息

● 应用实例-代码实现

● 其它常用方法一览

joinPoint.getSignature().getName(); // 获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
joinPoint.getTarget(); // 获取被代理的对象
joinPoint.getThis(); // 获取代理对象自己

9.AOP-返回通知获取结果

1.应用实例

● 如何在返回通知方法获取返回结果

如果希望拿到目标方法执行的结果,只有返回通知的方式才能拿到

在执行目标方法getSum成功之后,将结果赋值给 returning = "res",最终传给形参 Object res

//返回通知:即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
//1. 如果我们希望把目标方法执行的结果,返回给切入方法
//2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res"
//3. 同时在切入方法增加 Object res
//4. 注意: returning = "res" 和 Object res 的 res名字一致
@AfterReturning(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
    Signature signature = joinPoint.getSignature();
    System.out.println("切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
}

10.AOP-异常通知中获取结果

1.应用实例

● 异常通知方法中获取异常

看一个需求: 如何在异常通知方法中获取异常信息。

//异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
@AfterThrowing(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
    Signature signature = joinPoint.getSignature();
    System.out.println("切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
}

11.AOP环绕通知

1.应用实例

● 环绕通知可以完成其它四个通知要做的事情

看一个需求: 如何使用环绕通知完成其它四个通知的功能。

注意:

**1.如果是环绕通知形参使用ProceedingJoinPoint **

2.调用结构 try-catch-finally

3.在环绕通知中一定要调用joinPoint.proceed()来执行目 标方法

//演示环绕通知的使用-了解
//1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
//2. value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入点表达式
//3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
@Around(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))")
public Object doAround(ProceedingJoinPoint joinPoint) {
    Object result = null;
    String methodName = joinPoint.getSignature().getName();
    try {
        //1.相当于前置通知完成的事情
        Object[] args = joinPoint.getArgs();
        List<Object> argList = Arrays.asList(args);
        System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
        //在环绕通知中一定要调用joinPoint.proceed()来执行目 标方法
        result = joinPoint.proceed();
        //2.相当于返回通知完成的事情
        System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
    } catch (Throwable throwable) {
        //3.相当于异常通知完成的事情
        System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
    } finally {
        //4.相当于最终通知完成的事情
        System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
    }
    return result;
}

测试效果

@Test
public void smartDogTestByProxy() {
    //得到spring容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
    //这里我们需要通过接口类型来获取到注入的SmartDog对象-就是代理对象
    SmartAnimalable smartAnimalable = ioc.getBean(SmartAnimalable.class);
    //SmartAnimalable smartAnimalable = (SmartAnimalable)ioc.getBean("smartDog");
    smartAnimalable.getSum(10, 2);
    System.out.println("smartAnimalable运行类型="
            + smartAnimalable.getClass());
    System.out.println("=============================");
    smartAnimalable.getSub(100, 20);
}

image-20220521224432250

12.AOP-切入点表达式重用

1.应用实例

● 切入点表达式重用

为了统一管理切入点表达式,可以使用切入点表达式重用技术。

//定义一个切入点, 在后面使用时可以直接引用, 提高了复用性
@Pointcut(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float)))")
public void myPointCut() {
}
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) {
    //通过连接点对象joinPoint 可以获取方法签名
    Signature signature = joinPoint.getSignature();
    System.out.println("SmartAnimalAspect-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
            + Arrays.asList(joinPoint.getArgs()));
}
//@AfterReturning(value = "execution(public float com.llp.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
@AfterReturning(value = "myPointCut()", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
    Signature signature = joinPoint.getSignature();
    System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
}

13.AOP-切面优先级问题

1.应用实例

● 切面优先级问题:

如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制.

● 基本语法:

@order(value=n) 来控制 n 值越小,优先级越高.

● 案例说明

默认:@Order(value = Integer.MAX_VALUE)//表示该切面类执行的顺序, value的值越小, 优先级越高

@Order(value = 1)
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect3 {

}
@Order(value = 2)//表示该切面类执行的顺序, value的值越小, 优先级越高
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

}

image-20220522105554555

2.注意事项和细节说明

  1. 不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和 Filter 过滤器 链式调用类似)

  2. 如何理解执行顺序

    1)类似前面学习过的 Filter 链式调用

    2) 示意图

image-20220522105734725

14.AOP-基于 XML 配置 AOP

1.应用实例

● 基本说明:

前面我们是通过注解来配置 aop 的,在 spring 中,我们也可以通过 xml 的方式来配置 AOP

● 应用实例

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--使用XML配置,完成AOP编程-->
    <!--配置一个切面类对象-bean-->
    <bean class="com.llp.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>
    <!--配置一个SmartDog对象-bean-->
    <bean class="com.llp.spring.aop.xml.SmartDog" id="smartDog"/>
    <!--配置切面类, 细节一定要引入 xmlns:aop-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="myPointCut" expression="execution(public float com.llp.spring.aop.xml.SmartDog.getSum(float, float)))"/>
        <!--配置切面的前置,返回, 异常, 最终通知-->
        <aop:aspect ref="smartAnimalAspect" order="10">
            <!--配置前置通知-->
            <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
            <!--返回通知-->
            <aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
            <!--异常通知-->
            <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
            <!--最终通知-->
            <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
            <!--配置环绕通知-->
            <!--<aop:around method=""/>-->
        </aop:aspect>
    </aop:config>
</beans>

接口

public interface SmartAnimalable {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);
}

实现类

public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        float result = i + j;
        //result = 1 / 0; //模拟一个算术异常
        System.out.println("方法内部打印result = " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i - j;
        System.out.println("方法内部打印result = " + result);
        return result;
    }
}

切面类

public class SmartAnimalAspect {


    public void showBeginLog(JoinPoint joinPoint) {
        //通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
                + Arrays.asList(joinPoint.getArgs()));
    }

    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
    }


    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
    }

    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
    }
}

测试类

public class AopAspectjXMLTest {


    @Test
    public void testAspectByXML() {

        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable =
                ioc.getBean(SmartAnimalable.class);

        smartAnimalable.getSum(10, 2);
    }

}

测试效果

image-20220522111408124


标题:Spring—AOP
作者:llp
地址:https://llinp.cn/articles/2022/05/21/1653144415078.html