Java - создание подкласса динамически

Я хотел бы создать подкласс программно. Я думаю, у меня есть несколько вариантов: Javassist, CGLib, BCEL или ASM.

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

Теперь - как бы я это сделал? Я нашел примеры с перехватом вызовов методов, доступом к полям, инициализацией и т.д. Но ничего не касается подклассов.

Я хотел бы получить класс, который:

  • имеет имя, которое я хочу.
  • является (прямым, в лучшем случае) подклассом данного класса
  • копирует конструктор из родительского класса (или вызывает super(...))
  • В конце концов, я хотел бы дать несколько аннотаций.

Я знаю, что это возможно, потому что это могут сделать различные интеграции динамических языков, такие как GroovyClassLoader.

Ответ 1

Это довольно легко с Javassist:

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

static Class<? extends DefinitionBasedMigrator> createClass( String fullName )
        throws NotFoundException, CannotCompileException
{
    ClassPool pool = ClassPool.getDefault();

    // Create the class.
    CtClass subClass = pool.makeClass( fullName );
    final CtClass superClass = pool.get( DefinitionBasedMigrator.class.getName() );
    subClass.setSuperclass( superClass );
    subClass.setModifiers( Modifier.PUBLIC );

    // Add a constructor which will call super( ... );
    CtClass[] params = new CtClass[]{
        pool.get( MigratorDefinition.class.getName() ),
        pool.get( GlobalConfiguration.class.getName()) 
    };
    final CtConstructor ctor = CtNewConstructor.make( params, null, CtNewConstructor.PASS_PARAMS, null, null, subClass );
    subClass.addConstructor( ctor );

    return subClass.toClass();
}

Зависимость от Maven:

<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-GA</version>
</dependency>

Ответ 2

Здесь можно использовать одну библиотеку, которую я особенно люблю; Bytebuddy.

Пример, взятый непосредственно с целевой страницы:

Class<?> dynamicType = new ByteBuddy()
  .subclass(Object.class)
  .method(ElementMatchers.named("toString"))
  .intercept(FixedValue.value("Hello World!"))
  .make()
  .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
  .getLoaded();

Это невероятно гибкая и определенно стоит проверить, хотите ли вы сохранить свои волосы, я лично считаю, что интенсивное использование джавассиста может быть довольно уродливым и беспорядочным время от времени, bytebuddy чувствует себя как необходимое дыхание свежего воздуха!

Rafael Winterhalter также активен в StackOverflow, который позволяет узнать все, что вы не уверены в ветре.

Изменить: мои извинения за некропостинг. Приземлился здесь, когда друг связал вопрос и забыл проверить дату.

Ответ 3

Java Proxies может выполнять то, что вам нужно - они по существу позволяют вам динамически использовать функциональность слоя поверх объекта, так как вы можете перехватывать вызовы любого метода на этот объект и сами обрабатывать их или отправлять вызовы метода основной класс. В зависимости от того, что вы хотите сделать, может быть, вы можете получить тот же результат, что и вы, создав динамический подкласс

Oracle имеет достойное представление на своем веб-сайте (URL ссылается на Java версии 1.4.2, но я не думаю, что поведение этого изменилось в более поздних версиях). Вот более краткий пример который дает хороший вкус тому, что выглядит прокси-код.

Также можно делать вещи, используя манипуляции с прямым байтовым кодом (поддерживаемые ASM framework), однако я полагаю, что использование прокси-серверов будет более простой подход.