尚硅谷AOP动态代理(Invocation和Proxy)讲解(一看就会)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

环境:jdk17,spring6


一,代理的概念: 

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。简单来说就是,事情给别人做。
这种废话不多说,反正看不懂,待会我们直接看代码。

二,InvocationHandler接口:

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,就是说还是通过反射来调用的,这一点在后面的代码中可以看出来。


三:Proxy类

.Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

当然,还有其它的方法,比如说:

  • getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
  • isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。

四:下面代码中add方法的执行过程 :

首先:我们来打一些比喻:

把CalculatorPureImpl实例比作去酒店的老板。

把CalculatorPureImpl类中实现的add()方法比作老板手中的玛莎拉蒂车钥匙。

把LogDynamicProxyFactory类比作酒店。

把从LogDynamicProxyFactory实例target手中获得的proxy比作酒店的门童

故事开始:老板到了酒店门口,酒店就安排了一个门童去服务老板,然后,老板把车钥匙给酒店的门童,说:“给我把车停好”。

5.代码实现细节

首先这是接口的代码:

package com.example.AOP;

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

}

实现类的代码:

package com.example.AOP;



public class CalculatorPureImpl 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;
    }
}

代理类代码:

package com.example.AOP;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

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

@Slf4j
// 泛型T要求是目标对象实现的接口类型,本代理类根据这个接口来进行代理
public class LogDynamicProxyFactory<T> {

    // 将被代理的目标对象声明为成员变量
    private T target;

    public LogDynamicProxyFactory(T target) {
        this.target = target;
    }


