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), однако я полагаю, что использование прокси-серверов будет более простой подход.