【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

动态代理

代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位。代理模式从类型上来说,可以分为静态代理和动态代理两种类型。

在解释动态代理之前我们先理解一下静态代理:

首先你要明白静态代理的作用

我们有一个字体提供类,有多种实现(从磁盘,从网络,从系统)

public interface FontProvider {
    Font getFont(String name);
}

public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new FontProviderFromDisk();
    }
}

public class Main() {
    public static void main(String[] args) {
        FontProvider fontProvider = ProviderFactory.getFontProvider();
        Font font = fontProvider.getFont("微软雅黑");
        ......
    }
}

现在我们希望给他加上一个缓存功能,我们可以用静态代理来完成

public class CachedFontProvider implements FontProvider {
    private FontProvider fontProvider;
    private Map<String, Font> cached;

    public CachedFontProvider(FontProvider fontProvider) {
        this.fontProvider = fontProvider;
    }

    public Font getFont(String name) {
        Font font = cached.get(name);
        if (font == null) {
            font = fontProvider.getFont(name);
            cached.put(name, font);
        }
        return font;
    }
}


/* 对工厂类进行相应修改,代码使用处不必进行任何修改。
   这也是面向接口编程以及工厂模式的一个好处 */
public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new CachedFontProvider(new FontProviderFromDisk());
    }
}

当然,我们直接修改FontProviderFromDisk类也可以实现目的,但是我们还有FontProviderFromNet, FontProviderFromSystem等多种实现类,一一修改太过繁琐且易出错。况且将来还可能添加日志,权限检查,异常处理等功能显然用代理类更好一点。

然而今天的重点是:我们都知道牛逼轰轰的Spring AOP的实现的一种方式是使用JDK的动态代理(另一种是cglib),大部分人也会用jdk的动态代理,不过没有研究过jdk的动态代理到底是怎么实现的。今天就来揭开他的神秘面纱;

1. 原理源码剖析

首先我们先来讲一下JDK动态代理的实现原理

1.拿到被代理对象的引用,然后获取他的接口
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
3.把被代理对象的引用拿到了
4.重新动态生成一个class字节码
5.然后编译

然后先实现一个动态代理,代码很简单了,就是实现
java.lang.reflect.InvocationHandler接口,并使用
java.lang.reflect.Proxy.newProxyInstance()方法生成代理对象

/**
 * @author mark
 * @date 2018/3/30
 */
public class JdkInvocationHandler implements InvocationHandler {

    private ProductService target;

    public Object getInstance(ProductService target){
        this.target = target;
        Class clazz = this.target.getClass();
        // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
        return Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("日期【"+currentDate + "】添加了一款产品");

        return method.invoke(this.target,args);
    }
}

被代理接口和实现

/**
 * 模仿产品Service
 * @author mark
 * @date 2018-03-30
 */
public interface ProductService {
    /**
     * 添加产品
     * @param productName
     */
    void addProduct(String productName);
}

/**
 * @author mark
 * @date 2018/3/30
 */
public class ProductServiceImpl implements ProductService{
    public void addProduct(String productName) {
        System.out.println("正在添加"+productName);
    }
}

测试类

public class Test {
    public static void main(String[] args) throws Exception {
        ProductService productService = new ProductServiceImpl();
        ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);
        proxy.addProduct("iphone");

        // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});

        FileOutputStream os = new FileOutputStream("Proxy0.class");
        os.write(bytes);
        os.close();
    }
}

结果输出

日期【2018-03-30】添加了一款产品
正在添加iphone

Process finished with exit code 0

上面我们实现动态动态代理的时候输出了代理类的字节码文件,现在来看一下字节码文件反编译过后的内容

