动态代理机制详解

前言

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler

InvocationHandler is the interface implemented by the invocation
handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a
method is invoked on a proxy instance, the method invocation is
encoded and dispatched to the invoke method of its invocation handler.

每一个动态代理类必须要实现InvocationHandler接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

该方法三个参数

  • proxy: 指代我们所代理的那个真实对象
  • method: 指代的是我们所要调用真实对象的某个方法的Method对象
  • args: 指代的是调用真实对象某个方法时接受的参数

Proxy

Proxy的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstancenewProxyInstance方法的作用就是得到一个动态的代理对象,其接收三个参数:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
  • loader:一个ClassLoader对象定义了由哪个ClassLoader对象 对 生成的代理对象进行加载

  • interfaces:Interface对象的数组,表示的是将要给需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法

  • h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候会关联到哪一个InvocationHandler对象上

动态代理

在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个IHello类型的接口:

public interface IHello {
    /**
     * 业务方法
     * @param str
     */
    void sayHello(String str);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,Hello类:

public class Hello implements IHello{
    @Override
    public void sayHello(String str) {
        System.out.println("hello "+str);
    }
}

下一步,我们就要定义一个动态代理类了,前面说到,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class ProxyHandler implements InvocationHandler {
    //增强类 实例
    private Object enhanceObject;
    //目标对象
    private Object target;

    public Object bind(Object target, Object enhanceObject) {
        this.target = target;
        this.enhanceObject = enhanceObject;
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(),
            this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //反射得到增强类的实例
        Class clazz = this.enhanceObject.getClass();

        //反射得到增强类的Start方法
        Method start = clazz.getDeclaredMethod("start", new Class[] {Method.class});
        //反射执行start方法
        start.invoke(this.enhanceObject, method);

        //执行要处理对象的原本方法
        method.invoke(this.target, args);
        // 打印:Method:public abstract void com.alibaba.polystar.service.intelligent.test.IHello.sayHello(java.lang.String)
        System.out.println("Method:" + method);

        //反射得到增强类的end方法
        Method end = clazz.getDeclaredMethod("end", new Class[] {Method.class});
        //反射执行end方法
        end.invoke(this.enhanceObject, new Object[] {method});

        return result;
    }
}

最后,来看看我们的Client类:

public class Client {
    public static void main(String[] args) {
        IHello hello = (IHello) new ProxyHandler().bind(new Hello(),new DLogger());//如果我们需要日志功能,则使用代理类

        //IHello hello = new Hello();//如果我们不需要日志功能则使用目标类
        hello.sayHello("明天");

        // 打印 com.sun.proxy.$Proxy0
        System.out.println(hello.getClass().getName());
        // 打印 com.sun.proxy.$Proxy0
        System.out.println(hello.getClass().getTypeName());
        // 打印 $Proxy0
        System.out.println(hello.getClass().getSimpleName());
    }
}

我们先来看看控制台的输出:

Sat Jan 08 21:33:48 CST 2022sayHello say hello start...

hello 明天

Method:public abstract void com.alibaba.polystar.service.intelligent.test.IHello.sayHello(java.lang.String)

Sat Jan 08 21:33:48 CST 2022sayHello say hello end

com.sun.proxy.$Proxy0
com.sun.proxy.$Proxy0
$Proxy0

1、为什么这里可以将其转化为IHello类型的对象

IHello hello = (IHello) new ProxyHandler().bind(new Hello(),new DLogger());
// 而bind方法实例化了proxy
Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);

是因为在newProxyInstance方法的第二个参数上,给这个代理对象提供了一组接口,那么这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是IHello类型,所以就可以将其转化为IHello类型了。
2、 hello.sayHello(“明天”); 前后分别打印了日志
这是因为通过代理对象来调用被代理对象方法时,程序就会跳转到由这个代理对象关联到的handler中的invoke方法去执行,而我们的这个handler对象又接受了一个 Hello类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法。
3、为什么System.out.println(hello.getClass().getSimpleName()); 打印的是 $Proxy0
通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是以$开头,proxy为中,最后一个数字表示对象的标号。
4、handler的incoke中method打印
正好是IHello接口中的方法,这也就证明了当通过代理对象来调用方法的时候,其实就是委托由其关联到的 handler 对象的invoke方法中来调用并不是自己来真实调用

Method:public abstract void com.alibaba.polystar.service.intelligent.test.IHello.sayHello(java.lang.String)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值