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()));
- }
}