Spring之AOP理解及使用

大家好,我是Leo!今天给大家带来的是关于Spring AOP的一些知识和动态代理的两种实现方式,以及AOP的使用,希望大家可以喜欢👍👍👍

AOP是什么

AOP:面向切面编程,简单的说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清楚,耦合性更低。

AOP

切面(Aspect):描述的是一个应用系统的某一方面或领域,如日志、事务、权限检查等。切面和类非常相似,对连接点、切入点、通知及类型间声明进行封装。

连接点(Joinpoint):连接点是应用程序执行过程中插入切面的点,这些点可能是方法的调用、异常抛出或字段的修改。Spring只支持方法的Joinpoint,也就是Advice将方法执行的前后被应用

通知(Advice):表示切面的行为,具体表现位实现逻辑的一个方法,常见的方法有Before、After、Around和Throws。Before和After分别表示通知在连接点的前面或者后面执行,Around则表示在连接点的外面执行,并可以决定是否执行此连接点。Throws通知方法抛出异常时执行。

切入点(Pointcut):切入点指定了通知应用在哪些连接点上,Pointcut切点通过正则表达式定义方法集合。切入点由一系列切入点指示符通过逻辑运算组合得到,AspectJ的常用切入点指示符包括execution、call、inititalization、handler、get、set、this、target、args、within等

目标对象(Target): 目标对象是指被通知的对象,它是一个普通的业务对象,如果没有AOP,那么它其中可能包含打量的非核心业务逻辑代码,如日志、事务等;而如果使用了AOP,则其中只有核心的业务逻辑代码。

代理(Proxy):代理是指通知应用到目标对象后形成的新的对象。

织入(Weraving): 织入是指将切面应用到目标对象而建立一个新的代理对象的过程,切面在指定的接入点被织入目标对象中。

Spring的通知类型

1.Before通知

Before通知处理前,该方法还未执行,所以使Before通知无法返回目标方法的值

2. AfterReturning通知

在切点表达式中定义的方法后执行

pointcut/value:这两个属性是一样的,用来指定切入点对呀的切入点表达式,可以是已定义的切入点,也可直接定义切入点表达式

returning:指定一个返回值的形参名,可以通过该形参可访问目标方法的返回值

3.AfterThrowing通知

主要用来处理程序中未处理的异常。使用AfterThrowing通知可指定如下属性。

pointcut/value:用来指定切入点对应的切入点表达式

throwing:指定一个返回形参名,通过该形参访问目标方法抛出但未处理的异常。

4. After通知

与AfterReturning通知非常类似,但是AfterReturning通知只有在目标方法执行完毕才可以被织入

5. Around通知

这个功能比较强大,近似等于Before通知和AfterReturning通知的总合,但是Around通知可以决定目标方法什么时候执行,如何执行。

在定义Around通知的切面逻辑方法时,必须给方法加入ProceedingJoinPoint类型的参数,在方法内调用ProceedingJoinPoint的proceed()方法才会执行目标方法。

优先级由低到高的顺序

Before通知–》Around通知–》AterReturning通知 --》After通知 /AfterThrowing通知

动态代理

动态代理就是,在程序运行期创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

Spring AOP是通过动态代理实现的,动态代理主要有两种方式JDK动态代理和CGLib动态代理,这两种动态代理的使用和原理有些不同。

JDK动态代理

基于接口的动态代理

  1. 对于JDK代理,目标类需要实现一个Interface

  2. InvocationHandler是一个接口,可以实现这个接口,再通过调用反射机制(invoke)调用目标类的代码

  3. Proxy可以使用newProxyInstance,传入类加载器,目标的接口,InvocationHandler,生成一个动态代理的对象

CGLib动态代理

基于类的动态代理

  1. JDK代理的一大限制是只能为接口创建代理实例,CgLib没有这个限制

  2. CgLib创建的动态代理对象性能比JDK性能要高

  3. CgLib是基于ASM实现,通过字节码技术为一个类创建子类,在子类中采用方法拦截父类方法的调用