import com.gwf.jdkproxy.ProductServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// 继承了Proxy类
public final class $Proxy0 extends Proxy implements ProductServiceImpl {
    private static Method m1;
    private static Method m8;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m4;
    private static Method m7;
    private static Method m9;
    private static Method m0;
    private static Method m6;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

....
....

/**
* 这里是代理类实现的被代理对象的接口的相同方法
*/
    public final void addProduct(String var1) throws  {
        try {
            // super.h 对应的是父类的h变量,他就是Proxy.nexInstance方法中的InvocationHandler参数
           // 所以这里实际上就是使用了我们自己写的InvocationHandler实现类的invoke方法
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

   

    public final Class getClass() throws  {
        try {
            return (Class)super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

....
....
// 在静态构造块中,代理类通过反射获取了被代理类的详细信息,比如各种方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));
            m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);
            m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");
            m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

补充一下上面代母注释中的super.h

protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

// 这个方法是Proxy的newProxyInstance方法,主要就是生成了上面的动态字节码文件
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
       
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
// 重点看这里,将我们传来的InvocationHandler参数穿给了构造函数
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

以上就是jdk动态代理的内部实现过程,最后再次将上面的原理声明一遍,强化记忆
1.拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (上面的反编译文件中实现了同样的接口)
3.把被代理对象的引用拿到了(上面被代理对象中在静态代码块中通过反射获取到的信息,以及我们实现的JdkInvocationHandler中的target)
4.重新动态生成一个class字节码
5.然后编译

2.自己手写一个动态代理

(声明:本代码只用作实例,很多细节没有考虑进去,比如,多接口的代理类,Object类的其他默认方法的代理,为确保原汁原味,一些模板引擎和commons工具类也没有使用;觉得不足的老铁们可以随意完善,记得评论区留言完善方法哦)

我们使用jdk代理的类名和方法名定义,已经执行思路,但是所有的实现都自己来写;

首先先定义出类结构

/**
 * 自定义类加载器
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyClassLoader extends ClassLoader {

    /**
     * 通过类名称加载类字节码文件到JVM中
     * @param name 类名
     * @return 类的Class独享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }
}
/**
 * @desc 自己实现的代理类,用来生成字节码文件,并动态加载到JVM中
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyProxy {
    /**
     * 生成代理对象
     * @param loader 类加载器,用于加载被代理类的类文件
     * @param interfaces 被代理类的接口
     * @param h 自定义的InvocationHandler接口,用于具体代理方法的执行
     * @return 返回被代理后的代理对象
     * @throws IllegalArgumentException
     */
    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException{
        /**
         * 1.生成代理类的源代码
         * 2.将生成的源代码输出到磁盘,保存为.java文件
         * 3.编译源代码,并生成.java文件
         * 4.将class文件中的内容,动态加载到JVM中
         * 5.返回被代理后的代理对象
         */

        return null;

    }
}
/**
 * 自定义类加载器
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyClassLoader extends ClassLoader {

    /**
     * 通过类名称加载类字节码文件到JVM中
     * @param name 类名
     * @return 类的Class独享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }
}
/**
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class CustomInvocationHandler implements MyInvocationHandler {
    private ProductService target;

    public Object getInstance(ProductService target){
        this.target = target;
        Class clazz = this.target.getClass();
        // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
        // 这里的MyClassLoader先用new的方式保证编译不报错,后面会修改
        return MyProxy.newProxyInstance(new MyClassLoader(),
                clazz.getInterfaces(),
                this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("日期【"+currentDate + "】添加了一款产品");

        return method.invoke(this.target,args);
    }
}

接下来我们来按照步骤一步一步的完善我们的类

生成代理类的源文件

/**
     * 生成代理类的源代码
     * @return
     */
    private static String genSesource(Class<?> interfaces){
        StringBuilder src = new StringBuilder();
        src.append("package com.gwf.custom;").append(ln)
                .append("import java.lang.reflect.Method;").append(ln)
                .append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln)
                .append("private MyInvocationHandler h;").append(ln)
                .append("public $Proxy0(MyInvocationHandler h){").append(ln)
                .append("this.h=h;").append(ln)
                .append("}").append(ln);

        for(Method method:interfaces.getMethods()){
            src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln)
                    .append("try {").append(ln)
                    .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln)
                    .append("this.h.invoke(this, m, new Object[]{});").append(ln)
                    .append("}catch (Throwable e){").append(ln)
                    .append("e.printStackTrace();").append(ln)
                    .append("}").append(ln)
                    .append("}").append(ln);
        }
        src.append("}");

        return src.toString();

    }

2.将源文件保存到本地

