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
This commit is contained in:
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <strong>NOTE</strong> 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<LazyLoadingInterceptor> 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<LazyLoadingInterceptor> 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) {
|
||||
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
|
||||
@@ -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<ClassProxyAssert, ClassProxyHint> {
|
||||
|
||||
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<TypeReference> getProxiedInterfaces() {
|
||||
return actual.getProxiedInterfaces();
|
||||
}
|
||||
}
|
||||
@@ -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<CodeContributionAsser
|
||||
.filter(jdkProxyHint -> 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<ClassProxyHint> classProxiesFor(Class<?> entryPoint) {
|
||||
|
||||
return this.actual.getRuntimeHints().proxies().classProxies()
|
||||
.filter(jdkProxyHint -> jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName()
|
||||
.equals(entryPoint.getCanonicalName()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user