手写Java动态代理

本文深入探讨了Java动态代理的原理,通过实例展示了如何手写动态代理。首先,分析了JDK动态代理的使用,揭示了代理类是如何在运行时生成并调用InvocationHandler实现增强的。接着,详细讲解了自定义InvocationHandler、代理类的生成以及类加载的过程。最后,总结指出虽然动态代理提供了类的增强功能,但过度使用可能影响效率,且JDK动态代理依赖于接口,而Spring的CGLIB库则无需接口。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如图1所示,动态代理的使用

这篇文章讲解动态代理的原理,以及如何手写动态代理。

以下是有关动态代理的使用,这是JDK默认帮我们实现的动态代理。

public class Main implements InvocationHandler {
    static Person person=new PersonImp();
    public static void main(String[] args) throws Throwable {        

        Person o = (Person)Proxy.newProxyInstance(person.getClass().getClassLoader(),         
        person.getClass().getInterfaces(),new Main());
        o.marry();
    }

    /**
     * 动态代理生成的类调用的方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是媒婆");
        method.invoke(person,null);
        System.out.println("我去帮你找");
        return null;
    }
}

通过调试看到报道查看对象不是个人类,而是一个经过处理后的类,让我们看看内部做了什么工作。

通过ProxyGenerator生成人的代理类,并且输出到本地磁盘上。

        //生成通过代理生成器生成代理class
        byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{person.getClass()});
        //输出到本地磁盘上
        FileOutputStream fileOutputStream=new FileOutputStream("$Proxy0.class");
        fileOutputStream.write($Proxy0s);
        fileOutputStream.close();

通过反编译工具查看生成的代理类(代理类的内容删减过,避免内容过长),可​​以看出我们调用的是自定义实现的InvocationHander类的调用方法。

可以看出我们调用的结婚方法也是经过代理类改写的方法。

public final class $Proxy0 extends Proxy implements PersonImp {
    //反射方法
    private static Method m3;
    
    //实例化代理类时,把InvocationHander装入
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }


    //生成的方法,执行的是InvocationHander的invoke方法
    public final void marry() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //初始化方法,初始化方法类
    static {
        try {
            m3 = Class.forName("com.chumo.marry.PersonImp").getMethod("marry");

        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

了解了这些后我们开始手写动态代理的实现原理

2,手写动态代理

1,自定义的的的InvocationHandler的接口

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

2,实现的的的InvocationHandler的接口

public class CMInvocationHandlerImpl implements CMInvocationHandler {
    private Person person;
    //获取代理类对象
    public Object getInstance(Person person) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        this.person=person;
        Class<? extends Person> aClass = person.getClass();
        System.out.println("被代理的对象是"+aClass);
        //自定义的代理类的java和class的生成、装载到JVM中,并且实例化返回给调用者
        return CMPorxy.newProxyInstance(new CMClassLoader(),aClass.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是媒婆");
        method.invoke(person,null);
        System.out.println("我帮你找媳妇");
        return null;
    }
}

3,自定义代理类,实现的方法的的的newProxyInstance(请注意gengerateSrc方法,这是生成代理类的的java的的文件的核心点

/**
 * 生成代理对象的代码
 */
public class CMPorxy {
    private static String ln="\r\n";
    public static Object newProxyInstance(CMClassLoader loader,
                                          Class<?>[] interfaces,
                                          CMInvocationHandler h)
            throws IllegalArgumentException, IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1、生成源代码
        String proxySrc=gengerateSrc(interfaces);
        //2、将生成的源代码输出到磁盘,保存为.java文件
        String fileNmae=CMPorxy.class.getResource("").getPath();
        File f=new File(fileNmae+"$Proxy0.java");
        try {
            FileWriter fw=new FileWriter(f);
            fw.write(proxySrc);
            fw.flush();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3、编译源代码,并生成.class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> objects = manager.getJavaFileObjects(f);

        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, objects);
        task.call();
        manager.close();
        //4、将.class文件中的内容,动态加载到JVM中来
        Class<?> $Proxy0 = loader.findClass("$Proxy0");//获得类
        //5、返回被代理的对象
        Constructor<?> constructor = $Proxy0.getConstructor(CMInvocationHandler.class);//获取构造方法
        f.delete();//删除java文件
        return constructor.newInstance(h);//实例化
    }
    //生成java文件,这是关键点,通过这里可以看出如何创建一个代理类
    private static String gengerateSrc(Class<?>[] interfaces){
        StringBuffer src=new StringBuffer();
        src.append("package com.chumo.proxy;"+ln);
        src.append("import java.lang.reflect.InvocationHandler;\n" +ln+
                "import java.lang.reflect.Method;\n" +ln+
                "import java.lang.reflect.Proxy;\n" +ln+
                "import java.lang.reflect.UndeclaredThrowableException;"+ln);
        src.append("public final class $Proxy0  implements "+interfaces[0].getName()+" {"+ln);

        src.append("CMInvocationHandler h;"+ln);

        src.append("public $Proxy0(CMInvocationHandler h){"+ln);
        src.append("this.h=h;"+ln);
        src.append("}"+ln);

        for (Method method : interfaces[0].getMethods()) {
            src.append("public "+method.getReturnType().getName()+" "+method.getName()+" () throws Throwable{"+ln);
            src.append("Method m="+"Class.forName(\""+interfaces[0].getName()+"\").getMethod(\""+method.getName()+"\",new Class[]{});"+ln);
            src.append("this.h.invoke(this,m,null);"+ln);
            src.append("}"+ln);
        }
        src.append("}");
        return src.toString();
    }
}

如图4所示,我们需要使用自定义的类加载器,加载自定义路径的类文件装入到JVM中。

/**
 * 代码生成、编译、重新load到JVM中
 */
public class CMClassLoader extends ClassLoader{
    private File baseDir;
    public CMClassLoader() {
        //获取当前文件的所在路径
        String path = CMClassLoader.class.getResource("").getPath();
        this.baseDir=new File(path);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className=CMClassLoader.class.getPackage().getName()+"."+name;
        if (baseDir!=null){
            //获取class文件,使用类加载器加载到JVM
            File classFile=new File(baseDir,name.replace("\\.","/")+".class");
            if (classFile.exists()){
                try (FileInputStream fileInputStream = new FileInputStream(classFile);ByteArrayOutputStream outputStream=new ByteArrayOutputStream();) {
                    byte[] bytes=new byte[1024];
                    int len;
                    while ((len=fileInputStream.read(bytes))!=-1){
                        outputStream.write(bytes,0,len);
                    }
                    //装入class
                    return defineClass(className,outputStream.toByteArray(),0,outputStream.size());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    classFile.delete();//删除class文件
                }

            }
        }
        return super.findClass(name);
    }
}

5,运行,成功

(PS:如果代码不能运行,请检查路径,包名是否正确,以及代理的的java的的文件和类文件生成的位置,已经类加载器类加载器是否能读取类文件)

3,总结

通过以上代码得出,JAVA动态代理的原理,通过遍历被代理类的所有方法,调用调用的引用方法调用执行。

也就是说我们使用的人类的结婚方法其实是被重写过的。

并且代理类的的的的的Java和类文件的生成使用结束后,都会删除,保证了程序员的无感知的使用。

总结:1,JDK动态代理实现了类的增强,但是通过原理可以看出,过多的使用动态代理,效率是不高的。

           2,JDK动态代理是通过接口实现的,而出名的春天中的cjlib不需要接口的。

下节内容:手写cjlib动态代理。

 

附录:源码下载位置github

https://github.com/mingchangkun/hero

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值