    public T getProxy() {

        // 创建代理对象所需参数一:加载目标对象的类的类加载器
        //注意这里获取的是CalculatorPureImpl的类加载器,而不是它实现的接口,而且接口怎么会有类加载器呢
        ClassLoader classLoader = target.getClass().getClassLoader();

        // 创建代理对象所需参数二:目标对象的类所实现的所有接口组成的数组
        //这里获取了Calculator接口,因为CalculatorPureImpl是他的实现类
        Class<?>[] interfaces = target.getClass().getInterfaces();

        // 创建代理对象所需参数三:InvocationHandler对象
        // Lambda表达式口诀:
        // 1、复制小括号
        // 2、写死右箭头
        // 3、落地大括号
        //注意InvovcationHandler是个接口
        //重写了InvovcationHandler接口中的invoke方法
        // public Object invoke(Object proxy, Method method, Object[] args)
        InvocationHandler handler = (
                // 代理对象,当前方法用不上这个对象
                Object proxy,

                // method就是代表目标方法的Method对象
                Method method,

                // 外部调用目标方法时传入的实际参数
                Object[] args)->{

            // 我们对InvocationHandler接口中invoke()方法的实现就是在调用目标方法
            // 围绕目标方法的调用,就可以添加我们的附加功能

            // 声明一个局部变量,用来存储目标方法的返回值
            Object targetMethodReturnValue = null;

            // 通过method对象获取方法名
            //比如主人类的add()方法
            String methodName = method.getName();

            // 为了便于在打印时看到数组中的数据,把参数数组转换为List
            //比如说add方法中的参数(int,int)
            List<Object> argumentList = Arrays.asList(args);

            try {

                // 在目标方法执行前:打印方法开始的日志
                log.debug("[动态代理][日志] " + methodName + " 方法开始了,参数是:" + argumentList);

                // 调用目标方法:需要传入两个参数
                // 参数1:调用目标方法的目标对象
                // 参数2:外部调用目标方法时传入的实际参数
                // 调用后会返回目标方法的返回值
//                public Object invoke(Object obj, Object... args)
                //这个invoke方法的第二个参数是一个可变参数
                targetMethodReturnValue = method.invoke(target, args);

                // 在目标方法成功后:打印方法成功结束的日志【寿终正寝】
                log.debug("[动态代理][日志] " + methodName + " 方法成功结束了,返回值是:" + targetMethodReturnValue);

            }catch (Exception e){

                // 通过e对象获取异常类型的全类名
                String exceptionName = e.getClass().getName();

                // 通过e对象获取异常消息
                String message = e.getMessage();

                // 在目标方法失败后:打印方法抛出异常的日志【死于非命】
                log.debug("[动态代理][日志] " + methodName + " 方法抛异常了,异常信息是:" + exceptionName + "," + message);

            }finally {

                // 在目标方法最终结束后:打印方法最终结束的日志【盖棺定论】
                log.debug("[动态代理][日志] " + methodName + " 方法最终结束了");

            }

            // 这里必须将目标方法的返回值返回给外界,如果没有返回,外界将无法拿到目标方法的返回值
            return targetMethodReturnValue;
        };

        // 创建代理对象
        T proxy = (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
        List list = Arrays.asList(proxy.getClass().getDeclaredMethods());
        System.out.println(list);
        //public final int jdk.proxy2.$Proxy23.add(int,int),
        // public final boolean jdk.proxy2.$Proxy23.equals(java.lang.Object),
        // public final java.lang.String jdk.proxy2.$Proxy23.toString(),
        // public final int jdk.proxy2.$Proxy23.hashCode(),
        // public final int jdk.proxy2.$Proxy23.sub(int,int),
        // public final int jdk.proxy2.$Proxy23.mul(int,int),
        // public final int jdk.proxy2.$Proxy23.div(int,int),
        // private static java.lang.invoke.MethodHandles$Lookup
        // jdk.proxy2.$Proxy23.proxyClassLookup(java.lang.invoke.MethodHandles$Lookup) throws java.lang.IllegalAccessException]
        // 从这里我们可以发现,代理拥有(主人类)被代理类的方法如add(int,int),
        // 还拥有代理本身自己的方法equals(java.lang.Object),
        //T proxy = (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
        //所以我们知道这个操作之后,代理对象就拥有了主人类的方法
        // 但是这种说法不准确,其实代理用的还是主人的方法,并没有真正的拥有
        //这一点我们可以从下面的图片可以看出
        System.out.println(proxy.getClass().getName());
        //jdk.proxy2.$Proxy23
        // 返回代理对象
        return proxy;
    }

}

我感觉主人与代理的是,主人的资源你可以用但你不真正拥有他 

大家看看这里,proxy在调用add方法的时候,这里真正调用的还是接口的add方法(其实也就是调用主人的方法) 

 测试类:这里我们已测试add方法为例。

package com.example.AOP;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class testDynamicProxy {
    @Test
    public void testDynamicProxy() {

        // 1.创建被代理的目标对象
        Calculator target = new CalculatorPureImpl();
        //Calculator target = new CalculatorPureImpl();
        //这里的编译类型和运行类型不用我说了吧!
        // 2.创建能够生产代理对象的工厂对象
        LogDynamicProxyFactory<Calculator> factory = new LogDynamicProxyFactory<>(target);

        // 3.通过工厂对象生产目标对象的代理对象
   
        Calculator proxy = factory.getProxy();

        // 4.通过代理对象间接调用目标对象
        
        int addResult = proxy.add(10, 2);
        log.debug("方法外部 addResult = " + addResult + "\n");

//        int subResult = proxy.sub(10, 2);
//        log.debug("方法外部 subResult = " + subResult + "\n");
//
//        int mulResult = proxy.mul(10, 2);
//        log.debug("方法外部 mulResult = " + mulResult + "\n");
//
//        int divResult = proxy.div(10, 2);
//        log.debug("方法外部 divResult = " + divResult + "\n");
    }
}

 


总结

好的,这一篇博客就到这里了,也不知道大家看懂了没,如果没看懂可以自己去debug一下,再继续看一下。如果这篇博客对你有帮助的话,还请你点一个赞支持一下把!感谢感谢。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值