2015-12-11 5 views
2

Я пытаюсь перехватить конструкторы, аннотированные @Inject. Это отлично работает в контексте небольшого модульного теста. Однако в контексте контейнера DI, такого как Spring, он терпит неудачу с ClassNotFoundException.Перехват конструктора вызывает ClassNotFoundException

Мне удалось сузить основную причину. Вызов getDeclaredConstructors на инструментальном классе вызовет это исключение. Интересно, что если мы сначала создадим экземпляр этого класса, проблема исчезнет.

Например:

public class InterceptConstructorTest { 

    @Test 
    public void testConstructorInterception() throws ClassNotFoundException { 

     ByteBuddyAgent.install(); 

     new AgentBuilder.Default().type(nameStartsWith("test")).transform(new AgentBuilder.Transformer() { 

      @Override 
      public Builder<?> transform(Builder<?> builder, TypeDescription td) { 

       return builder.constructor(isAnnotatedWith(Inject.class)) 
         .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(ConstructorInterceptor.class))); 
      } 
     }).installOnByteBuddyAgent(); 

     // If this line is uncommented, ClassNotFoundException won't be thrown 
//  MyClass myClass = new MyClass("a param"); 

     // Manually load MyClass 
     Class<?> myClassDefinition = getClass().getClassLoader().loadClass("test.MyClass"); 

     // Throws NoClassDefFoundError 
     for(Constructor<?> constructor : myClassDefinition.getDeclaredConstructors()) { 
      System.out.println(constructor); 
     } 
    } 
} 

Стек трассировки стека можно найти: http://pastebin.com/1zhx3fVX

class MyClass { 

    @Inject 
    public MyClass(String aParam) { 
     System.out.println("constructor called"); 
    } 
} 

class ConstructorInterceptor { 

    public static void intercept() { 
     System.out.println("Intercepted"); 
    } 
} 
+1

Начиная с версии 0.7.7 Байт Бадди по умолчанию «InitiailizationStrategy» позаботится об этой проблеме. Новая версия в настоящее время синхронизирована с Центральным репозиторием Maven. –

+0

После перехода на 0.7.7 мое приложение преждевременно выходит, прежде чем выполняется 'main'. Никаких исключений не было. Любая идея, как я могу помочь вам разобраться в этой проблеме? – user3408654

+0

Этого не должно быть, даже если прибор не работает. Вы всегда можете добавить 'AgentBuilder.Listener', чтобы проверить, не выдает ли Byte Buddy ошибку. Может быть, ваш агент все еще использует старую версию Byte Buddy, вызывающую «NoClassDefFoundError»?Работает ли ваш блок с новой версией? Если это так, я предполагаю, что у вас конфликт версий. –

ответ

1

Проблема в этом случае является внедрение конструктора. Для того, чтобы перебазироваться конструктором, Byte Buddy необходимо создать дополнительный тип и создает класс, как следующее:

class MyClass { 

    private synthetic MyClass(String aParam, $SomeType ignored) { 
     System.out.println("constructor called"); 
    } 

    @Inject 
    public MyClass(String aParam) { 
     this(aParam, null); 
     // Instrumentation logic. 
    } 
} 

Дополнительный типа, к сожалению, необходимо создать уникальную подпись для нормированных конструкторов. С помощью методов Byte Buddy может изменить имя, но для конструкторов это невозможно, поскольку должен иметь имя <init> в файле класса, который будет распознаваться как конструкторы.

Byte Buddy пытается загрузить только вспомогательные классы после a тип был инструментарий. В зависимости от виртуальной машины загрузка класса, ссылающегося на другой класс, вызывает загрузку ссылочного типа. Если этот тип является инструментальным классом, инструментарий отменяет текущее инструментальное средство для округлости.

Таким образом, Байт Бадди гарантирует, что любой вспомогательный тип загружается только в первой возможной точке после того, как он может быть уверен, что загружен инструментальный тип. И он делает это, добавляя самоинициализацию в инициализатор класса инструментального класса. В некотором смысле, Byte Buddy добавляет блок:

static { 
    ByteBuddy.loadAuxiliaryTypes(MyClass.class); 
} 

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

Class.forName("test.MyClass", true, getClass().getClassLoader()); 

вместо loadClass, проблема была бы не произойти, когда второй параметр указывает на то, чтобы выполнить инициализатора класса с нетерпением. Кроме того, инициализатор выполняется, если вы создаете экземпляр.

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

 Смежные вопросы

  • Нет связанных вопросов^_^