javassist 实现动态代理

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

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

JDK动态代理机制优缺点

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

1.png

由图可见,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>

方式1

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

方式2

采用和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);

Ending!

参考资料

dubbo com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory


javassist ProxyFactory

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,672评论 18 139
  • 原文: Dyanmic Proxy Classes 介绍 一个动态代理类是实现了多个接口存在于运行时的类,这样,一...
    半黑月缺阅读 943评论 0 0
  • 这篇博客主要介绍使用 InvocationHandler 这个接口来达到 hook 系统 service ,从而实...
    Shawn_Dut阅读 5,108评论 0 33
  • 明日夏至,友人午至,吃饭谈天。午休泡汤,15:30硬拉去家附近小竹签吃烤羊肉,五十串羊肉塞下肚,从开始的食...
    060宋丹阅读 310评论 0 1
  • 总有一些行动力特别强的人,她们既不做梦,也不瞎想,她们只是做而已。这句话致敬我的偶像——航母(宁波天航妈),我特别...
    童心杰阅读 349评论 2 6