动态代理的实现

可以为任何接口生成任何代理的问题了,首先定义一个接口InvocationHandler,这么起名字是因为JDK提供的代理实例处理程序的接口也是InvocationHandler:

public interface InvocationHandler
{
    void invoke(Object proxy, Method method) throws Exception;
}

所以我们的Proxy类也要修改了,改为:


 1 public class ProxyVersion_2 implements Serializable
 2 {
 3     private static final long serialVersionUID = 1L;
 4     
 5     public static Object newProxyInstance(Class<?> interfaces, InvocationHandler h) throws Exception
 6     {
 7         Method[] methods = interfaces.getMethods();        
 8         StringBuilder sb = new StringBuilder(1024);
 9         
10         sb.append("package com.xrq.proxy;\n\n");
11         sb.append("import java.lang.reflect.Method;\n\n");
12         sb.append("public class $Proxy1 implements " +  interfaces.getSimpleName() + "\n");
13         sb.append("{\n");
14         sb.append("\tInvocationHandler h;\n\n");
15         sb.append("\tpublic $Proxy1(InvocationHandler h)\n");
16         sb.append("\t{\n");
17         sb.append("\t\tthis.h = h;\n");
18         sb.append("\t}\n\n");
19         for (Method m : methods)
20         {
21             sb.append("\tpublic " + m.getReturnType() + " " + m.getName() + "()\n");
22             sb.append("\t{\n");
23             sb.append("\t\ttry\n");
24             sb.append("\t\t{\n");
25             sb.append("\t\t\tMethod md = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\");\n");
26             sb.append("\t\t\th.invoke(this, md);\n");
27             sb.append("\t\t}\n");
28             sb.append("\t\tcatch (Exception e)\n");
29             sb.append("\t\t{\n");
30             sb.append("\t\t\te.printStackTrace();\n");
31             sb.append("\t\t}\n");
32             sb.append("\t}\n");
33         }
34         sb.append("}");
35         
36         /** 生成一段Java代码 */
37         String fileDir = System.getProperty("user.dir");
38         String fileName = fileDir + "\\src\\com\\xrq\\proxy\\$Proxy1.java";
39         File javaFile = new File(fileName);
40         Writer writer = new FileWriter(javaFile);
41         writer.write(sb.toString());
42         writer.close();
43         
44         /** 动态编译这段Java代码,生成.class文件 */
45         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
46         StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
47         Iterable<? extends JavaFileObject> iter = sjfm.getJavaFileObjects(fileName);
48         CompilationTask ct = compiler.getTask(null, sjfm, null, null, null, iter);
49         ct.call();
50         sjfm.close();
51         
52         /** 将生成的.class文件载入内存,默认的ClassLoader只能载入CLASSPATH下的.class文件 */
53         URL[] urls = new URL[] {(new URL("file:\\" + System.getProperty("user.dir") + "\\src"))};
54         URLClassLoader ul = new URLClassLoader(urls);
55         Class<?> c = Class.forName("com.xrq.proxy.$Proxy1", false, ul);
56         
57         /** 利用反射将c实例化出来 */
58         Constructor<?> constructor = c.getConstructor(InvocationHandler.class);
59         Object obj = constructor.newInstance(h);
60         
61         /** 使用完毕删除生成的代理.java文件和.class文件,这样就看不到动态生成的内容了 */
62         File classFile = new File(fileDir + "\\src\\com\\xrq\\proxy\\$Proxy1.class");
63         javaFile.delete();
64         classFile.delete();
65         
66         return obj;
67     }
68 }

最明显的变化,代理的名字变了,从StaticProxy变成了$Proxy1,因为JDK也是这么命名的,用过代理的应该有印象。这个改进中拼接$Proxy1的.java文件是一个难点,不过我觉得可以不用纠结在这里,关注重点,看一下生成的$Proxy1.java的内容是什么:


public class $Proxy1 implements HelloWorld
{
    InvocationHandler h;

    public $Proxy1(InvocationHandler h)
    {
        this.h = h;
    }

    public void print()
    {
        try
        {
            Method md = com.xrq.proxy.HelloWorld.class.getMethod("print");
            h.invoke(this, md);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

看到,我们把对于待生成代理的接口方法的调用,变成了对于InvocationHandler接口实现类的invoke方法的调用(这就是动态代理最关键的一点),并传入了待调用的接口方法,这样不就实现了我们的要求了吗?我们InvocationHandler接口的实现类写invoke方法的具体实现,传入的第二个参数md.invoke就是调用被代理对象的方法,在这个方法前后都是代理内容,想加什么加什么,不就实现了动态代理了?所以,我们看一个InvocationHandler实现类的写法:


public class HelloInvocationHandler implements InvocationHandler
{
    private Object obj;
    
    public HelloInvocationHandler(Object obj)
    {
        this.obj = obj;
    }
    
    public void invoke(Object proxy, Method method)
    {
        System.out.println("Before Hello World!");
        try
        {
            method.invoke(obj, new Object[]{});
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
        System.out.println("After Hello World!");
    }
}

写个main函数测试一下:


public static void main(String[] args) throws Exception
{    
    long start = System.currentTimeMillis();
    HelloWorld helloWorldImpl = new HelloWorldImpl();
    InvocationHandler ih = new HelloInvocationHandler(helloWorldImpl);
    HelloWorld helloWorld = (HelloWorld)ProxyVersion_2.newProxyInstance(HelloWorld.class, ih);
    System.out.println("动态生成代理耗时:" + (System.currentTimeMillis() - start) + "ms");
    helloWorld.print();
    System.out.println();
}

运行结果为:

动态生成代理耗时:351ms
Before Hello World!
Hello World
After Hello World!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值