Handle hints for CGLIB proxies consistently

This commit makes sure that hints are registered for CGLIB proxies even
if the proxy itself is not created. This typically happens when AOT runs
on an existing classpath, and a previous run already created the proxy.

Closes gh-29295
This commit is contained in:
Stephane Nicoll
2022-10-10 13:46:26 +02:00
parent e8ce86a6f0
commit 4eca87baa3
7 changed files with 142 additions and 52 deletions

View File

@@ -50,7 +50,7 @@ public class ApplicationContextAotGenerator {
*/
public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
GenerationContext generationContext) {
return withGeneratedClassHandler(new GeneratedClassHandler(generationContext), () -> {
return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
ApplicationContextInitializationCodeGenerator codeGenerator =
@@ -60,12 +60,14 @@ public class ApplicationContextAotGenerator {
});
}
private <T> T withGeneratedClassHandler(GeneratedClassHandler generatedClassHandler, Supplier<T> task) {
private <T> T withCglibClassHandler(CglibClassHandler cglibClassHandler, Supplier<T> task) {
try {
ReflectUtils.setGeneratedClassHandler(generatedClassHandler);
ReflectUtils.setLoadedClassHandler(cglibClassHandler::handleLoadedClass);
ReflectUtils.setGeneratedClassHandler(cglibClassHandler::handleGeneratedClass);
return task.get();
}
finally {
ReflectUtils.setLoadedClassHandler(null);
ReflectUtils.setGeneratedClassHandler(null);
}
}

View File

@@ -30,46 +30,48 @@ import org.springframework.cglib.core.ReflectUtils;
import org.springframework.core.io.ByteArrayResource;
/**
* Handle generated classes by adding them to a {@link GenerationContext},
* Handle CGLIB classes by adding them to a {@link GenerationContext},
* and register the necessary hints so that they can be instantiated.
*
* @author Stephane Nicoll
* @see ReflectUtils#setGeneratedClassHandler(BiConsumer)
* @see ReflectUtils#setLoadedClassHandler(Consumer)
*/
class GeneratedClassHandler implements BiConsumer<String, byte[]> {
class CglibClassHandler {
private static final Consumer<Builder> asCglibProxy = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS);
private static final Consumer<Builder> asCglibProxyTargetType = hint ->
hint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS);
private static final Consumer<Builder> instantiateCglibProxy = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
private final RuntimeHints runtimeHints;
private final GeneratedFiles generatedFiles;
GeneratedClassHandler(GenerationContext generationContext) {
CglibClassHandler(GenerationContext generationContext) {
this.runtimeHints = generationContext.getRuntimeHints();
this.generatedFiles = generationContext.getGeneratedFiles();
}
@Override
public void accept(String className, byte[] content) {
this.runtimeHints.reflection().registerType(TypeReference.of(className), asCglibProxy)
.registerType(TypeReference.of(getTargetTypeClassName(className)), asCglibProxyTargetType);
String path = className.replace(".", "/") + ".class";
/**
* Handle the specified generated CGLIB class.
* @param cglibClassName the name of the generated class
* @param content the bytecode of the generated class
*/
public void handleGeneratedClass(String cglibClassName, byte[] content) {
registerHints(TypeReference.of(cglibClassName));
String path = cglibClassName.replace(".", "/") + ".class";
this.generatedFiles.addFile(Kind.CLASS, path, new ByteArrayResource(content));
}
private String getTargetTypeClassName(String proxyClassName) {
int index = proxyClassName.indexOf("$$SpringCGLIB$$");
if (index == -1) {
throw new IllegalArgumentException("Failed to extract target type from " + proxyClassName);
}
return proxyClassName.substring(0, index);
/**
* Handle the specified loaded CGLIB class.
* @param cglibClass a cglib class that has been loaded
*/
public void handleLoadedClass(Class<?> cglibClass) {
registerHints(TypeReference.of(cglibClass));
}
private void registerHints(TypeReference cglibTypeReference) {
this.runtimeHints.reflection().registerType(cglibTypeReference, instantiateCglibProxy);
}
}

View File

@@ -21,9 +21,12 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -46,6 +49,7 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Generic ApplicationContext implementation that holds a single internal
@@ -107,6 +111,16 @@ import org.springframework.util.Assert;
*/
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private static final Consumer<Builder> asCglibProxy = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS);
private static final Consumer<Builder> asCglibProxyTarget = hint ->
hint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS);
private final DefaultListableBeanFactory beanFactory;
@Nullable
@@ -433,6 +447,14 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
if (Proxy.isProxyClass(beanType)) {
runtimeHints.proxies().registerJdkProxy(beanType.getInterfaces());
}
else {
Class<?> userClass = ClassUtils.getUserClass(beanType);
if (userClass != beanType) {
runtimeHints.reflection()
.registerType(beanType, asCglibProxy)
.registerType(userClass, asCglibProxyTarget);
}
}
}
}
}