ODL中使用YANG-UTIL带来类无法加载BUG

ODL基于的MDSL,其模型用YAGN语言进行定义,编程则使用yang-toolst生成的类,而产生的数据,即存储在ODL的DataStore中的数据需要经过序列化和反序列化。而序列化反序列化代码是通过Javassist动态生成的。

BindingToNormalizedNodecodec.java

    public <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(InstanceIdentifier<T> path, T data) {
        return this.codecRegistry.toNormalizedNode(path, data);
    }

通过上图可以看到其是将借助YANG工具生成的模型代码,以该代码构建的实例,进行序列化成通用的NormailizedNode结构。该工具存在在Cache中

private final LoadingCache<Class<? extends DataObject>, DataObjectSerializer> serializers;
this.serializers = CacheBuilder.newBuilder().weakKeys().build(new GeneratorLoader());

项目中关于YANG文件定义各种各样,不可能将每种结构都提交定义好Serializer,所以需要根据类型动态生成对应的Serializer

AbstractStreamWriterGenerator#load

            Class<? extends DataObjectSerializerImplementation> cls;
            try {
                cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
                        .loadClass(type.getClassLoader(), serializerName);
            } catch (final ClassNotFoundException e) {
                cls = generateSerializer(type, serializerName);
            }

生成代码中参考generateSerializer

类型为YangProtectRestoreParameter,最终返回的类为YangProtectRestoreParameter$StreamWriter(动态生成)

生成的入口为AbstractStreamWriterGenerator#generateEmitter0

private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source, final String serializerName) {
        final CtClass product;

        /*
         * getSerializerBody() has side effects, such as loading classes and codecs, it should be run in model class
         * loader in order to correctly reference load child classes.
         *
         * Furthermore the fact that getSerializedBody() can trigger other code generation to happen, we need to take
         * care of this before calling instantiatePrototype(), as that will call our customizer with the lock held,
         * hence any code generation will end up being blocked on the javassist lock.
         */
        final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(), new Supplier<String>() {
                @Override
                public String get() {
                    return source.getSerializerBody().toString();
                }
            }
        );

        try {
            product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName, new ClassCustomizer() {
                @Override
                public void customizeClass(final CtClass cls) throws CannotCompileException, NotFoundException {
                    // Generate any static fields
                    for (final StaticConstantDefinition def : source.getStaticConstants()) {
                        final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
                        field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
                        cls.addField(field);
                    }

                    // Replace serialize() -- may reference static fields
                    final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
                    serializeTo.setBody(body);

                    // The prototype is not visible, so we need to take care of that
                    cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
                }
            });
        } catch (final NotFoundException e) {
            LOG.error("Failed to instatiate serializer {}", source, e);
            throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
        }
        return product;
    }

上面代码使用到了Javassist

Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

下面通过测试用例复现该Bug

首先了解到我们定义的Forwarding明细,注意其还在一个以小写forwarding命令的包,目录结构如下:

测试用例代码如下:

package com.zte.sunquan.demo.cache;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import org.junit.Test;

/**
 * Created by sunquan on 2018/8/21.
 */
public class JavaAssistTestBug {

    @Test
    public void testBug() throws Exception {
        //动态创建一个类
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.zte.sunquan.demo.User");
        //创建属性
        CtField field03 = CtField.make("private org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.forwarding.A a;", cc);
        CtField field04 = CtField.make("private org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.Forwarding b;", cc);
        cc.addField(field03);
        cc.addField(field04);

        //创建方法
//        CtMethod method01 = CtMethod.make("public org.opendaylight.yang.gen.v1.cx.zx.xx.model.cim.configuration.rev170216.forwarding.A getName(){return name;}", cc);
//        CtMethod method02 = CtMethod.make("public void setName(String name){this.name = name;}", cc);
//        cc.addMethod(method01);
//        cc.addMethod(method02);
        //添加有参构造器
        CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, cc);
        constructor.setBody("{this.b=new org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.forwarding();}");
        cc.addConstructor(constructor);
        //无参构造器
//        CtConstructor cons = new CtConstructor(null, cc);
//        cons.setBody("{}");
//        cc.addConstructor(cons);

        cc.writeFile("E:/workspace/TestCompiler/src");
    }
}

代码逻辑很简单,通过Javassist进行动态创建一个类User,同时定义两个属性一个构造函数,构造函数中初始化b,但注意的是其使用的是小写的forwarding,实际上是不存在小写的forwarding类的。

代码执行抛出异常:

java.lang.RuntimeException: cannot find org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.forwarding: org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.Forwarding found in org/opendaylight/yang/gen/v1/cx/zx/xxx/model/cim/configuration/rev170216/forwarding.class
 

异常信息表示无法找到forwarding.class这个类

仔细观察调用栈,可以发现:

真实的异常抛出是由于cf.getName()与qualifiedName不一致导致的

为何qualifiedName会是小写的forwarding,从代码逻辑判断出,如果执行到这,实际上forwarding.class是已经能够在classPool中找到的。验证:

代码走到了return new CtClassType,所以find(classname)返回并不为空,但classname实际上就是:

org.opendaylight.yang.gen.v1.cx.zx.xxx.model.cim.configuration.rev170216.forwarding

那即从AppClassLoader中居然找到了forwarding.class这个类?这明明是一个包,不可能是类,太诡异了!

再次查看目录,classPath中明明只有Forwarding.class这个类

 

是时候揭露真相了,即在window系统中,AppClassLoader在find类时并不区别大小写,所以其找到的实际上就是Forwarding.class,如果此时其就使用获取到的句柄执行操作,那其实没有任何问题。

但Javassist的会进一步判断类名是否一致,你传进去的是forwarding,而根据JAVA开发习惯,类名只能以大写字母开关,所以Javassist会进行一步转换将首字母大写(ClassFile构造函数中)

thisclassname = constPool.getClassInfo(thisClass);

所以出现了不一致,导致异常抛出。

 

修改:

1、不要命名类名与包名一致

2、Assisti源码修改,判断究竟是类还是路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值