jdk动态代理实现原理,并造一个类似的轮子

1. JDK动态代理最小案例

JDK 动态代理干什么 怎么用,我想并不需要复述,这里快速贴上本案列的代码

委托类:

public class Xiaobai implements House  {
    @Override
    public void buy() {
        System.out.println("我是小白,我要买房");
    }
}

委托接口:

public interface House {
    public void  buy();
}

代理类

public class JdkProxy implements InvocationHandler {
    private Object target;
    public Object getProxy(Object target2) {
        this.target = target2;
        Class<? extends Object> aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是一个代理");
        method.invoke(target,args);
        System.out.println("代理结束的啦");
        return proxy;
    }
}

调用:

 House proxy = (House) new JdkProxy().getProxy(new Xiaobai());
 System.out.println(proxy.getClass().getName());
 proxy.buy();

打印结果如下

2.获取动态代理生成的class文件

这里如果直接打印生成的House的类,会发现,他不并不是原来我实现的 Xiaobai 的实例对象,如图所示

所以要探究他的原理如何,就要看看他生成对应的代理文件是怎么样的

        //获取代理类
        Class<? extends House> aClass = proxy.getClass();
        //获取代理对象CLASS中的内容,这里的$Proxy0 是 上面proxy.getClass().getName() 打印出来的最后一位
        byte[] chyxxxes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{House.class});
        //保存class内容到 xxxx.class中,后面是流操作
        File file = new File("xxxx.class");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(chyxxxes);
        fileOutputStream.close();

这里 就把生成的class文件保存下来了.打开对应的文件.

这里分部讲解一下 首先

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

 static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("chy.House").getMethod("buy");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

这里可以看出,他把代理对象中的方法 都用反射提取出来. 放入类属性中.这方便后面的调用

这里删除了try 以及 equals toString 这些方法的代理.只看我自己定义的接口如下:

public final class $Proxy0 extends Proxy implements House {
    private static Method m3;
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void buy() throws  {
       super.h.invoke(this, m3, (Object[])null);
    }

    static {
            m3 = Class.forName("chy.House").getMethod("buy");
    }

其中 

这个 InvocationHandler 是不是很熟悉,他就是 我们自己写代理类时候实现的那个接口

 

所以 JDK实现的动态代理,必须要有接口,因为 他生成的代理类,会实现对应的接口,然后重写接口里的所有方法,重写的时候,调用你自定义的 InvocationHandler.invoke

3. 尝试自己造个一样的轮子

以上所看,JDK动态代理原理其实很简单.总结起来 步骤如下:

1 . 动态创建一个类,该类实现需要代理的接口,

2. 反射获取对应的方法.

3. 生成对应的class文件,加载进JVM中.

难点在于: 如何生成class文件,并动态加载.

首先我们先去看看 JDK是怎么生成的

进入

newProxyInstance 方法一直走,发现他调用的一个内部类 ProxyClassFactory 来生成代理对象

这里生成了对应 代理对象的 的流文件 然后调用了 defineClass0 方法生成对应的 类

不过这个 defineClass0 是native 的方法,还是私有的.看来白嫖JDK是不行了.

所以 用了个LOW点的方法

4. 一个很山寨的轮子

由上可见, JDK动态代理 调用了,本地的方法生成加载了 对应的类对象, 我这边 为了方便操作, 使用javassis 字节码库进行 类的动态创建. 

当然 也可以不用 字节码类库 ,思路如下:

           在硬盘上生成对应的 .JAVA文件. 编译成 .class文件,动态加载.

而笔者秉着早点写完早点打游戏的心态 偷个懒

和JDK的一样先来个接口:

public interface ChyInvocationHandler {

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

 

然后主要逻辑类:

public class ChyProxy {

    //classPool 创建需要大量性能,作为全局使用
    public static  ClassPool classPool = ClassPool.getDefault();
    
    public static Method m1 = null;

    //生成代理类的名称,按照约定,动态生成的 类前面加上 $
    private static final String proxyClassName = "chy.proxy.$ChyProxy";