// 1.生成代理类的源代码
            String src = genSesource(interfaces);
            // 2.将生成的源代码输出到磁盘,保存为.java文件
            String path = MyProxy.class.getResource("").getPath();
            File file = new File(path+"$Proxy0.java");

            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.close();

3.编译源代码,并生成.java文件

// 3.编译源代码,并生成.java文件
            // 获取java编译器
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            // 标注java文件管理器,用来获取java字节码文件
            StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
            Iterable iterable = manager.getJavaFileObjects(file);

            // 创建task,通过java字节码文件将类信息加载到JVM中
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
            // 开始执行task
            task.call();
            // 关闭管理器
            manager.close();

4.将class文件中的内容,动态加载到JVM中

public class MyClassLoader extends ClassLoader {

    private String baseDir;

    public MyClassLoader(){
        this.baseDir = MyClassLoader.class.getResource("").getPath();
    }

    /**
     * 通过类名称加载类字节码文件到JVM中
     * @param name 类名
     * @return 类的Class独享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 获取类名
        String className = MyClassLoader.class.getPackage().getName()+"."+name;
        if(null == baseDir) {
            throw new ClassNotFoundException();
        }

        // 获取类文件
        File file = new File(baseDir,name+".class");
        if(!file.exists()){
            throw new ClassNotFoundException();
        }

        // 将类文件转换为字节数组
        try(
        FileInputStream in = new FileInputStream(file);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ){
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }

            // 调用父类方法生成class实例
            return defineClass(className,out.toByteArray(),0,out.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

5.返回被代理后的代理对象

Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            return c.newInstance(h);

最后看一下总体的MyProxy类 的 newProxyInstance方法

public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?> interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException{
        /**
         * 1.生成代理类的源代码
         * 2.将生成的源代码输出到磁盘,保存为.java文件
         * 3.编译源代码,并生成.java文件
         * 4.将class文件中的内容,动态加载到JVM中
         * 5.返回被代理后的代理对象
         */
        try {
            // 1.生成代理类的源代码
            String src = genSesource(interfaces);
            // 2.将生成的源代码输出到磁盘,保存为.java文件
            String path = MyProxy.class.getResource("").getPath();
            File file = new File(path+"$Proxy0.java");

            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.close();

            // 3.编译源代码,并生成.java文件
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
            Iterable iterable = manager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
            task.call();
            manager.close();

            // 4.将class文件中的内容,动态加载到JVM中
            Class proxyClass = loader.findClass("$Proxy0");

            // 5.返回被代理后的代理对象
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

激动人心的时刻:测试运行

public class CustomClient {
    public static void main(String[] args){
        ProductService productService = new ProductServiceImpl();
        ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);
        proxy.addProduct();
    }
}

运行结果

日期【2018-03-30】添加了一款产品
正在添加iphone

Process finished with exit code 0

总结:以上通过理解jdk动态代理的原理,自己手写了一个动态代理,里面涉及到的重点主要是代理类字节码的生成(这里采用通过反射强行生成源文件并编译的方法,其实应该可以直接生成字节码文件的,有兴趣的同学可以尝试)和将生成的类动态加载到JVM中(本次试验由于测试,比较简单,直接将类名硬编码到了系统里,正常应该是自动加载),虽然还不完善,但是对于理解原理应该是有很多帮助了,欢迎同学们评论区留言评论给出更好的建议

在互联网公司面试中,架构的底层一定是面试官会问问的问题,针对面试官一般会提到的问题,我录制了一些分布式,微服务,性能优化等技术点底层原理的录像视频,加群895244712可以免费获取这些录像,里面还有些分布式,微服务,性能优化,spring,MyBatis的等源码知识点的录像视频。

image

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 一、基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念。代理这个词本身并不是计...
    小李弹花阅读 16,427评论 2 40
  • title: Jdk动态代理原理解析 tags:代理 categories:笔记 date: 2017-06-14...
    行径行阅读 19,220评论 3 36
  • 教程: https://wangdoc.com/javascript/basic/index.html 笔记 1....
    智勇双全的小六阅读 136评论 0 0
  • 今天的主人公就是春秋时代楚国著名的贤君――楚庄王,其在位期间为公元前613年――前590年在位。 他少年即位,面临...
    日月新莫阅读 343评论 0 0