Java 动态代理

动态代理


1 Overview

        1.1 InvocationHandler

        1.2 Proxy

2 示例


1 Overview

从 Java 1.3 开始,JDK 在 java.lang.reflect 包中加入了 Proxy 类和 InvocationHandler 接口。Proxy 是一个功能强大的类,它可以用来动态地创建指定接口的代理实现,并把对接口方法调用发送至 InvocationHandler 的实现类执行。

Spring 的 AOP 框架即基于此技术实现。

1.1 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: 指代的是调用真实对象某个方法时接受的参数

1.2 Proxy

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

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

JDK 中注释如下

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义

  • loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
  • interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
  • h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

2 示例

  1. 创建接口

    Hello.java

    package lld.test.reflect;
    
    public interface Hello {
        void sayHi(String name);
    }
    
  2. 创建实现类

    HelloImpl.java

    package lld.test.reflect;
    
    public class HelloImpl implements Hello {
    
        public void sayHi(String name) {
            System.out.println("Hello, " + name);
        }
    
    }
  3. 定义动态代理类,这个可以做成通用类

    MyDynamicProxy.java

    package lld.test.reflect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class MyDynamicProxy implements InvocationHandler {
        
        private Object realObj;
        
        public MyDynamicProxy(Object realObj) {
            this.realObj = realObj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before invoke");
            System.out.println("Method: " + method);
            method.invoke(realObj, args);
            System.out.println("After invoke");
            return null;
        }
    
    }
    
  4. 最后,完成最终应用场景

    ProxyClient.java

    package lld.test.reflect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class ProxyClient {
    
        public static void main(String[] args) {
            Hello realObj = new HelloImpl();
            InvocationHandler handler = new MyDynamicProxy(realObj);
    
            Hello hello = (Hello) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                    realObj.getClass().getInterfaces(), handler);
            System.out.println(hello.getClass().getName());
            hello.sayHi("Lindong");
    
        }
    
    }
    
  5. 运行,输出结果如下所示:

    com.sun.proxy.$Proxy0
    Before invoke
    Method: public abstract void lld.test.reflect.Hello.sayHi(java.lang.String)
    Hello, Lindong
    After invoke
    

首先,我们看到打印出来的类名是 $Proxy0,而不是 Hello 或 InvocationHandler 的实现类,但却又可以转化为 Hello 接口实现。原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Hello 类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

我们看到我们的这个 method 对象是这样的:

Method: public abstract void lld.test.reflect.Hello.sayHi(java.lang.String)

正好就是我们的 Hello 接口中的方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

 

posted @ 2017-10-22 20:54  真栋哥  阅读(158)  评论(0编辑  收藏  举报