两种动态代理的图如下,可以很清楚看出JDK代理是需要实现接口,而CgLib不需要。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7UCAVtP-1677496876494)(D:\卓面\学习文件\学习内容\博客\博客插图\JDK动态代理.drawio.png)]

动态代理的代码展示

JDK动态代理

public class ProxyFactory {

    private Object target;

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

    public Object getProxy() {

        ClassLoader classLoader = target.getClass().getClassLoader();

        Class<?>[] interfaces = target.getClass().getInterfaces();

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             *
             * @param proxy 代理对象
             * @param method 代理需要实现的方法
             * @param args 参数
             * @return 返回值
             * @throws Throwable 抛出的异常
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                // 调用目标的方法
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+ method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+ method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+ method.getName()+",异常:"+ e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] "+ method.getName()+",方法执行完毕");
                }
                return result;
            }
        };
        /**
         * Proxy.newProxyInstance
         * 三个参数
         * 1. 加载动态生成代理类来加载的类加载器
         * 2. interfaces: 目标对象实现的所有接口class类型数组
         * 3. InvocationHandler 设置代理对象实现对象方法的过程
         */
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

CgLib动态代理

public class ProxyCglibFactory implements MethodInterceptor {

    private Object target;

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

    public Object getProxyInstance() {

        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类对象处理
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        // 调用目标的方法
        Object result = null;
        try {
            System.out.println("[动态代理][日志] "+ method.getName()+",参数:"+ Arrays.toString(args));
            result = method.invoke(target, args);
            System.out.println("[动态代理][日志] "+ method.getName()+",结果:"+ result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("[动态代理][日志] "+ method.getName()+",异常:"+ e.getMessage());
        } finally {
            System.out.println("[动态代理][日志] "+ method.getName()+",方法执行完毕");
        }

        return result;
    }
}

AOP使用

AOP切面这些概念的定义是比较抽象的,下面我们来看一下如何使用。

假设一个场景,我有一个计算器类有加减乘除四个方法,我想要打印每一个方法的参数和对应的返回值。

有人就说啦,我每一个System.out.println,或者log.info不就行了嘛,但是加入有一百个方法呢,不断复制CV是不是多余的?

Calculator.java

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("方法内部 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;
    }
}

切面类的配置

@Component
@Aspect
public class LogAspect {

    /**
     * 设置切入点和通知类型
     * 切入点表达式 execution(访问修饰符 返回值类型 方法所在类的全路径 方法名 参数列表
     * 通知类型:
     * 前置@Before
     * 返回@AfterReturning
     * 异常@AfterThrowing
     * 后置@After
     * 环绕@Around()
     */
    @Before(value = "pointCut()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Logger-->前置通知,方法名称:" + methodName + "参数:" + Arrays.toString(args));
    }

    @After(value = "pointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名称:" + methodName);
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();

        System.out.println("Logger-->返回前置通知,方法名称:" + methodName + "返回值:" + result.toString());
    }

    @AfterThrowing(value = "pointCut())", throwing = "exception")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名称:" + methodName + "异常:" + exception.toString());
    }

    @Around(value = "pointCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String argsString = joinPoint.getArgs().toString();
        Object result = null;
        try {
            System.out.println("环绕通知");
            // 调用目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知==目标方法返回值之后");
        } catch (Throwable ex) {
            System.out.println("环绕通知==目标方法出现异常之后");
        } finally {
            System.out.println("环绕通知==目标方法执行完毕");
        }
        return result;
    }

    /**
     * 重用切入点表达式
     */
    @Pointcut(value = "execution(* com.zly.aop.annotation.CalculatorImpl.* (..))")
    public void pointCut() {

    }
}

[外链图片转存中...(img-o6Rg7hvZ-1677496876495)]

最后

希望各位大佬指正,点点👍

posted @ 2023-02-27 19:26  Leo哥coding~  阅读(31)  评论(0编辑  收藏  举报  来源