从源码角度解读JDK动态代理

本文深入探讨了Java动态代理的概念,对比了静态代理和动态代理的区别,通过案例展示了动态代理的实现,并详细解析了JDK动态代理的源码。动态代理在运行时根据接口生成class文件,利用InvocationHandler处理接口方法调用,实现方法的增强或改变,避免了手动创建大量代理类的繁琐。

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

目录

什么是动态代理 

静态代理和动态代理区别

动态代理案例

源码解读

总结


什么是动态代理 

我们熟知的Javaee中大量框架使用到动态代理这个概念,那么动态代理到底是个什么玩意呢?

我们知道接口他是无法生成对象的,他只能被实现,而动态代理就是程序在运行中,通过一种方式使得接口生成一个代理的class文件,然后再通过class文件再生成代理类的对象。并且通过构造方法来传入InvocationHandler对象,而InvocationHandler对象通过用户自己重写并传入,具体的代理业务逻辑就可以在其中实现。InvocationHandler顾名思义就是用来处理接口代理对象的方法的回调。

静态代理和动态代理区别

静态代理一般是手动去创建一个代理类,代理类中维护一个接口的实现类,然后创建一个代理方法,将原有的实现类的方法在代理方法中执行,此时就可以在原有方法的前后做一些处理了。通俗易懂的话来说就是套一层就可以为所欲为了(手动滑稽),入下图所示。

这样就可以不改变原有代码的情况下增强或者改变。那要动态代理干嘛?

虽然这样确实可以实现,但是你去思考随着业务越来越复杂,当你接口的实现类越来越多你的代理类会越来越多,并且当接口的方法做出改变的时候,其他所有的代理类都需要做改变,极其难维护。而动态代理就不需要开发者再去手动创建代理类,JDK底层帮你通过接口的class文件,克隆出一个class文件,再通过底层维护的字节码生成器,生成一个代理的class文件,再通过class文件反射创建一个代理的对象。代理对象中需要传一个InvocationHandler。如下图所示。

 区别就在于JDK底层帮你根据接口生成了一个字节码文件,内部还维护了InvocationHandler对象,所以你接口有改变也没关系,JDK底层去帮你生成新的字节码文件,所以是一个可变动态的。

动态代理案例

概念都讲了一大堆了,这时候需要来个案例来证实一下概念。


/**
 * @author liha
 * @version 1.0
 * @date 2022/3/17 14:36
 */
public class JDKProxy {

    public static void main(String[] args) {

        UserDao dao = new UserDaoImpl();

        Class[] interfaces = {UserDao.class};

        /*
        * 也就是newProxyInstance这个静态方法返回的对象就是一个代理对象
        * */
        dao= (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new MyInvocationHandler(dao));


        /*
        * 他会回调代理对象的方法
        * */
        dao.update("1");

    }
}


class MyInvocationHandler implements InvocationHandler{

    Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        // 被增强方法之前
        System.out.println("这里是之前");
        
        //原来代码的执行
        Object res = method.invoke(object, args);
        System.out.println(res);

        //增强方法之后
        System.out.println("这里是之后");

        return res;
    }
}

interface UserDao{
    String update(String newData) ;
}
class UserDaoImpl implements UserDao{

    @Override
    public String update(String newData) {
        System.out.println("update被执行");
        return newData;
    }
}

 理论和实操介绍过后,就是源码证明了。

源码解读

我们直接看到newProxyInstance()方法创建代理对象的具体实现。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    // 克隆一份
    // 目的是不影响原有的Class
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    // 这里就是去获取缓存,如果缓存中没有就去创建字节码
    Class<?> cl = getProxyClass0(loader, intfs);

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

        // 获取到生成的代理类的构造方法,这里传入了InvocationHandler,所以是一个有参构造
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;

        // 一些修饰符的判断,如果为private,就通过一个标识符来控制代理
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }

        // 通过字节码然后反射生成代理对象
        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);
    }
}

具体步骤如下:

  1. 为了不影响原本接口的class文件,克隆出一个
  2. 一些必要的验证
  3. 得到代理的Class——查询缓存,缓存中没有就去创建,具体逻辑下面会详细讲解
  4. 从代理的Class中获取到有参构造方法(InvocationHandler)
  5. 反射(执行有参构造)得到代理对象

接着看到创建代理Class对象的过程。

里面大量使用到函数式接口(也就是一个接口回调),这里大家可以去学习一下,不然想自己追具体的逻辑不一定看得懂。

因为是查缓存,没有就创建,所以直接看到创建的流程,在Proxy中维护了一个WeakCache对象。