    public static Object newProxyInstance(Class<?>[] interfaces, ChyInvocationHandler invocationHandler) throws NotFoundException, CannotCompileException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {


        //区别每一个 代理类的名称
        String className = proxyClassName + System.currentTimeMillis();
        //创建一个类
        CtClass ctClass = classPool.makeClass(className);

        //创建代理类的时候,设置一个全局属性 invocationHandler;
        ctClass.addField(CtField.make("private chy.proxy.ChyInvocationHandler invocationHandler;",ctClass));




        String constructorInitMethode = "";
        //因为传入的可能是多个接口,遍历每一个接口
        for (Class<?> inf : interfaces) {
            //拿到接口CtClass对象
            CtClass infCtClass = classPool.get(inf.getTypeName());
            //让代理类实现这个接口
            ctClass.addInterface(infCtClass);
            //拿到接口上所有方法
            Method[] infMethods = inf.getDeclaredMethods();
            //将接口上所有方法都在 新的代理类中实现一遍
            constructorInitMethode += infMethodsHandle(infMethods,ctClass,inf);
        }

        String tryCath = " try {\n" +
                "            " +constructorInitMethode+"\n"+
                "        } catch (NoSuchFieldException e) {\n" +
                "            e.printStackTrace();\n" +
                "        } catch (ClassNotFoundException e) {\n" +
                "            e.printStackTrace();\n" +
                "        }";

        //设置一个有参构造器,把invocationHandler 传进去,并且给 全局方法赋值
        CtConstructor constructor = new CtConstructor(new CtClass[] {classPool.get(ChyInvocationHandler.class.getTypeName())}, ctClass);
        constructor.setModifiers(Modifier.PUBLIC);
        constructor.setBody("{this.invocationHandler=$1;"+tryCath+"}");
        ctClass.addConstructor(constructor);


        ctClass.writeFile("/Users/bignosecat/IdeaProjects/pg/shejimoshi/target/classes/chy/proxy/");
        //获取动态生成的类

        Class<?> aClass =  ctClass.toClass();
        //获取有参构造器
        Constructor constructor1 = aClass.getDeclaredConstructor(ChyInvocationHandler.class);
        //生成对应的对象
        Object o = constructor1.newInstance(invocationHandler);
        return o;
    }


    public static String  infMethodsHandle(Method[] methods, CtClass ctClass, Class<?> inf) throws CannotCompileException {
        //用于抽取接口方法,用反射投射到属性上;
        String initMethod = "";

        for (Method method : methods) {
            String name = method.getName();
            //获取接口的参数个数
            Parameter[] parameters = method.getParameters();
            //拼接接口的参数
            String param = "";
            int count = 0;
            for (Parameter parameter : parameters) {
                if(count != 0){
                    param = param + ",";
                }
                String typeName = parameter.getType().getTypeName();
                param = param + typeName+ " var"+count;
                count++;
            }
            //invocationHandler.invoke 中参数的拼接
            String invokeParam = "(Object[])null";
            if(count != 0){

            }
            //在代理类里设置一个 全局变量存放 接口方法, 用var_ 开头 如 var_buy
            String methodName = "var_" + name;
            ctClass.addField(CtField.make("private static java.lang.reflect.Method "+methodName+";",ctClass));


            String methodStr = "  public final void "+name+"("+param+")  {\n" +
                    "        try {\n" +
                    "            invocationHandler.invoke(this, var_"+name+", "+invokeParam+");\n" +
                    "        } catch (java.lang.Exception e) {\n" +
                    "            throw e;\n" +
                    "        } catch (java.lang.Throwable t1) {\n" +
                    "            throw new java.lang.reflect.UndeclaredThrowableException(t1);\n" +
                    "        }\n" +
                    "    }";


            //创建对应的 实现方法
            ctClass.addMethod( CtMethod.make(methodStr,ctClass));

            //拼接反射抽取方法
            initMethod = initMethod + methodName+" = java.lang.Class.forName(\""+inf.getTypeName()+"\").getMethod(\""+name+"\",null);\n";
        }
        return initMethod;
    }
}

5.轮子测试

在之前的案列上改进一下

委托类和接口都不变, 重写一个 ChyInvocationHandlerHandle 类:

public class ChyInvocationHandlerHandle implements ChyInvocationHandler {

    private Object target;

    public Object getProxy(Object target2) throws NoSuchMethodException, IllegalAccessException, IOException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException, ClassNotFoundException {
        this.target = target2;
        Class<? extends Object> aClass = target.getClass();
        return ChyProxy.newProxyInstance( aClass.getInterfaces(),this);
    }

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

        System.out.println("我是一个代理");
        method.invoke(target,args);
        System.out.println("代理结束的啦");

        return proxy;
    }
}
运行:
 public static void main(String[] args) throws Exception {

        House proxy = (House) new ChyInvocationHandlerHandle().getProxy(new Xiaobai());
        proxy.buy();
    }

结果:

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值