一个简单的例子模拟JDK动态代理

今天学习了代理模式,并且对JDK动态代理的底层实现有了一定的了解,下面把我学到的和我对动态代理的底层实现写个小例子展示一下。

说到JDK动态代理,大家可能都熟悉。JDK动态代理用起来比较简单。

首先写一个自己的代理类实现InvocationHandler接口

public class MyProxy implements InvocationHandler {

    //被代理对象
    private Object target;

    //通过构造方法注入被代理对象
    public MyProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //这里是一些业务逻辑...

        method.invoke(target, args);

        //这里是一些业务逻辑...

        return null;
    }
}

然后在客户端创建一个代理实例,调用代理的方法来实现业务逻辑的增强

public class MyProxyTest {
    public static void main(String[] args) {
        //创建代理对象实例并且注入被代理对象
        MyProxy myProxy = new MyProxy(new Car());
        //返回一个代理对象
        Moveable car = (Moveable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), myProxy);
        //调用代理对象的方法是已经加上业务逻辑的被代理对象的方法
        car.move();
    }
}

到这里,一个简单的JDK动态代理的例子就写完了。

可能有人会问了,为什么JDK的动态代理为什么必须要求被代理对象实现接口,调用Proxy.newProxyInstance方法返回的为什么是个接口而不是个类,下面我将用个小例子模拟下JDK动态代理。

主要由以下三个类来完成这次模拟操作。

1. 模拟JDK的Proxy类,提供newProxyInstance()方法返回代理对象

2. 模拟InvocationHandler接口,并且实现这个接口写上自己的业务逻辑,完成对原本方法的“加料”。

3. 客户端调用模拟的Proxy类的newProxyInstance方法返回代理对象,并且调用接口的方法完成代理过程。


1.Proxy类

package design.myjdkproxy;

import org.apache.commons.io.FileUtils;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * Created by Administrator on 2017/7/18/018.
 */
public class Proxy {

    /**
     *
     * @param infce 被代理类的接口(因为是模拟,所以只是传入一个接口,
     *                              真实的JDK动态代理是传入被代理类的多个接口)
     * @param h 实现InvocationHandler的实例
     */
    public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception{

        String methodStr = "";
        /**
         * 这里就是为什么JDK动态代理为什么要求被代理类必须实现接口
         * 生成的代理类会重写原代理类接口的所有方法
         * 然后调用用户自己实现的InvocationHandler接口的invoke方法
         * 这样用户自己的业务逻辑就得到了实现
         */
        for(Method method: infce.getMethods()){
            methodStr +=  "    @Override\n" +
                    "    public void " + method.getName() + "() {\n" +
                    "       try{\n" +
                    "           Method m = " +infce.getName() + ".class.getMethod(\"" +
                                           method.getName() + "\");\n" +
                    "           h.invoke(this, m);\n" +
                    "       } catch(Throwable e){\n  e.printStackTrace(); \n}\n" +
                    "    }\n" ;
        }
        /**
         * 通过字符串的方式动态生成类
         */
        String str = "package design.myjdkproxy;\n" +
                "import java.lang.reflect.Method;\n" +
                "public class $Proxy0 implements " + infce.getName() + " {\n" +
                "\n" +
                //通过构造器传入动态生成的代理类
                "    public $Proxy0(InvocationHandler h) {\n" +
                "        this.h = h;\n" +
                "    }\n" +
                "\n" +
                "    private InvocationHandler h;\n" +
                "\n" + methodStr +"\n"+
                "}";

        /**
         * 生成.java文件
         */
        String fileName =  "F:\\untitled\\src\\design\\myjdkproxy\\$Proxy0.java";
        File file = new File(fileName);
        //需要导入commons-io包 将写的字符串的java源文件写入文件中
        FileUtils.writeStringToFile(file, str);

        /**
         * 编译 生成.class字节码文件
         */
        //拿到编译器,类似于javac的东西
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //通过编译器获得文件管理器  参数是语法分析器和一些编码的东西,想了解更多自行百度,这里就设为Null
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        //获取文件
        Iterable units = fileManager.getJavaFileObjects(fileName);
        //获得编译任务
        JavaCompiler.CompilationTask task =compiler.getTask(null, fileManager, null, null, null, units);
        //进行编译,会生成.class字节码文件
        task.call();
        //关闭文件管理器
        fileManager.close();

        /**
         * 1. 获得动态生成的代理类的Class,
         * 2. 通过Class找到构造器
         * 3. 通过构造器返回代理实例
         */
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class c = classLoader.loadClass( "design.myjdkproxy.$Proxy0");

        //LoadClass和Class.forName加载类获得Class的区别是前者不会加载类的静态块和静态变量,后者会
        //Class c = Class.forName("design.myjdkproxy.$Proxy0");

        //找到参数是InvocationHandler的$Proxy0的构造器
        Constructor ctr = c.getConstructor(InvocationHandler.class);

        //返回生成的动态代理的实例
        return ctr.newInstance(h);
    }
}

