MethodHandler в Hibernate с использованием прокси-серверов ByteBuddy застревает в бесконечном цикле
В настоящее время я перевожу старый инструмент для Hibernate для автоматизации предварительной выборки на основе статистики сущностей. В старом инструменте использовался Hibernate 3.1, так что есть над чем поработать. Традиционно Hibernate использовал прокси-серверы CGlib, но так как я разрабатываю для последней версии Hibernate, я меняю генерацию прокси на ByteBuddy.
Тем не менее, после перехода на ByteBuddy у меня возникли некоторые проблемы с тем, чтобы заставить MethodHandler работать. Предполагается, что обработчик методов в моем инструменте обрабатывает все вызовы прокси, чтобы обеспечить сбор необходимой статистики. В настоящее время мой обработчик метода выглядит так:
public class EntityProxyMethodHandler implements ProxyConfiguration.Interceptor, Serializable {
private final EntityTracker entityTracker;
private final Object proxiedObject;
private final String proxiedClassName;
EntityProxyMethodHandler(
Object proxiedObject,
String proxiedClassName,
Set<Property> persistentProperties,
ExtentManager extentManager) {
this.proxiedObject = proxiedObject;
this.proxiedClassName = proxiedClassName;
this.entityTracker = new EntityTracker(persistentProperties, extentManager );
}
@Override
@RuntimeType
public Object intercept(@This Object instance, @Origin Method method, @AllArguments Object[] arguments)
throws Exception {
final String methodName = method.getName();
if ( "toString".equals( methodName ) ) {
return proxiedClassName + "@" + System.identityHashCode( instance );
}
else if ( "equals".equals( methodName ) ) {
return proxiedObject == instance;
}
else if ( "hashCode".equals( methodName ) ) {
return System.identityHashCode( instance );
}
else if ( arguments.length == 0 ) {
switch ( methodName ) {
case "disableTracking": {
boolean oldValue = entityTracker.isTracking();
entityTracker.setTracking( false );
return oldValue;
}
case "enableTracking": {
boolean oldValue = entityTracker.isTracking();
entityTracker.setTracking( true );
return oldValue;
}
case "isAccessed":
return entityTracker.isAccessed();
default:
break;
}
}
else if ( arguments.length == 1 ) {
if ( methodName.equals( "addTracker" ) && method.getParameterTypes()[0].equals( Statistics.class ) ) {
entityTracker.addTracker( (Statistics) arguments[0] );
return null;
}
else if ( methodName.equals( "addTrackers" ) && method.getParameterTypes()[0].equals( Set.class ) ) {
@SuppressWarnings("unchecked")
Set<Statistics> newTrackers = (Set) arguments[0];
entityTracker.addTrackers( newTrackers );
return null;
}
else if ( methodName
.equals( "extendProfile" ) && method.getParameterTypes()[0].equals( Statistics.class ) ) {
entityTracker.extendProfile( (Statistics) arguments[0], instance );
return null;
}
else if ( methodName
.equals( "removeTracker" ) && method.getParameterTypes()[0].equals( Statistics.class ) ) {
entityTracker.removeTracker( (Statistics) arguments[0] );
return null;
}
}
entityTracker.trackAccess( instance );
return method.invoke( instance, arguments ); // Gets stuck here, the method interception gets intercepted endlessly
}
Фабрика прокси выглядит так:
public class EntityProxyFactory {
private static SessionFactoryImplementor sessionFactory = AutofetchIntegrator.getSessionFactory();
private static final ConcurrentMap<Class<?>, Constructor<?>> entityConstructorMap = new ConcurrentHashMap<>();
private static <T> Constructor<T> getDefaultConstructor(Class<T> clazz) throws NoSuchMethodException {
Constructor<T> constructor = clazz.getDeclaredConstructor();
if ( !constructor.isAccessible() ) {
constructor.setAccessible( true );
}
return constructor;
}
static Object getProxyInstance(
Class persistentClass,
Set<Property> persistentProperties,
ExtentManager extentManager)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
if ( Modifier.isFinal( persistentClass.getModifiers() ) ) {
// Use the default constructor, because final classes cannot be inherited.
return useDefaultConstructor( persistentClass );
}
final ProxyConfiguration proxy = (ProxyConfiguration) Environment.getBytecodeProvider()
.getProxyFactoryFactory()
.buildBasicProxyFactory( persistentClass, new Class[] { TrackableEntity.class } )
.getProxy();
proxy.$$_hibernate_set_interceptor( new EntityProxyMethodHandler(proxy,persistentClass.getName(),
persistentProperties,
extentManager
) );
return proxy;
}
private static Object useDefaultConstructor(Class<?> clazz)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if ( !entityConstructorMap.containsKey( clazz ) ) {
entityConstructorMap.put( clazz, getDefaultConstructor( clazz ) );
}
final Constructor<?> c = entityConstructorMap.get( clazz );
return c.newInstance();
}
}
Когда я запускаю тест, содержащий доступ к одному из полей прокси, я получаю бесконечный стек вызовов следующего:
EntityProxyMethodHandler.intercept(EntityProxyMethodHandler.java:105)
Employee.$HibernateBasicProxy$Rbz7NDpP.getSupervisor(Unknown Source)
Caused by java.lang.reflect.InvocationTargetException:
EntityProxyMethodHandler.intercept(EntityProxyMethodHandler.java:105)
Employee.$HibernateBasicProxy$Rbz7NDpP.getSupervisor(Unknown Source)
По какой-то причине он вызывает не реальную сущность, а сам прокси, застревая в бесконечном цикле перехватов. Я понятия не имею, как решить эту проблему, я попытался найти способ вызвать "цель" внутри обработчика метода, но не смог ничего найти, так как по какой-то причине прокси, сгенерированные фабрикой прокси, не реализуют HibernateProxy- интерфейс. Может быть, это проблема, с которой у ByteBuddy есть несколько способов справиться, однако, поскольку я довольно неопытен с фреймворком, я еще не нашел пути.
Кроме того, это работало безупречно, когда я использовал прокси-серверы Javassist. Тогда у меня были похожие занятия:
public class EntityProxyFactory {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger(AutofetchLazyInitializer.class);
private static final MethodFilter FINALIZE_FILTER = new MethodFilter() {
@Override
public boolean isHandled(Method m) {
// skip finalize methods
return !(m.getParameterTypes().length == 0 && m.getName().equals("finalize"));
}
};
private static final ConcurrentMap<Class<?>, Class<?>> entityFactoryMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, Constructor<?>> entityConstructorMap = new ConcurrentHashMap<>();
//if entityFactoryMap doesnt contain that specific class, add it
private static Class<?> getProxyFactory(Class<?> persistentClass, String idMethodName) {
// Not sure how enhancer work, but it seems like you tell enhancer what type of subclasses that can be created, and all the method calls will be intercepted by entitycallbackfilter
if (!entityFactoryMap.containsKey(persistentClass)) {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(persistentClass);
factory.setInterfaces(new Class[]{TrackableEntity.class});
factory.setFilter(FINALIZE_FILTER);
entityFactoryMap.putIfAbsent(persistentClass, factory.createClass());
}
return entityFactoryMap.get(persistentClass);
}
private static <T> Constructor<T> getDefaultConstructor(Class<T> clazz) throws NoSuchMethodException {
Constructor<T> constructor = clazz.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor;
}
public static Object getProxyInstance(Class persistentClass, String idMethodName, Set<Property> persistentProperties,
ExtentManager extentManager)
throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
if (Modifier.isFinal(persistentClass.getModifiers())) {
// Use the default constructor, because final classes cannot be inherited.
return useDefaultConstructor(persistentClass);
}
Class<?> factory = getProxyFactory(persistentClass, idMethodName);
try {
final Object proxy = factory.newInstance();
((Proxy) proxy).setHandler(new EntityProxyMethodHandler(persistentProperties, extentManager));
return proxy;
} catch (IllegalAccessException | InstantiationException e) {
return useDefaultConstructor(persistentClass);
}
}
private static Object useDefaultConstructor(Class<?> clazz) throws NoSuchMethodException, InstantiationException,
InvocationTargetException, IllegalAccessException {
if (!entityConstructorMap.containsKey(clazz)) {
entityConstructorMap.put(clazz, getDefaultConstructor(clazz));
}
final Constructor<?> c = entityConstructorMap.get(clazz);
return c.newInstance((Object[]) null);
}
}
public class EntityProxyMethodHandler implements MethodHandler, Serializable {
private final EntityTracker entityTracker;
public EntityProxyMethodHandler(Set<Property> persistentProperties, ExtentManager extentManager) {
this.entityTracker = new EntityTracker(persistentProperties, extentManager);
}
@Override
public Object invoke(Object obj, Method thisMethod, Method proceed, Object[] args) throws Throwable {
if (args.length == 0) {
if (thisMethod.getName().equals("disableTracking")) {
boolean oldValue = entityTracker.isTracking();
entityTracker.setTracking(false);
return oldValue;
} else if (thisMethod.getName().equals("enableTracking")) {
boolean oldValue = entityTracker.isTracking();
entityTracker.setTracking(true);
return oldValue;
} else if (thisMethod.getName().equals("isAccessed")) {
return entityTracker.isAccessed();
}
} else if (args.length == 1) {
if (thisMethod.getName().equals("addTracker") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
entityTracker.addTracker((Statistics) args[0]);
return null;
} else if (thisMethod.getName().equals("addTrackers") && thisMethod.getParameterTypes()[0].equals(Set.class)) {
@SuppressWarnings("unchecked")
Set<Statistics> newTrackers = (Set) args[0];
entityTracker.addTrackers(newTrackers);
return null;
} else if (thisMethod.getName().equals("extendProfile") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
entityTracker.extendProfile((Statistics) args[0], obj);
return null;
} else if (thisMethod.getName().equals("removeTracker") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
entityTracker.removeTracker((Statistics) args[0]);
return null;
}
}
entityTracker.trackAccess(obj);
return proceed.invoke(obj, args);
}
Кто-нибудь знает, как я мог бы вызвать объект реальной сущности в моем обработчике метода здесь?
Вот часть трассировки стека, когда я запускаю модульное тестирование, вызванное повторением "бесконечно":
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.autofetch.hibernate.EntityProxyMethodHandler.intercept(EntityProxyMethodHandler.java:105)
at org.autofetch.test.Employee$HibernateBasicProxy$dbvGRz97.getSupervisor(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.autofetch.hibernate.AutofetchLazyInitializer.intercept(AutofetchLazyInitializer.java:123)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at org.autofetch.test.Employee$HibernateProxy$PajATc2c.getSupervisor(Unknown Source)
at org.autofetch.test.ExtentTest.secondLevelSupervisorAccess(ExtentTest.java:533)
at org.autofetch.test.ExtentTest.testSecondLevelSupervisorAccess(ExtentTest.java:246)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.hibernate.testing.junit4.ExtendedFrameworkMethod.invokeExplosively(ExtendedFrameworkMethod.java:45)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:298)
at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:292)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.autofetch.hibernate.EntityProxyMethodHandler.intercept(EntityProxyMethodHandler.java:105)
at org.autofetch.test.Employee$HibernateBasicProxy$dbvGRz97.getSupervisor(Unknown Source)
... 30 more
1 ответ
Если вы хотите вызвать оригинальный метод, вы можете получить его, используя @SuperMethod Method m
или же @SuperCall Callable<?> c
,
Использование вызываемого прокси - это метод с наименьшими затратами, поэтому вам не нужно получать массив аргументов.