Dubbo使用javassist生成动态类

Posted qiaozhuangshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo使用javassist生成动态类相关的知识,希望对你有一定的参考价值。

Dubbo使用javassist生成动态类

在服务(本地和远程)暴露的时候会调用proxyFactory.getInvoker方法

具体位置:

  • 本地暴露:ServiceConfig#exportLocal line:538
  • 远程暴露: ServiceConfig#doExportUrlsFor1Protocol line:512

会先调用AOP织入的类StubProxyFactoryWrapper#getInvoker

然后执行JavassistProxyFactory#getInvoker

JavassistProxyFactory#getInvoker如下

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) 
    // getWrapper会生成代理类
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    return new AbstractProxyInvoker<T>(proxy, type, url) 
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable 
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        
    ;

然后进入Wrapper#getWrapper--> Wrapper#makeWrapper, 具体代码就在这个makeWrapper方法里面

例如现在暴露的服务如下:

public interface TestService 

    String getData(String var1);

    List<String> getList();

那么生成的代理类如下:

public class Wrapper0 extends com.alibaba.dubbo.common.bytecode.Wrapper 

    /**
     * 属性名, 属性类型
     */
    public static java.util.Map pts = new HashMap<String, Class<?>>();
    public static String[] pns = new String[0];

    /**
     * 所有的方法名
     */
    public static String[] mns = "getData";
    /**
     * 本类中的所有方法名
     */
    public static String[] dmns = "getData";

    /**
     * 一个方法中所有的参数类型  mts[n]属性的个数和方法的个数形同
     */
    public static Class[] mts0 = String.class;

    public static Class[] mts1 = List.class;

    @Override
    public String[] getPropertyNames() 
        return pns;
    

    @Override
    public boolean hasProperty(String n) 
        return pts.containsKey(n);
    

    @Override
    public Class getPropertyType(String n) 
        return (Class) pts.get(n);
    

    @Override
    public String[] getMethodNames() 
        return mns;
    

    @Override
    public String[] getDeclaredMethodNames() 
        return dmns;
    

    @Override
    public void setPropertyValue(Object o, String n, Object v) 
        per.qiao.service.TestService w;
        try 
            w = ((per.qiao.service.TestService) o);
         catch (Throwable e) 
            throw new IllegalArgumentException(e);
        
        throw new com.alibaba.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + n + "\" filed or setter method in class per.qiao.service.TestService.");
    

    @Override
    public Object getPropertyValue(Object o, String n) 
        per.qiao.service.TestService w;
        try 
            w = ((per.qiao.service.TestService) o);
         catch (Throwable e) 
            throw new IllegalArgumentException(e);
        
        if (n.equals("list")) 
            return w.getList();
        
        throw new com.alibaba.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + n + "\" filed or setter method in class per.qiao.service.TestService.");
    
    
    /**
     *  在调用接口时,就时调用的这个方法
        @param o 接口实例
        @param n 方法名
        @param p 参数类型
        @param v 参数
     */
    @Override
    public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException 
        per.qiao.service.TestService w;
        try 
            w = ((per.qiao.service.TestService) o);
         catch (Throwable e) 
            throw new IllegalArgumentException(e);
        
        try 
            //这个try范围内就是你所需要暴露的所有方法
            if ("getData".equals(n) && p.length == 1) 
                return w.getData((java.lang.String) v[0]);
            
            if ("getList".equals(n) && p.length == 0) 
                return w.getList();
            
         catch (Throwable e) 
            throw new java.lang.reflect.InvocationTargetException(e);
        
        throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + n + "\" in class per.qiao.service.TestService.");
    

javassist生成动态代理类的示例

public class CompilerByJavassist 

    public static void main(String[] args) throws Exception 
        // ClassPool:CtClass对象的容器
        ClassPool pool = ClassPool.getDefault();

        // 通过ClassPool生成一个public新类Emp.java
        CtClass ctClass = pool.makeClass("per.qiao.javassist.Qiao");

        // 添加属性 private String name
        CtField nameFild = new CtField(pool.getCtClass("java.lang.String"), "name", ctClass);
        nameFild.setModifiers(Modifier.PRIVATE);
        ctClass.addField(nameFild);

        // 其次添加熟悉privtae int age
        CtField ageField = new CtField(pool.getCtClass("int"), "age", ctClass);
        ageField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ageField);

        // 为属性name和age添加getXXX和setXXX方法
        ctClass.addMethod(CtNewMethod.getter("getName", nameFild));
        ctClass.addMethod(CtNewMethod.setter("setName", nameFild));
        ctClass.addMethod(CtNewMethod.getter("getAge", ageField));
        ctClass.addMethod(CtNewMethod.setter("setAge", ageField));

        // 添加构造函数
        CtConstructor ctConstructor = new CtConstructor(new CtClass[] , ctClass);
        // 为构造函数设置函数体
        StringBuffer buffer = new StringBuffer();
        buffer.append("\n").append("name=\"qiaogege\";\n").append("age=25;\n");
        ctConstructor.setBody(buffer.toString());
        // 把构造函数添加到新的类中
        ctClass.addConstructor(ctConstructor);


        // 添加自定义方法  public void printInfo ...
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printInfo", new CtClass[] , ctClass);
        // 为自定义方法设置修饰符
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 为自定义方法设置函数体
        StringBuffer buffer2 = new StringBuffer();
        buffer2.append("\nSystem.out.println(\"begin!\");\n")
                .append("System.out.println(name);\n")
                .append("System.out.println(age);\n")
                .append("System.out.println(\"over!\");\n").append("");
        ctMethod.setBody(buffer2.toString());
        ctClass.addMethod(ctMethod);


        //最好生成一个class
        Class<?> clazz = ctClass.toClass();
        Object obj = clazz.newInstance();
        //ctClass.debugWriteFile("E://Qiao.class");

        //反射 执行方法
        obj.getClass().getMethod("printInfo", new Class[] )
                .invoke(obj, new Object[] );

        ctClass.debugWriteFile("E://Emp.class");
        // 把生成的class文件写入文件
        byte[] byteArr = ctClass.toBytecode();
        FileOutputStream fos = new FileOutputStream(new File("E://Qiao.class"));
        fos.write(byteArr);
        fos.close();
    

生成的Class文件放入IDEA中反编译后的结果如下

public class Qiao 
    private String name = "qiaogege";
    private int age = 25;

    public String getName() 
        return this.name;
    

    public void setName(String var1) 
        this.name = var1;
    

    public int getAge() 
        return this.age;
    

    public void setAge(int var1) 
        this.age = var1;
    

    public Qiao() 
    

    public void printInfo() 
        System.out.println("begin!");
        System.out.println(this.name);
        System.out.println(this.age);
        System.out.println("over!");
    

小结:

1. Dubbo通过javassist动态生成一个代理类对象,该对象不同于普通的javassist生成的对象,而是只记录了暴露接口中的方法的相关参数,生成一个Wrapper类型的对象,并保存在WRAPPER_MAP中,通过invokeMethod方法来执行相应的方法
2. 再将生成的Wrapper对象包装在AbstractProxyInvoker中进行服务暴露

以上是关于Dubbo使用javassist生成动态类的主要内容,如果未能解决你的问题,请参考以下文章

Java动态字节技术之Javassist

Dubbo实践代理

5.Dubbo原理解析-代理之Javassist字节码技术生成代理 (转)

使用 Javassist 向运行时生成的方法/类添加注释

Javassist | 字节码增强技术

Java逆向基础之动态生成类