diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/LazyLoadingProxyAotProcessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/LazyLoadingProxyAotProcessor.java index c07c60d95..97ea07710 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/LazyLoadingProxyAotProcessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/LazyLoadingProxyAotProcessor.java @@ -29,6 +29,8 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.data.annotation.Reference; import org.springframework.data.aot.TypeUtils; +import org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory; +import org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory.LazyLoadingInterceptor; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DocumentReference; @@ -72,10 +74,7 @@ class LazyLoadingProxyAotProcessor { generationContext.getRuntimeHints().proxies().registerJdkProxy(interfaces.toArray(Class[]::new)); } else { - - generationContext.getRuntimeHints().proxies().registerClassProxy(field.getType(), builder -> { - builder.proxiedInterfaces(org.springframework.data.mongodb.core.convert.LazyLoadingProxy.class); - }); + LazyLoadingProxyFactory.resolveProxyType(field.getType(), () -> LazyLoadingInterceptor.none()); } }); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java index 1a393a902..247af8589 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java @@ -22,45 +22,87 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.NativeDetector; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.ClientSessionException; import org.springframework.data.mongodb.LazyLoadingException; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.lang.Nullable; -import org.springframework.objenesis.ObjenesisStd; +import org.springframework.objenesis.SpringObjenesis; import org.springframework.util.ReflectionUtils; import com.mongodb.DBRef; /** * {@link ProxyFactory} to create a proxy for {@link MongoPersistentProperty#getType()} to resolve a reference lazily. + * NOTE This class is intended for internal usage only. * * @author Christoph Strobl * @author Mark Paluch */ -class LazyLoadingProxyFactory { +public final class LazyLoadingProxyFactory { private static final Log LOGGER = LogFactory.getLog(LazyLoadingProxyFactory.class); - private final ObjenesisStd objenesis; + private final SpringObjenesis objenesis; private final PersistenceExceptionTranslator exceptionTranslator; + private LazyLoadingProxyFactory() { + this(ex -> null); + } + public LazyLoadingProxyFactory(PersistenceExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; - this.objenesis = new ObjenesisStd(true); + this.objenesis = new SpringObjenesis(null); + } + + /** + * Predict the proxy target type. This will advice the infrastructure to resolve as many pieces as possible in a + * potential AOT scenario without necessarily resolving the entire object. + * + * @param propertyType the type to proxy + * @param interceptor the interceptor to be added. + * @return the proxy type. + * @since 4.0 + */ + public static Class resolveProxyType(Class propertyType, Supplier interceptor) { + + LazyLoadingProxyFactory factory = new LazyLoadingProxyFactory(); + + if (!propertyType.isInterface()) { + return factory.getEnhancedTypeFor(propertyType); + } + + return factory.prepareProxyFactory(propertyType, interceptor) + .getProxyClass(LazyLoadingProxy.class.getClassLoader()); + } + + private ProxyFactory prepareProxyFactory(Class propertyType, Supplier interceptor) { + + ProxyFactory proxyFactory = new ProxyFactory(); + + for (Class type : propertyType.getInterfaces()) { + proxyFactory.addInterface(type); + } + + proxyFactory.addInterface(LazyLoadingProxy.class); + proxyFactory.addInterface(propertyType); + proxyFactory.addAdvice(interceptor.get()); + + return proxyFactory; } public Object createLazyLoadingProxy(MongoPersistentProperty property, DbRefResolverCallback callback, @@ -71,33 +113,14 @@ class LazyLoadingProxyFactory { if (!propertyType.isInterface()) { - if (NativeDetector.inNativeImage()) { - - ProxyFactory factory = new ProxyFactory(); - factory.addAdvice(interceptor); - factory.addInterface(LazyLoadingProxy.class); - factory.setTargetClass(propertyType); - factory.setProxyTargetClass(true); - return factory.getProxy(propertyType.getClassLoader()); - } - Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType)); factory.setCallbacks(new Callback[] { interceptor }); return factory; } - ProxyFactory proxyFactory = new ProxyFactory(); - - for (Class type : propertyType.getInterfaces()) { - proxyFactory.addInterface(type); - } - - proxyFactory.addInterface(LazyLoadingProxy.class); - proxyFactory.addInterface(propertyType); - proxyFactory.addAdvice(interceptor); - - return proxyFactory.getProxy(LazyLoadingProxy.class.getClassLoader()); + return prepareProxyFactory(propertyType, + () -> new LazyLoadingInterceptor(property, callback, source, exceptionTranslator)).getProxy(); } /** @@ -110,8 +133,10 @@ class LazyLoadingProxyFactory { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(type); - enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); + enhancer.setCallbackType(LazyLoadingInterceptor.class); enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class }); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); return enhancer.createClass(); } @@ -139,6 +164,29 @@ class LazyLoadingProxyFactory { private volatile boolean resolved; private @Nullable Object result; + /** + * @return a {@link LazyLoadingInterceptor} that just continues with the invocation. + * @since 4.0 + */ + public static LazyLoadingInterceptor none() { + + return new LazyLoadingInterceptor(null, null, null, null) { + @Nullable + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); + } + + @Nullable + @Override + public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable { + + ReflectionUtils.makeAccessible(method); + return method.invoke(o, args); + } + }; + } + public LazyLoadingInterceptor(MongoPersistentProperty property, DbRefResolverCallback callback, Object source, PersistenceExceptionTranslator exceptionTranslator) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/AotMongoRepositoryPostProcessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/AotMongoRepositoryPostProcessorUnitTests.java index ba83c84a0..84edebd4d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/AotMongoRepositoryPostProcessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/AotMongoRepositoryPostProcessorUnitTests.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.mongodb.aot.RepositoryRegistrationAotContributionAssert.*; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RegisteredBean; @@ -52,7 +53,6 @@ public class AotMongoRepositoryPostProcessorUnitTests { contribution.contributesJdkProxy(LastModifiedDate.class, SynthesizedAnnotation.class); contribution.contributesJdkProxy(Document.class, SynthesizedAnnotation.class); contribution.contributesJdkProxy(DBRef.class, SynthesizedAnnotation.class); -// TODO: not supported yet contribution.contributesClassProxy(Address.class, LazyLoadingProxy.class); }); } @@ -60,7 +60,7 @@ public class AotMongoRepositoryPostProcessorUnitTests { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(configuration); - ctx.refreshForAotProcessing(); + ctx.refreshForAotProcessing(new RuntimeHints()); return it -> { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/ClassProxyAssert.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/ClassProxyAssert.java deleted file mode 100644 index 0f9fd8261..000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/ClassProxyAssert.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.mongodb.aot; - -import static org.assertj.core.api.Assertions.*; - -import java.util.Arrays; -import java.util.List; - -import org.assertj.core.api.AbstractAssert; -import org.springframework.aot.hint.ClassProxyHint; -import org.springframework.aot.hint.TypeReference; - -/** - * @author Christoph Strobl - * @since 2022/04 - */ -public class ClassProxyAssert extends AbstractAssert { - - protected ClassProxyAssert(ClassProxyHint classProxyHint) { - super(classProxyHint, ClassProxyAssert.class); - } - - public void matches(Class... proxyInterfaces) { - assertThat(actual.getProxiedInterfaces().stream().map(TypeReference::getCanonicalName)) - .containsExactly(Arrays.stream(proxyInterfaces).map(Class::getCanonicalName).toArray(String[]::new)); - } - - public List getProxiedInterfaces() { - return actual.getProxiedInterfaces(); - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/CodeContributionAssert.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/CodeContributionAssert.java index c3705e5cc..2503ad691 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/CodeContributionAssert.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/CodeContributionAssert.java @@ -22,7 +22,6 @@ import java.util.stream.Stream; import org.assertj.core.api.AbstractAssert; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.ClassProxyHint; import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -101,20 +100,4 @@ public class CodeContributionAssert extends AbstractAssert jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName() .equals(entryPoint.getCanonicalName())); } - - public CodeContributionAssert contributesClassProxy(Class... proxyInterfaces) { - - assertThat(classProxiesFor(proxyInterfaces[0])) - .describedAs("Unable to find JDK proxy matching [%s]", Arrays.asList(proxyInterfaces)) - .anySatisfy(it -> new ClassProxyAssert(it).matches(proxyInterfaces)); - - return this; - } - - private Stream classProxiesFor(Class entryPoint) { - - return this.actual.getRuntimeHints().proxies().classProxies() - .filter(jdkProxyHint -> jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName() - .equals(entryPoint.getCanonicalName())); - } }