From a66438fc20c251df2f80a27551166538fbe761fc Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 18 Aug 2022 07:52:31 +0200 Subject: [PATCH] Resolve cglib proxies during AOT processing. We now make sure to run the enhancer during AOT which allows the infrastructure to pick up the generated type. Along the lines we removed the no longer supported asserts for class proxies and followed changes in FW6. Closes: #4148 --- .../aot/LazyLoadingProxyAotProcessor.java | 7 +- .../core/convert/LazyLoadingProxyFactory.java | 102 +++++++++++++----- ...MongoRepositoryPostProcessorUnitTests.java | 4 +- .../data/mongodb/aot/ClassProxyAssert.java | 45 -------- .../mongodb/aot/CodeContributionAssert.java | 17 --- 5 files changed, 80 insertions(+), 95 deletions(-) delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/ClassProxyAssert.java 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())); - } }