因为是字符串写的动态类,所以不太好写注释,把生成的结果也就是$Proxy0这个实际的代理拿出来看

这是被代理对象实现的接口,里面有两个方法

public interface Movable {

    void move();

    void test();

}
生成的代理对象$Proxy0根据上面的代码应该是也实现了这个接口并且把这两个方法全部实现

package design.myjdkproxy;
import java.lang.reflect.Method;
public class $Proxy0 implements design.myjdkproxy.Movable {

    //通过构造方法传入自定义InvocationHandler
    public $Proxy0(InvocationHandler h) {
        this.h = h;
    }

    private InvocationHandler h;

    @Override
    public void test() {
       try{
           //获取当前方法的Method对象
           Method m = design.myjdkproxy.Movable.class.getMethod("test");
           /**
            * 调用InvocationHandler的invoke方法
            * this:当前代理对象
            * m:当前方法的Method对象,用来使用反射执行被代理对象的原始方法
            */
           h.invoke(this, m);
       } catch(Throwable e){
            e.printStackTrace();
       }
    }
    @Override
    public void move() {
       try{
           Method m = design.myjdkproxy.Movable.class.getMethod("move");
           h.invoke(this, m);
       } catch(Throwable e){
            e.printStackTrace();
       }
    }
}

果然,生成的代理类实现了原被代理类的接口并且实现了方法,通过调用InvocationHandler的invoke方法,然后再根据用户自定义的业务逻辑对应执行,当然,被代理对象的原本的方法肯定是也要执行的。

下面是被代理对象、自定义的InvocationHandler接口和其实现类


被代理对象Car:

package design.myjdkproxy;

/**
 * Created by Administrator on 2017/7/15/015.
 */
public class Car implements Movable {

    @Override
    public void move() {     
        System.out.println("汽车行驶中...");
    }

    @Override
    public void test() {
        System.out.println("JDK动态代理测试");
    }
}

自定义InvocationHandler接口:


public interface InvocationHandler {

    /**
     * 模拟JDK动态代理的InvocationHandler的invoke方法
     * @param obj 代理对象
     * @param method 被代理对象原本要执行的方法
     * @throws Throwable
     */
    void invoke(Object obj, Method method) throws Throwable;

}

InvocationHandler接口的实现类:


package design.myjdkproxy;

import java.lang.reflect.Method;

/**
 * Created by Administrator on 2017/7/18/018.
 */
public class MyHandler implements InvocationHandler {

    //被代理对象
    private Object target;

    //通过构造器注入被代理对象用来执行被代理对象原本的方法
    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public void invoke(Object obj, Method method) throws Throwable{

        //对应的业务逻辑...
        System.out.println("我在汽车行使前...");

        //执行实际的方法也就是被代理对象原本的方法
        method.invoke(target);

        //对应的业务逻辑...
        System.out.println("我在汽车行使后...");
    }
}

到现在为止,一个模拟的JDK动态代理就剩最后一部了,就是如何调用生成代理对象


package design.myjdkproxy;

/**
 * Created by Administrator on 2017/7/18/018.
 */
public class Client {
    public static void main(String[] args) throws Exception {
        //将被代理对象绑定到自定义事务处理器上
        MyHandler handler = new MyHandler(new Car());
        /**
         * 返回生成的动态代理的实例
         * 这个代理对象也实现了被代理对象的接口
         * 被代理对象实现的接口的方法,代理对象也全部实现了
         * 所以调用被代理对象的方法:m.move,实际上是调用了代理对象.move方法
         * 然后代理对象的move方法又会进入到InvocationHandler的实现类的invoke方法
         * 然后就会执行自定义的业务逻辑并且也执行了被代理对象的原本方法( 这里是move()方法)
         */
        Movable m = (Movable) Proxy.newProxyInstance(Movable.class, handler);
        //调用代理对象的方法
        m.move();
    }
}

最后执行结果:

我在汽车行使前...
汽车行驶中...
我在汽车行使后...


好了,一个简单的JDK动态代理的实现就写完了,本人也是刚开始学习代理模式,这篇文章如果有什么不对或者不恰当的地方欢迎各位大神指正,大家共同学习进步。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值