我们暂时不关心如何查缓存,更关心如何创建,所以跳过一些缓存的判断,直接看到创建的代码。

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 代理类的名字的前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 原子类,用来计数
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    // 回调方法
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        
        // 遍历所有的接口
        // 并且这个for循环其实也就是在检查一些内容,并没有来参与创建
        // 其实你会发现,这些源码啊,他们为了性能,判断过程都是分几批来判断,一层一层的筛选  
        for (Class<?> intf : interfaces) {
   
            Class<?> interfaceClass = null;
            try {
                // 获取到接口的Class
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }

            // 如果接口不一致
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
     
            // 如果接口不是接口
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
        
            // 判断接口是否重复
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        // 用来保存代理类的名字
        String proxyPkg = null;     // package to define proxy class in

        // 生成的代理类的修饰符
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

  
        // 再次遍历所有接口
        for (Class<?> intf : interfaces) {

            // 获取到接口的修饰符
            int flags = intf.getModifiers();

            // 如果修饰符不为public的话生成的代理类和接口同包
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        
        // 如果是public修饰的接口就拼接生成的代理接口的路径com.sun.proxy.
        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        // 生成名字的最后后缀就是全局计数器,从0开始
        // 所以最后代理名字为com.sun.proxy.$Proxy+计数器值
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;


        // ProxyGenerator做一个代理类的字节码生成
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {

            // 这是一个native方法,底层调JNI来对字节码做处理,返回一个代理的Class对象
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {

            throw new IllegalArgumentException(e.toString());
        }
    }
}

 具体为什么会走到这里来创建,大家可以自己追一下,并不难,就是有点点绕。

创建的过程:

  1. 做一些必要的判断逻辑
  2. 根据接口的修饰符创建出代理类的修饰符
  3. 根据接口的修饰符创建代理类的名字ReflectUtil.PROXY_PACKAGE + "."+"$Proxy"+索引
  4. 根据ProxyGenerator生成代理类的字节码,因为创建类,是需要字节码文件的。
  5. 根据defineClass0()JNI方法对字节码做操作并且返回代理的Class文件为后面的操作做铺垫

创建完代理的Class类后就是根据Class反射得到代理的实例对象

我们知道一个Class中包含了一个类的构造器、方法、字段。InvocationHandler需要我们来手动赋值,所以这里获取到代理类的有参构造方法,因为要通过有参构造方法将我们自定义的InvocationHandler 类给传到代理对象中,然后有参构造方法反射获取到代理类的实例对象并且返回实例对象。

后面通过代理对象调用原有的接口方法,就会走InvocationHandler接口中的invoke()方法执行。

有读者可能就会问你怎么知道代理类运行方法会走InvocationHandler接口中的invoke()方法?

这里逻辑推理一下,我们猜测代理类干了一些什么:

  1. 实现了UserDao接口
  2. 重写接口中的方法
  3. 实现了有参构造方法,要传一个InvocationHandler进去。

话不多说直接上代理类的字节码的反编译

public final class $proxy0 extends Proxy implements UserDao {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    // 需要传入的InvocationHandler对象,也就是我们重写的InvocationHandler对象
    public $proxy0(InvocationHandler var1) throws  {

        // 回调给Proxy类,如上图所示
        super(var1);
    }



    // 省略了hashcode、equals、toString方法


    // UserDao里面的方法进行重写。
    public final String update(String var1) throws  {
        try {

            // 调用InvocationHandler的invoke,这里就已经证明了代理类是走invoke方法了
            // 这里的m3也是static{}静态代码块中初始化完毕了
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }


    // 类被创建的时候就进行初始化工作
    // 这里也就是提前通过反射获取到class,再通过class获取到具体的Method
    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("com.liha.UserDao").getMethod("update", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这里也就证实了代理类运行方法是回调的InvocationHandler的invoke()方法。

Java查看动态代理生成的字节码文件https://blog.csdn.net/qq_43799161/article/details/123559050

总结

可能概念讲的没那么通俗易懂,更希望大家不是为了面试来背概念的,更希望大家是写个案例,然后追源码来证实天上飞的这些概念。下面是笔者Cglib的源码解读,有需要的可以跳转链接阅读!

从源码角度理解Cglib动态代理icon-default.png?t=M276https://blog.csdn.net/qq_43799161/article/details/123604338?spm=1001.2014.3001.5502

最后,有不懂的地方请评论区留言。如果本帖对读者有帮助,希望点赞+关注+收藏,您的支持是给我最大的动力,一直在努力的更新各种框架的使用和源码解读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值