zoukankan      html  css  js  c++  java
  • 动态代理总结

    本文转载自javassist 实现动态代理

    导语

    动态代理是指在运行时动态生成代理类。
    常见的动态代理生成方式有:

    • JDK动态代理
    • Apache BCEL (Byte Code Engineering Library):是Java classworking广泛使用的一种框架,它可以深入到JVM汇编语言进行类操作的细节。
    • ObjectWeb ASM:是一个Java字节码操作框架。它可以用于直接以二进制形式动态生成stub根类或其他代理类,或者在加载时动态修改类。
    • Cglib(Code Generation Library):是一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。
    • Javassist:是Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。
    • ByteBuddy

    JDK动态代理

    JDK动态代理用法

    Java的动态代理技术很强大,能够在运行时动态生成接口的,具体用法如下:

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
         new Class[] { Interface.class }, 
         handler );
    

    JDK动态代理机制优缺点

    首先是动态生成的代理类本身的一些特点:

    • 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
    • 类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
    • 类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
    • 类继承关系:该类的继承关系如图:

    img

    由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

    JDK动态代理总结

    JDK动态代理仅支持 interface 代理的桎梏,因为动态生成的代理类的继承自 Proxy,Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

    Javassist动态代理

    javassist 是一款非常优秀的Java 字节码引擎工具,能够在运行时编译、生成Java Class。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

    maven依赖:

    <dependency>
    	<groupId>org.javassist</groupId>
    	<artifactId>javassist</artifactId>
    	<version>3.21.0-GA</version>
    </dependency>
    

    ProxyFactory方式

    通过javassist.util.proxy.ProxyFactory类来生成代理,代码如下:

    public Object getProxy(Class<?> type) throws IllegalAccessException, InstantiationException {
            ProxyFactory f = new ProxyFactory();
            f.setSuperclass(type);
            f.setFilter(new MethodFilter() {
                public boolean isHandled(Method m) {
                    // ignore finalize()
                    return !m.getName().equals("finalize");
                }
            });
            Class c = f.createClass();
            MethodHandler mi = new MethodHandler() {
                public Object invoke(Object self, Method m, Method proceed,
                                     Object[] args) throws Throwable {
                    System.out.println("method name: " + m.getName()+" exec");
                    return proceed.invoke(self, args);  // execute the original method.
                }
            };
            Object proxy = c.newInstance();
            ((Proxy)proxy).setHandler(mi);
            return proxy;
        }
    

    编辑源码生成字节码方式

    采用和Dubbo 框架中 com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory似的方式,首先定义ProxyFactory:

    public interface ProxyFactory {
        <T> T getProxy(Object target, InvocationHandler handler) throws Throwable;
    }
    

    com.bytebeats.codelab.javassist.proxy.javassist.JavassistProxyFactory 代码如下:

    import com.bytebeats.codelab.javassist.proxy.ProxyFactory;
    import java.lang.reflect.InvocationHandler;
    
    /**
     * ${DESCRIPTION}
     *
     * @author Ricky Fung
     * @date 2017-02-20 14:55
     */
    public class JavassistProxyFactory implements ProxyFactory {
    
        @Override
        public <T> T getProxy(Object target, InvocationHandler handler) throws Throwable {
            return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    target.getClass(), handler);
        }
    }
    

    最关键的 ProxyGenerator 代码如下:

    package com.bytebeats.codelab.javassist.proxy.javassist;
    
    import com.bytebeats.codelab.javassist.util.ClassUtils;
    import javassist.*;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * ${DESCRIPTION}
     *
     * @author Ricky Fung
     * @date 2017-03-18 14:55
     */
    public class ProxyGenerator {
    
        private static final AtomicInteger counter = new AtomicInteger(1);
    
        private static ConcurrentHashMap<Class<?>, Object> proxyInstanceCache = new ConcurrentHashMap<>();
    
        public static Object newProxyInstance(ClassLoader classLoader, Class<?> targetClass, InvocationHandler invocationHandler)
                throws Exception {
    
            if(proxyInstanceCache.containsKey(targetClass)){
                return proxyInstanceCache.get(targetClass);
            }
            ClassPool pool = ClassPool.getDefault();
            //生成代理类的全限定名
            String qualifiedName = generateClassName(targetClass);
            // 创建代理类
            CtClass proxy = pool.makeClass(qualifiedName);
            //接口方法列表
            CtField mf = CtField.make("public static java.lang.reflect.Method[] methods;", proxy);
            proxy.addField(mf);
            CtField hf = CtField.make("private " + InvocationHandler.class.getName() + " handler;", proxy);
            proxy.addField(hf);
            CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get(InvocationHandler.class.getName())}, proxy);
            constructor.setBody("this.handler=$1;");
            constructor.setModifiers(Modifier.PUBLIC);
            proxy.addConstructor(constructor);
            proxy.addConstructor(CtNewConstructor.defaultConstructor(proxy));
            // 获取被代理类的所有接口
            List<Class<?>> interfaces = ClassUtils.getAllInterfaces(targetClass);
            List<Method> methods = new ArrayList<>();
            for (Class cls : interfaces) {
                CtClass ctClass = pool.get(cls.getName());
                proxy.addInterface(ctClass);
                Method[] arr = cls.getDeclaredMethods();
                for (Method method : arr) {
                    int ix = methods.size();
                    Class<?> rt = method.getReturnType();
                    Class<?>[] pts = method.getParameterTypes();
                    StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
                    for(int j=0;j<pts.length;j++) {
                        code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
                    }
                    code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
                    if(!Void.TYPE.equals(rt) )
                        code.append(" return ").append(asArgument(rt, "ret")).append(";");
    
                    StringBuilder sb = new StringBuilder(1024);
                    sb.append(modifier(method.getModifiers())).append(' ').append(getParameterType(rt)).append(' ').append(method.getName());
                    sb.append('(');
                    for(int i=0;i<pts.length;i++)
                    {
                        if( i > 0 )
                            sb.append(',');
                        sb.append(getParameterType(pts[i]));
                        sb.append(" arg").append(i);
                    }
                    sb.append(')');
    
                    Class<?>[] ets = method.getExceptionTypes();    //方法抛出异常
                    if( ets != null && ets.length > 0 )
                    {
                        sb.append(" throws ");
                        for(int i=0;i<ets.length;i++)
                        {
                            if( i > 0 )
                                sb.append(',');
                            sb.append(getParameterType(ets[i]));
                        }
                    }
                    sb.append('{').append(code.toString()).append('}');
                    CtMethod ctMethod = CtMethod.make(sb.toString(), proxy);
                    proxy.addMethod(ctMethod);
                    methods.add(method);
                }
            }
    
            proxy.setModifiers(Modifier.PUBLIC);
            Class<?> proxyClass = proxy.toClass(classLoader, null);
            proxyClass.getField("methods").set(null, methods.toArray(new Method[0]));
            Object instance = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
            Object old = proxyInstanceCache.putIfAbsent(targetClass, instance);
            if(old!=null){
                instance = old;
            }
            return instance;
        }
    
        private static String modifier(int mod) {
            if( Modifier.isPublic(mod) ) return "public";
            if( Modifier.isProtected(mod) ) return "protected";
            if( Modifier.isPrivate(mod) ) return "private";
            return "";
        }
    
        /**
         * 数组类型返回 String[]
         * @param c
         * @return
         */
        public static String getParameterType(Class<?> c) {
            if(c.isArray()) {   //数组类型
                StringBuilder sb = new StringBuilder();
                do {
                    sb.append("[]");
                    c = c.getComponentType();
                } while( c.isArray() );
    
                return c.getName() + sb.toString();
            }
            return c.getName();
        }
    
        private static String asArgument(Class<?> cl, String name) {
            if( cl.isPrimitive() ) {
                if( Boolean.TYPE == cl )
                    return name + "==null?false:((Boolean)" + name + ").booleanValue()";
                if( Byte.TYPE == cl )
                    return name + "==null?(byte)0:((Byte)" + name + ").byteValue()";
                if( Character.TYPE == cl )
                    return name + "==null?(char)0:((Character)" + name + ").charValue()";
                if( Double.TYPE == cl )
                    return name + "==null?(double)0:((Double)" + name + ").doubleValue()";
                if( Float.TYPE == cl )
                    return name + "==null?(float)0:((Float)" + name + ").floatValue()";
                if( Integer.TYPE == cl )
                    return name + "==null?(int)0:((Integer)" + name + ").intValue()";
                if( Long.TYPE == cl )
                    return name + "==null?(long)0:((Long)" + name + ").longValue()";
                if( Short.TYPE == cl )
                    return name + "==null?(short)0:((Short)" + name + ").shortValue()";
                throw new RuntimeException(name+" is unknown primitive type.");
            }
            return "(" + getParameterType(cl) + ")"+name;
        }
        private static String generateClassName(Class<?> type){
            return String.format("%s$Proxy%d", type.getName(), counter.getAndIncrement());
        }
    }
    

    案例

    被代理的接口:

    import com.bytebeats.codelab.javassist.model.User;
    
    import java.util.List;
    
    /**
     * ${DESCRIPTION}
     *
     * @author Ricky Fung
     * @date 2017-02-20 15:16
     */
    public interface HelloService {
    
        void say(String msg);
    
        String echo(String msg);
    
        String[] getHobbies();
    
        int insert(User user);
    
        User getUser();
    
        List<User> getUser(String group, int age);
    }
    

    客户端代码:

        final HelloService target = new HelloServiceImpl();
        ProxyFactory factory = factory = new JavassistProxyFactory();
    
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                //打印日志
                System.out.println("[before] The method " + methodName + " begins");
                Object result = null;
                try{
                    //前置通知
                    result = method.invoke(target, args);
                    //返回通知, 可以访问到方法的返回值
                    System.out.println(String.format("after method:%s execute", method.getName()));
                } catch (Exception e){
                    e.printStackTrace();
                    //异常通知, 可以访问到方法出现的异常
                }
                //后置通知. 因为方法可以能会出异常, 所以访问不到方法的返回值
                //打印日志
                System.out.println("[after] The method ends with " + result);
                return result;
            }
        };
    
        HelloService proxy = factory.getProxy(target, handler);
        System.out.println(proxy);
    
        proxy.say("ricky");
        proxy.echo("world");
        proxy.getHobbies();
        proxy.insert(new User());
        proxy.getUser();
        proxy.getUser("A", 23);
    

    参考资料

    Javassist实现JDK动态代理
    动态代理方案性能对比-梁飞

  • 相关阅读:
    Android6.0以后动态增加权限
    Failed to resolve: junit:junit:4.12
    tflite
    error: undefined reference to `cv::imread(std::string const&, int)'
    Makefile
    tf模型可视化工具
    linux c++下遍历文件
    mobilenetV3
    centos7安装mxnet
    chrome的一些插件
  • 原文地址:https://www.cnblogs.com/yungyu16/p/13162797.html
Copyright © 2011-2022 走看看