4_面向切面AOP

面向切面AOP

1. 场景模拟

声明计算器接口Calculator,包含加减乘除的抽象方法

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);
}

创建接口实现类:CalculatorImpl

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

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

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result =" + result);
        return result;
    }
}

创建带日志功能的实现类CalculatorLogImpl

public class CalculatorLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i + j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i - j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i * j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i / j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }
}

提出问题

针对带日志功能的实现,我们发现有如下缺陷:

  • 对核心业务有干扰,导致程序员在开发核心功能时分散了注意力
  • 附加功能分散在各个业务功能的方法中,不利于维护统一

解决思路:解耦合,让核心代码与日志分离

目的就是为了减少非业务代码对业务代码的干扰!有利于统一维护。

2. 代理模式

开始->调用代理对象->调用目标方法->目标方法把结果返回代理对象->代理对象把返回值返回给最初的调用者。

生活中的代理:

  • 广告商找明星拍广告需要经过经纪人
  • 房产中介是买卖双方的代理
  • 合作伙伴找大老板合作要约见面时间需要经过秘书

2.1 静态代理

静态代理能够实现代码的解耦,但是由于代码写死了,完全不具备灵活性,因此这并不是一个很好的方案。

创建静态代理

public class CalculatorStaticProxy implements Calculator{
    //被代理的目标对象要穿递过来
    private Calculator calculator;
    public CalculatorStaticProxy(Calculator calculator){
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        //输出日志
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        //调用核心业务
        int result = calculator.add(i,j);
        //输出日志
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        return 0;
    }

    @Override
    public int mul(int i, int j) {
        return 0;
    }

    @Override
    public int div(int i, int j) {
        return 0;
    }
}

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过一个代理类来实现。这就是需要使用动态代理技术。

2.2 动态代理

调用流程:调用代理类->操作前->调用核心代码->调用后->结束

生产代理工厂ProxyFactory:

package com.lily;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {
    //创建一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //创建一个方法,返回一个代理对象
    public Object getProxy() {
        /**
         * Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);
         *
         * ClassLoader loader:加载动态生成代理类的类加载器
         * interfaces:目标对象实现的所有接口class类型的数组
         * handler:设置代理对象它实现目标对象方法的过程
         */
        ClassLoader classLoader = this.target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        var invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象要实现的方法
                 * args:method方法当中的参数
                 */
                //调用方法前输出
                System.out.println("[动态代理][日志]" + method.getName() + ", 参数:" + Arrays.toString(args));
                Object result = method.invoke(target, args);
                //调用方法后输出
                System.out.println("[动态代理][日志]" + method.getName() + ", 结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

创建测试类测试:

public class Main {
    public static void main(String[] args) {
        //创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
        //调用方法
        Calculator proxy = (Calculator)proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

结果如下:

[动态代理][日志]add, 参数:[1, 2]
方法内部 result =3
[动态代理][日志]add, 结果:3

Process finished with exit code 0

3 AOP概念以及相关术语

AOP(Aspect Oriented Programming)是一种设计思想,面向横切面编程。它以通过预编译的方法和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对各个部分进行隔离,进一步降低耦合度。

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

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束执行
  • 环绕通知:使用tay...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的位置。

4. 基于注解的AOP

AOP底层使用的就是动态代理,其中分为两类:

  • JDK动态代理->适用于有接口的情况->生成接口实现类的代理对象。
  • cglib动态代理->适用于没有接口的情况->通过继承被代理的目标类,生成子类代理对象。

AspectJ->是AOP框架,Spring只是借用了AspectJ当中的注解实现了AOP功能。

引入如下依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>

创建切面类LogAspect并配置

package com.lily;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect //  表示这是一个切面类
public class LogAspect {
    //设置切入点和通知类型
    /**
     * 通知类型:前置@Before(value="切入点表达式")
     * 返回@AfterReturning  异常@AfterThrowing  后置@After  环绕@Around
     */
    /**
     * 切入点表达式:
     * execution(方法权限修饰符 方法返回值 方法所在类的全类名(实现类).方法名(参数列表) )
     */
    @Before(value = "execution(public int com.lily.CalculatorImpl.add(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("前置通知,参数名称:" + joinPoint.getArgs().toString());
    }

    @After(value = "execution(public int com.lily.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }


    @AfterReturning(value = "execution(public int com.lily.CalculatorImpl.*(..))", returning = "result")
    public void afterReturing(JoinPoint joinPoint, Object result) {
        System.out.println("返回通知,结果为:" + result);
    }

    @AfterThrowing(value = "execution(* com.lily.CalculatorImpl.*(..))", throwing = "exceptionMessage")
    public void afterThrowing(JoinPoint joinPoint, Throwable exceptionMessage) {
        System.out.println("异常通知,异常信息" + exceptionMessage);
    }

    @Around(value = "execution(* com.lily.CalculatorImpl.*(..))")
    public void around(JoinPoint joinPoint) {
        System.out.println("环绕信息");
    }
}

开启启AspectJ自动代理,为目标对象生成代理:

@ComponentScan("com.lily")
@EnableAspectJAutoProxy //开启AspectJ自动代理,为目标对象生成代理
public class Main {
    public static void main(String[] args) {

    }
}

package com.lily;

import org.springframework.stereotype.Component;

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

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

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result =" + result);
        return result;
    }
}

重用切入点表达式

由于程序中出现了大量重复的表达式,因此我们可以重用切入点表达式:

  • 声明

    @Pointcut(value = "execution(* com.lily.CalculatorImpl.*(..))")
        public void pointCut(){}
    
  • 使用

    //如果在同一个切面(同一个文件下):
    @After(value = "pointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    
    //如果不在同一个切面(同一个文件下):
    @After(value = "com.lily.LogAspectpointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    

切面的优先级:

  • 外面的优先级高于里面
  • @Order(较小的数):优先级高;@Order(较大的数):优先级低

5. 基于XML的AOP

实现

<?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:content="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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!-- 开启组件扫描 -->
    <content:component-scan base-package="com.lily"></content:component-scan>
    <!--配置AOP五种通知-->
    <aop:config>
        <!-- 配置切面类 -->
        <aop:aspect ref="logAspect">
            <!-- 配置切入点 -->
            <aop:pointcut id="pointCut" expression="execution(* com.lily.CalculatorImpl.*(..))"/>
            <!-- 配置5种通知类型 -->
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturing" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" throwing="exceptionMessage" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:before method="around" pointcut-ref="pointCut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
posted @ 2024-03-06 13:22  LilyFlower  阅读(2)  评论(0编辑  收藏  举报