2014-11-26 3 views
0

В настоящее время я разрабатываю пользовательскую структуру ORM и использую ASM для динамического создания подклассов во время выполнения. Процесс генерации, похоже, завершен ОК, однако, когда я пытаюсь создать экземпляр полученного класса, я получаю «NoClassDefFoundError».Динамическое создание подкласса во время выполнения

Ошибка, похоже, относится к классу Super, а не к фактическому подклассу. Вот выдержка из метода генерации подкласса:

private Class generateProxyClass(Class anEntitySuperClass, 
           List<Field> fieldsToIntercept) throws ClassNotFoundException{ 
    String entitySuperClassName = this.convertToInternalName(anEntitySuperClass.getName()); 
    //String entityProxySubClassName = "com/flux/dynamic/".concat(anEntitySuperClass.getSimpleName()).concat("Proxy"); 
    String entityProxySubClassName = anEntitySuperClass.getSimpleName().concat("Proxy"); 

    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 
    cw.visit(V1_6,ACC_PUBLIC+ACC_SUPER,entityProxySubClassName,null,entitySuperClassName,null); 
    cw.visitSource(entityProxySubClassName.concat(".java"),null); 

    //create constructor 
    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"<init>","()V",null,null); 
    mv.visitCode(); 
    //have our consturctor initailise its super class. 
    mv.visitVarInsn(ALOAD,0); 
    mv.visitMethodInsn(INVOKESPECIAL,entitySuperClassName,"<init>","()V"); 
    mv.visitInsn(RETURN); 
    mv.visitMaxs(0,0); 
    mv.visitEnd(); 

    this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw); 
    cw.visitEnd(); 
    //at this point our class should be fully generated an include all required fields. next we 
    //convert the class to a byte array and pass it in to our helper method to load an 
    //actual class from the byte array. 
    return this.loadProxyClass(cw.toByteArray(),entityProxySubClassName); 
} 

Метод «loadProxyClass» назвал выше вспомогательный метод, который в основном конкретизирует и вызывает пользовательский ClassLoader для того, чтобы загрузить динамически созданный класс:

/**loads the generated proxy class from the provided bytes. */ 
private Class loadProxyClass(byte[] aGeneratedProxyClass,String proxyClassName) throws ClassNotFoundException{ 
    return new ProxyClassLoader(Thread.currentThread().getContextClassLoader(),aGeneratedProxyClass) 
       .loadClass(this.convertToExternalName(proxyClassName)); 
} 

ProxyClassLoader просто расширяет ClassLoader и переопределяет метод «findClass» для того, чтобы загрузить динамически генерируемый класс байт:

public class ProxyClassLoader extends ClassLoader { 

    private byte[] rawClassBytes; 

    public ProxyClassLoader(ClassLoader parentClassLoader,byte[] classBytes){ 
    super(parentClassLoader); 
    this.rawClassBytes = classBytes; 
    } 

    @Override 
    public Class findClass(String name) { 
    return defineClass(name,this.rawClassBytes, 0,this.rawClassBytes.length); 
    } 
} 

Ошибки я получаю: Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)

DummyEntity Где это супер класс я прохожу в метод generateProxyClass и DummyEntityProxy класс я пытаюсь создать. Я в тупик, любая помощь будет очень признательна.

ответ

0

Благодарим всех вас за ваши предложения. После многих часов возиться мне удалось решить эту ошибку. Оказывается, что ошибка была приписана к методу:

this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw); 

Более конкретно, некоторые из кода, генерируемого этим методом неправильно ссылки суперкласс его простым именем, а не его внутреннее полное имя класса. Я пропустил реализацию этого метода из моего вопроса для краткости, а также потому, что я искренне не ожидал, что проблема связана с этим методом. Как правило, при возникновении ошибок в динамически генерируемой логике байтового кода очень сложно определить причину, просто потому, что сообщения об ошибках JVM настолько неоднозначны.

+0

Только для дальнейших ссылок: с Java 8 сообщения об ошибках верификатора стали более читаемыми. –

2

Проблемы раскрывается сообщением вашего исключения в:

Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)

загрузчика классов Вашего ожидаемого загрузить класс DummyEntity но связанный ресурс содержит класс с именем DummyEntityProxy. Как это могло случиться? Это findClass метода реализации вашего класса загрузчика:

@Override 
public Class findClass(String name) { 
    return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length); 
} 

Вы не различать, что класс пытался загрузить, но вы определяете любой класс name с единственным классом, он знает, байт репрезентации DummyEntityProxy «s. Скорее всего:

@Override 
public Class findClass(String name) { 
    if (!name.equals(entityProxySubClassName)) { 
    throw new ClassNotFoundException(name); 
    } 
    return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length); 
} 

Таким образом, вы убедитесь, что вы не определяете класс другого имени. Кажется, однако, что ProxyClassLoader не должен быть задан для класса в первую очередь, но один из его родителей должен успешно его решить.

Кажется, что ASM - это довольно низкоуровневый API для ваших нужд. Вы рассматривали более высокоуровневый API, например, например, мою библиотеку Byte Buddy? Другие ORM, такие как Hibernate или Eclipse, также используют API на этом уровне, просто потому, что вещи, с которыми вы боретесь, трудно получить.

+0

Спасибо за ваши предложения, невероятно, что ошибка не связана с загрузкой класса, это связано с моей неправильной спецификацией имени подкласса подзаголовка. (т. е. я использовал простое имя, а не полное внутреннее имя класса) - я не должен лучше :) –

+0

Я принимаю вашу точку зрения на ASM, но мне действительно нравится тот факт, что его настолько низкий уровень, что он дает вам полный контроль над поколением/инструментами классов java. На сегодняшний день я использовал его более чем в полдюжины проектов. –

+0

@GilesThompson И все же вы все еще сталкиваетесь с трудными сценариями. Это просто подсказка. ASM великолепна, но очень легко заставить вещи сломаться, и всегда есть угловой случай, о котором вы не подумали. –

2

Как правило, нецелесообразно реализовывать ClassLoader, который пытается вернуть тот же класс, независимо от того, на что он был запрошен. Это прекрасно иллюстрируется ошибкой, которую вы получаете: NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy). Система запросила ваш ClassLoader для класса с именем DummyEntity, и вы вернули класс с именем DummyEntityProxy.

Остается вопрос, почему ваш погрузчик был запрошен для этого класса, как правило, сначала запрашивается родительский загрузчик. Похоже, что родительский загрузчик не нашел суперкласс, который указывает, что загрузчик родительского класса, который вы использовали (Thread.currentThread().getContextClassLoader()), не имеет доступа к вашему суперклассу.Было бы проще, если бы вы использовали anEntitySuperClass.getClassLoader() в качестве родительского загрузчика.

Конечно, вы должны убедиться, что все другие классы, используемые вашим сгенерированным прокси, доступны с помощью загрузчика классов anEntitySuperClass. Если нет, вам может понадобиться очень сложная структура делегирования загрузчика, чтобы сделать обе группы классов доступными. Это может быть даже невозможно (это зависит от того, что должен делать ваш прокси-сервер).

+0

Большое спасибо за ваши предложения, похоже, что ошибка связана с некоторыми динамически генерируемыми методами в классе. Методы, о которых идет речь, ошибочно ссылаются на связанный суперкласс по простому имени, а не по его внутреннему полностью квалифицированному имени класса. –