• JDK动态代理


     

    静态代理是通过在代码中显式编码定义一个业务实现类的代理类,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;

    JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;

    CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

    静态代理在编译时产生class字节码文件,可以直接使用,效率高。动态代理必须实现InvocationHandler接口,通过invoke调用被委托类接口方法是通过反射方式,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。 cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。

    此篇只介绍jdk的动态代理,而不会说cglib代理。

    JDK动态代理的代理类字节码在创建时,需要实现业务实现类所实现的接口作为参数。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。(JDK动态代理重要特点是代理接口) 并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

    动态代理只能对接口产生代理,不能对类产生代理。

    新建一个接口,一个类实现此接口:

    public interface IPeople {
        void eat();
    }
    public class Student implements IPeople{
        @Override
        public void eat() {
            System.out.println("Student eat");
        }
    }

    新建类,实现InvokeHandle接口:

    public class ProxyHandle implements InvocationHandler {
        // 此object是被代理对象
        private Object object;
        public ProxyHandle(Object object){
            this.object = object;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before");
            method.invoke(object,args);
            System.out.println("after");
            return null;
        }
    }

    新建测试类:

    public class Test {
        public static void main(String[] args) {
            IPeople iPeople = new Student();
            ProxyHandle p = new ProxyHandle(iPeople);
            IPeople proxy = (IPeople) Proxy.newProxyInstance(iPeople.getClass().getClassLoader(),
                    iPeople.getClass().getInterfaces(), p);
            proxy.eat();
        }
    }

    打印如下:

    before
    Student eat
    after

    源码

    我们通过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;
                        }
                    });
                }
                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);
            }
        }

    我们断点进入此方法,看看是哪一步产生代理对象:

     上面这一步产生了代理对象:

    private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            
        // 参数长度校验
         if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }

     进入get()方法:

     apply()的方法逻辑在Proxy类的内部类ProxyClassFactory中:

     ProxyGenerator.generateProxyClass()生成字节码:

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }
    
                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }
    
        return var4;

     generateClassFile()方法生成代理对象字节码,这里var0,var1,var2是

    proxyName, interfaces, accessFlags

     通过defineClass0()再解析字节码,生成代理对象。

  • 相关阅读:
    如何打日志才能方便排查问题?
    为什么 HashMap 并发时会引起死循环?
    Spring 为什么会有 FactoryBean?
    常用 Git 使用技巧,收藏了~
    Gin中context的使用
    Gin的路由算法
    k8s中的网络通信总结
    k8s架构
    Golang中的值拷贝与引用拷贝
    golang知识要点总结
  • 原文地址:https://www.cnblogs.com/SunSAS/p/12318677.html
Copyright © 2020-2023  润新知