From d25e840cf51b3b5cb1f36eef897ecf8693431a4e Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Thu, 17 Apr 2014 15:10:11 +0200 Subject: [PATCH] =?UTF-8?q?DATAMONGO-914=20-=20Improve=20resolving=20of=20?= =?UTF-8?q?lazy-loading=20proxies=20for=20classes=20that=20override=20equa?= =?UTF-8?q?ls(=E2=80=A6)/hashCode().?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now properly resolve lazy-loading proxies for @DBRef's when an overridden equals or hash code method is called with Spring 4. We fall back to our old Objenesis proxy generation in order to circumvent the default handling for overridden hashcCode() and equals(…) methods in CglibAopProxies generated by Spring 4. If we detect that we run with Spring 4 we use the repacked Objenesis that is included in Spring 4. Previously the generated proxy used some generic hashCode() or equals(…) logic that did not trigger a proper lazy loading in such cases. Original pull request: #171. --- .../core/convert/DefaultDbRefResolver.java | 90 +++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java index 90a6caab7..d096dbdee 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java @@ -28,6 +28,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.objenesis.Objenesis; import org.objenesis.ObjenesisStd; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.BeanUtils; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; @@ -57,7 +58,6 @@ import com.mongodb.DBRef; */ public class DefaultDbRefResolver implements DbRefResolver { - private static final boolean IS_SPRING_4_OR_BETTER = SpringVersion.getVersion().startsWith("4"); private static final boolean OBJENESIS_PRESENT = ClassUtils.isPresent("org.objenesis.Objenesis", null); private final MongoDbFactory mongoDbFactory; @@ -138,7 +138,7 @@ public class DefaultDbRefResolver implements DbRefResolver { proxyFactory.setProxyTargetClass(true); proxyFactory.setTargetClass(propertyType); - if (IS_SPRING_4_OR_BETTER || !OBJENESIS_PRESENT) { + if (!OBJENESIS_PRESENT) { proxyFactory.addAdvice(interceptor); return proxyFactory.getProxy(); } @@ -374,13 +374,25 @@ public class DefaultDbRefResolver implements DbRefResolver { } /** - * Static class to accomodate optional dependency on Objenesis. + * Static class to accommodate optional dependency on Objenesis. * * @author Oliver Gierke + * @author Thomas Darimont + * @since 1.4 */ private static class ObjenesisProxyEnhancer { - private static final Objenesis OBJENESIS = new ObjenesisStd(true); + private static final boolean IS_SPRING_4_OR_BETTER = SpringVersion.getVersion().startsWith("4"); + private static final InstanceCreatorStrategy INSTANCE_CREATOR; + + static { + + if (IS_SPRING_4_OR_BETTER) { + INSTANCE_CREATOR = new Spring4ObjenesisInstanceCreatorStrategy(); + } else { + INSTANCE_CREATOR = new DefaultObjenesisInstanceCreatorStrategy(); + } + } public static Object enhanceAndGet(ProxyFactory proxyFactory, Class type, org.springframework.cglib.proxy.MethodInterceptor interceptor) { @@ -390,9 +402,77 @@ public class DefaultDbRefResolver implements DbRefResolver { enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class }); - Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass()); + Factory factory = (Factory) INSTANCE_CREATOR.newInstance(enhancer.createClass()); factory.setCallbacks(new Callback[] { interceptor }); return factory; } + + /** + * Strategy for constructing new instances of a given {@link Class}. + * + * @author Thomas Darimont + */ + interface InstanceCreatorStrategy { + Object newInstance(Class clazz); + } + + /** + * An {@link InstanceCreatorStrategy} that uses Objenesis from the classpath. + * + * @author Thomas Darimont + */ + private static class DefaultObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy { + + private static final Objenesis OBJENESIS = new ObjenesisStd(true); + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class) + */ + @Override + public Object newInstance(Class clazz) { + return OBJENESIS.newInstance(clazz); + } + } + + /** + * An {@link InstanceCreatorStrategy} that uses a repackaged version of Objenesis from Spring 4. + * + * @author Thomas Darimont + */ + private static class Spring4ObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy { + + private static final String SPRING4_OBJENESIS_CLASS_NAME = "org.springframework.objenesis.ObjenesisStd"; + private static final Object OBJENESIS; + private static final Method NEW_INSTANCE_METHOD; + + static { + + try { + Class objenesisClass = ClassUtils.forName(SPRING4_OBJENESIS_CLASS_NAME, + ObjenesisProxyEnhancer.class.getClassLoader()); + + OBJENESIS = BeanUtils.instantiateClass(objenesisClass.getConstructor(boolean.class), true); + NEW_INSTANCE_METHOD = objenesisClass.getMethod("newInstance", Class.class); + + } catch (Exception e) { + throw new RuntimeException("Could not setup Objenesis infrastructure with Spring 4 ", e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class) + */ + @Override + public Object newInstance(Class clazz) { + + try { + return NEW_INSTANCE_METHOD.invoke(OBJENESIS, clazz); + } catch (Exception e) { + throw new RuntimeException("Could not created instance for " + clazz, e); + } + } + } } }