JDK动态代理机制

转载自 http://www.cnblogs.com/huxi/archive/2009/12/16/1625899.html

jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。

写一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。
// Adder.java 
  
package test; 
  
public interface Adder { 
    int add(int a, int b); 
} 
  // AdderImpl.java 
  
package test; 
  
public class AdderImpl implements Adder { 
    @Override
    public int add(int a, int b) { 
        return a + b; 
    } 
} 
// Test.java 
  
package test; 
  
public class Test { 
    public static void main(String[] args) throws Exception { 
        Adder calc = new AdderImpl(); 
        int result = calc.add(1, 2); 
        System.out.println("The result is " + result); 
    } 
} 


然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:
Proxy: invoke add() at 2009-12-16 17:18:06 
The result is 3 

动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。

第一步:实现InvokationHandler,定义调用方法时应该执行的动作。

自定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,需要重写的方法只有一个:

// AdderHandler.java 
  
package test; 
  
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
  
class AdderHandler implements InvocationHandler { 
    /** 
     * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl 
     * @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例 
     * @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数 
     * @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable { 
        // ... 
    } 
} 

使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:

Object returnValue = method.invoke(proxy, args);

如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

// 被代理的对象
private Object target;
 
public AdderHandler(Object target) {
     this .target = target;

接着,invoke()就可以这么用了:

// AdderHandler.java 
  
package test; 
  
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.util.Date; 
  
class AdderHandler implements InvocationHandler { 
    // 被代理的对象 
    private Object target; 
  
    public AdderHandler(Object target) { 
        this.target = target; 
    } 
      
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable { 
        // 调用被代理对象的方法并得到返回值 
        Object returnValue = method.invoke(target, args); 
        // 调用方法前后都可以加入一些其他的逻辑 
        System.out.println("Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString()); 
        // 可以返回任何想要返回的值 
        return returnValue; 
    } 
} 

第二步: 使用jdk提供的java.lang.reflect.Proxy生成代理对象

使用newProxyInstance()方法就可以生成一个代理对象:

 /** 
 * @param loader 类加载器,用于加载生成的代理类。 
 * @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。 
 * @param h 第一步中我们建立的Handler类的实例。 
 * @return 代理对象,实现了所有要代理的接口。 
 */
public static Object newProxyInstance(ClassLoader loader, 
          Class<?>[] interfaces, 
          InvocationHandler h) 
throws IllegalArgumentException 


这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:

// 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。 
  
class AdderProxy extends Proxy implements Adder { 
    protected AdderProxy(InvocationHandler h) { 
        super(h); 
    } 
  
    @Override
    public int add(int a, int b) { 
        try { 
            Method m = Adder.class.getMethod("add", new Class[] {int.class, int.class}); 
            Object[] args = {a, b}; 
            return (Integer) h.invoke(this, m, args); 
        } catch (Throwable e) { 
            throw new RuntimeException(e); 
        } 
    } 
} 

 

所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。

看明白这个方法后,我们来改造一下Test 的main()方法:

// Test.java 
  
package test; 
  
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Proxy; 
  
public class Test { 
    public static void main(String[] args) throws Exception { 
        Adder calc = new AdderImpl(); 
          
        // 类加载器 
        ClassLoader loader = Test.class.getClassLoader(); 
        // 需要代理的接口 
        Class[] interfaces = {Adder.class}; 
        // 方法调用处理器,保存实际的AdderImpl的引用 
        InvocationHandler h = new AdderHandler(calc); 
        // 为calc加上代理 
        calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h); 
          
        /* 什么?你说还有别的需求? */
        // 另一个处理器,保存前处理器的引用 
        // InvocationHandler h2 = new XXOOHandler(h); 
        // 再加代理 
        // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2); 
          
        int result = calc.add(1, 2); 
        System.out.println("The result is " + result); 
    } 
} 

我觉得这篇文章语言讲得比较浅显,对于我这样的新手比较好理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值