From b31a15851e7aaabf3629cc101d285e751e535927 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 10 Aug 2022 23:30:19 +0200 Subject: [PATCH] Support for pre-generated CGLIB proxy classes (in AOT scenarios) Includes runtime storing of generated classes to a directory specified by the "cglib.generatedClasses" system property. Avoids lazy CGLIB fast-class generation and replaces generated Enhancer and MethodWrapper key classes with equivalent record types. Introduces support for early type determination in InstantiationStrategy, AopProxy and SmartInstantiationAwareBeanPostProcessor - in order to trigger CGLIB class generation in refreshForAotProcessing (through early determineBeanType calls for bean definitions). Closes gh-28115 --- .../AbstractAdvisingBeanPostProcessor.java | 29 +++++- .../aop/framework/AopProxy.java | 11 ++- .../aop/framework/CglibAopProxy.java | 89 ++++++------------- .../aop/framework/DefaultAopProxyFactory.java | 4 - .../aop/framework/JdkDynamicAopProxy.java | 8 +- .../aop/framework/ObjenesisCglibAopProxy.java | 7 +- .../aop/framework/ProxyFactory.java | 13 ++- .../autoproxy/AbstractAutoProxyCreator.java | 39 +++++++- .../AutowiredAnnotationBeanPostProcessor.java | 84 ++++++++++------- ...rtInstantiationAwareBeanPostProcessor.java | 24 ++++- .../AbstractAutowireCapableBeanFactory.java | 24 ++--- ...CglibSubclassingInstantiationStrategy.java | 14 ++- .../support/InstantiationStrategy.java | 10 ++- ...athScanningCandidateComponentProvider.java | 7 +- .../ConfigurationClassEnhancer.java | 2 + .../ConfigurationClassPostProcessor.java | 24 ++--- .../support/GenericApplicationContext.java | 23 +++++ spring-core/spring-core.gradle | 1 + .../cglib/core/MethodWrapper.java | 47 ++++++++++ .../cglib/core/ReflectUtils.java | 13 +++ .../cglib/core/SpringNamingPolicy.java | 35 +++++--- .../springframework/cglib/proxy/Enhancer.java | 26 +++--- .../handler/invocation/ResolvableMethod.java | 1 + .../testfixture/method/ResolvableMethod.java | 1 + .../annotation/MvcUriComponentsBuilder.java | 1 + 25 files changed, 364 insertions(+), 173 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index 57c301e9b1..1c333af694 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.springframework.aop.Advisor; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; import org.springframework.core.SmartClassLoader; import org.springframework.lang.Nullable; @@ -33,7 +34,8 @@ import org.springframework.lang.Nullable; * @since 3.2 */ @SuppressWarnings("serial") -public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor { +public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport + implements SmartInstantiationAwareBeanPostProcessor { @Nullable protected Advisor advisor; @@ -58,8 +60,27 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return bean; + public Class determineBeanType(Class beanClass, String beanName) { + if (this.advisor != null && isEligible(beanClass)) { + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.copyFrom(this); + proxyFactory.setTargetClass(beanClass); + + if (!proxyFactory.isProxyTargetClass()) { + evaluateProxyInterfaces(beanClass, proxyFactory); + } + proxyFactory.addAdvisor(this.advisor); + customizeProxyFactory(proxyFactory); + + // Use original ClassLoader if bean class not locally loaded in overriding class loader + ClassLoader classLoader = getProxyClassLoader(); + if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { + classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + } + return proxyFactory.getProxyClass(classLoader); + } + + return beanClass; } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxy.java index cc7a3fd099..f103477504 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-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. @@ -52,4 +52,13 @@ public interface AopProxy { */ Object getProxy(@Nullable ClassLoader classLoader); + /** + * Determine the proxy class. + * @param classLoader the class loader to create the proxy class with + * (or {@code null} for the low-level proxy facility's default) + * @return the proxy class + * @since 6.0 + */ + Class getProxyClass(@Nullable ClassLoader classLoader); + } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 144b3edcdf..1e9ccc3068 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -153,11 +153,20 @@ class CglibAopProxy implements AopProxy, Serializable { @Override public Object getProxy() { - return getProxy(null); + return buildProxy(null, false); } @Override public Object getProxy(@Nullable ClassLoader classLoader) { + return buildProxy(classLoader, false); + } + + @Override + public Class getProxyClass(@Nullable ClassLoader classLoader) { + return (Class) buildProxy(classLoader, true); + } + + private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) { if (logger.isTraceEnabled()) { logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); } @@ -190,6 +199,7 @@ class CglibAopProxy implements AopProxy, Serializable { enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); Callback[] callbacks = getCallbacks(rootClass); @@ -203,7 +213,7 @@ class CglibAopProxy implements AopProxy, Serializable { enhancer.setCallbackTypes(types); // Generate the proxy class and create a proxy instance. - return createProxyClassAndInstance(enhancer, callbacks); + return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks)); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + @@ -216,6 +226,11 @@ class CglibAopProxy implements AopProxy, Serializable { } } + protected Class createProxyClass(Enhancer enhancer) { + enhancer.setInterceptDuringConstruction(false); + return enhancer.createClass(); + } + protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { enhancer.setInterceptDuringConstruction(false); enhancer.setCallbacks(callbacks); @@ -375,22 +390,6 @@ class CglibAopProxy implements AopProxy, Serializable { return false; } - /** - * Invoke the given method with a CGLIB MethodProxy if possible, falling back - * to a plain reflection invocation in case of a fast-class generation failure. - */ - @Nullable - private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy) - throws Throwable { - try { - return methodProxy.invoke(target, args); - } - catch (CodeGenerationException ex) { - CglibMethodInvocation.logFastClassGenerationFailure(method); - return AopUtils.invokeJoinpointUsingReflection(target, method, args); - } - } - /** * Process a return value. Wraps a return of {@code this} if necessary to be the * {@code proxy} and also verifies that {@code null} is not returned as a primitive. @@ -424,10 +423,9 @@ class CglibAopProxy implements AopProxy, Serializable { /** - * Method interceptor used for static targets with no advice chain. The call - * is passed directly back to the target. Used when the proxy needs to be - * exposed and it can't be determined that the method won't return - * {@code this}. + * Method interceptor used for static targets with no advice chain. The call is + * passed directly back to the target. Used when the proxy needs to be exposed + * and it can't be determined that the method won't return {@code this}. */ private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable { @@ -441,7 +439,7 @@ class CglibAopProxy implements AopProxy, Serializable { @Override @Nullable public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { - Object retVal = invokeMethod(this.target, method, args, methodProxy); + Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args); return processReturnType(proxy, this.target, method, retVal); } } @@ -466,7 +464,7 @@ class CglibAopProxy implements AopProxy, Serializable { Object oldProxy = null; try { oldProxy = AopContext.setCurrentProxy(proxy); - Object retVal = invokeMethod(this.target, method, args, methodProxy); + Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args); return processReturnType(proxy, this.target, method, retVal); } finally { @@ -494,7 +492,7 @@ class CglibAopProxy implements AopProxy, Serializable { public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object target = this.targetSource.getTarget(); try { - Object retVal = invokeMethod(target, method, args, methodProxy); + Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); return processReturnType(proxy, target, method, retVal); } finally { @@ -524,7 +522,7 @@ class CglibAopProxy implements AopProxy, Serializable { Object target = this.targetSource.getTarget(); try { oldProxy = AopContext.setCurrentProxy(proxy); - Object retVal = invokeMethod(target, method, args, methodProxy); + Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); return processReturnType(proxy, target, method, retVal); } finally { @@ -695,13 +693,13 @@ class CglibAopProxy implements AopProxy, Serializable { Object retVal; // Check whether we only have one InvokerInterceptor: that is, // no real advice, but just reflective invocation of the target. - if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) { + if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly. // Note that the final invoker must be an InvokerInterceptor, so we know // it does nothing but a reflective operation on the target, and no hot // swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); - retVal = invokeMethod(target, method, argsToUse, methodProxy); + retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... @@ -743,17 +741,11 @@ class CglibAopProxy implements AopProxy, Serializable { */ private static class CglibMethodInvocation extends ReflectiveMethodInvocation { - @Nullable - private final MethodProxy methodProxy; - public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method, Object[] arguments, @Nullable Class targetClass, List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); - - // Only use method proxy for public methods not derived from java.lang.Object - this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null); } @Override @@ -781,35 +773,6 @@ class CglibAopProxy implements AopProxy, Serializable { } } } - - /** - * Gives a marginal performance improvement versus using reflection to - * invoke the target when invoking public methods. - */ - @Override - protected Object invokeJoinpoint() throws Throwable { - if (this.methodProxy != null) { - try { - return this.methodProxy.invoke(this.target, this.arguments); - } - catch (CodeGenerationException ex) { - logFastClassGenerationFailure(this.method); - } - } - return super.invokeJoinpoint(); - } - - static boolean isMethodProxyCompatible(Method method) { - return (Modifier.isPublic(method.getModifiers()) && - method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) && - !AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method)); - } - - static void logFastClassGenerationFailure(Method method) { - if (logger.isDebugEnabled()) { - logger.debug("Failed to generate CGLIB fast class for method: " + method); - } - } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index 75d547d6db..5710f8e03b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -20,7 +20,6 @@ import java.io.Serializable; import java.lang.reflect.Proxy; import org.springframework.aop.SpringProxy; -import org.springframework.core.NativeDetector; import org.springframework.util.ClassUtils; /** @@ -63,9 +62,6 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { return new JdkDynamicAopProxy(config); } - if (NativeDetector.inNativeImage()) { - throw new AopConfigException("Subclass-based proxies are not support yet in native images"); - } return new ObjenesisCglibAopProxy(config); } else { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index 3aeb80cd18..9eeae4382c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-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. @@ -126,6 +126,12 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); } + @SuppressWarnings("deprecation") + @Override + public Class getProxyClass(@Nullable ClassLoader classLoader) { + return Proxy.getProxyClass(classLoader, this.proxiedInterfaces); + } + /** * Finds any {@link #equals} or {@link #hashCode} method that may be defined * on the supplied set of interfaces. diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java index ba31e39fe2..df24ece095 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -52,6 +52,11 @@ class ObjenesisCglibAopProxy extends CglibAopProxy { } + @Override + protected Class createProxyClass(Enhancer enhancer) { + return enhancer.createClass(); + } + @Override protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { Class proxyClass = enhancer.createClass(); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java index f4b6208e44..56330a6395 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-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. @@ -110,6 +110,17 @@ public class ProxyFactory extends ProxyCreatorSupport { return createAopProxy().getProxy(classLoader); } + /** + * Determine the proxy class according to the settings in this factory. + * @param classLoader the class loader to create the proxy class with + * (or {@code null} for the low-level proxy facility's default) + * @return the proxy class + * @since 6.0 + */ + public Class getProxyClass(@Nullable ClassLoader classLoader) { + return createAopProxy().getProxyClass(classLoader); + } + /** * Create a new proxy for the given interface and interceptor. diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index e81b6e43e0..50beaa0061 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -38,6 +38,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyProcessorSupport; import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry; import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; +import org.springframework.aop.target.EmptyTargetSource; import org.springframework.aop.target.SingletonTargetSource; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; @@ -231,6 +232,30 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport return this.proxyTypes.get(cacheKey); } + @Override + public Class determineBeanType(Class beanClass, String beanName) { + Object cacheKey = getCacheKey(beanClass, beanName); + Class proxyType = this.proxyTypes.get(cacheKey); + if (proxyType == null) { + TargetSource targetSource = getCustomTargetSource(beanClass, beanName); + if (targetSource != null) { + if (StringUtils.hasLength(beanName)) { + this.targetSourcedBeans.add(beanName); + } + } + else { + targetSource = EmptyTargetSource.forClass(beanClass); + } + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); + if (specificInterceptors != DO_NOT_PROXY) { + this.advisedBeans.put(cacheKey, Boolean.TRUE); + proxyType = createProxyClass(beanClass, beanName, specificInterceptors, targetSource); + this.proxyTypes.put(cacheKey, proxyType); + } + } + return (proxyType != null ? proxyType : beanClass); + } + @Override @Nullable public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) { @@ -436,6 +461,18 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport protected Object createProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { + return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false); + } + + private Class createProxyClass(Class beanClass, @Nullable String beanName, + @Nullable Object[] specificInterceptors, TargetSource targetSource) { + + return (Class) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true); + } + + private Object buildProxy(Class beanClass, @Nullable String beanName, + @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) { + if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } @@ -477,7 +514,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); } - return proxyFactory.getProxy(classLoader); + return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader)); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 3a0ce71898..5b6f692c2c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -69,6 +69,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.beans.factory.support.LookupOverride; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RegisteredBean; @@ -311,44 +312,26 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA this.injectionMetadataCache.remove(beanName); } + @Override + public Class determineBeanType(Class beanClass, String beanName) throws BeanCreationException { + checkLookupMethods(beanClass, beanName); + + // Pick up subclass with fresh lookup method override from above + if (this.beanFactory instanceof AbstractAutowireCapableBeanFactory aacbf) { + RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName); + if (mbd.getFactoryMethodName() == null && mbd.hasBeanClass()) { + return aacbf.getInstantiationStrategy().getActualBeanClass(mbd, beanName, this.beanFactory); + } + } + return beanClass; + } + @Override @Nullable public Constructor[] determineCandidateConstructors(Class beanClass, final String beanName) throws BeanCreationException { - // Let's check for lookup methods here... - if (!this.lookupMethodsChecked.contains(beanName)) { - if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) { - try { - Class targetClass = beanClass; - do { - ReflectionUtils.doWithLocalMethods(targetClass, method -> { - Lookup lookup = method.getAnnotation(Lookup.class); - if (lookup != null) { - Assert.state(this.beanFactory != null, "No BeanFactory available"); - LookupOverride override = new LookupOverride(method, lookup.value()); - try { - RootBeanDefinition mbd = (RootBeanDefinition) - this.beanFactory.getMergedBeanDefinition(beanName); - mbd.getMethodOverrides().addOverride(override); - } - catch (NoSuchBeanDefinitionException ex) { - throw new BeanCreationException(beanName, - "Cannot apply @Lookup to beans without corresponding bean definition"); - } - } - }); - targetClass = targetClass.getSuperclass(); - } - while (targetClass != null && targetClass != Object.class); - - } - catch (IllegalStateException ex) { - throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); - } - } - this.lookupMethodsChecked.add(beanName); - } + checkLookupMethods(beanClass, beanName); // Quick check on the concurrent map first, with minimal locking. Constructor[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); @@ -450,6 +433,41 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA return (candidateConstructors.length > 0 ? candidateConstructors : null); } + private void checkLookupMethods(Class beanClass, final String beanName) throws BeanCreationException { + if (!this.lookupMethodsChecked.contains(beanName)) { + if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) { + try { + Class targetClass = beanClass; + do { + ReflectionUtils.doWithLocalMethods(targetClass, method -> { + Lookup lookup = method.getAnnotation(Lookup.class); + if (lookup != null) { + Assert.state(this.beanFactory != null, "No BeanFactory available"); + LookupOverride override = new LookupOverride(method, lookup.value()); + try { + RootBeanDefinition mbd = (RootBeanDefinition) + this.beanFactory.getMergedBeanDefinition(beanName); + mbd.getMethodOverrides().addOverride(override); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(beanName, + "Cannot apply @Lookup to beans without corresponding bean definition"); + } + } + }); + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); + } + } + this.lookupMethodsChecked.add(beanName); + } + } + @Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java index d4a6ce7f71..cd54203aea 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/SmartInstantiationAwareBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-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. @@ -28,12 +28,10 @@ import org.springframework.lang.Nullable; *

NOTE: This interface is a special purpose interface, mainly for * internal use within the framework. In general, application-provided * post-processors should simply implement the plain {@link BeanPostProcessor} - * interface or derive from the {@link InstantiationAwareBeanPostProcessorAdapter} - * class. New methods might be added to this interface even in point releases. + * interface. * * @author Juergen Hoeller * @since 2.0.3 - * @see InstantiationAwareBeanPostProcessorAdapter */ public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor { @@ -41,6 +39,8 @@ public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationA * Predict the type of the bean to be eventually returned from this * processor's {@link #postProcessBeforeInstantiation} callback. *

The default implementation returns {@code null}. + * Specific implementations should try to predict the bean type as + * far as known/cached already, without extra processing steps. * @param beanClass the raw class of the bean * @param beanName the name of the bean * @return the type of the bean, or {@code null} if not predictable @@ -51,6 +51,22 @@ public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationA return null; } + /** + * Determine the type of the bean to be eventually returned from this + * processor's {@link #postProcessBeforeInstantiation} callback. + *

The default implementation returns the given bean class as-is. + * Specific implementations should fully evaluate their processing steps + * in order to create/initialize a potential proxy class upfront. + * @param beanClass the raw class of the bean + * @param beanName the name of the bean + * @return the type of the bean (never {@code null}) + * @throws org.springframework.beans.BeansException in case of errors + * @since 6.0 + */ + default Class determineBeanType(Class beanClass, String beanName) throws BeansException { + return beanClass; + } + /** * Determine the candidate constructors to use for the given bean. *

The default implementation returns {@code null}. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index c836c373e3..331b633e6f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -70,7 +70,6 @@ import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.NamedThreadLocal; -import org.springframework.core.NativeDetector; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.PriorityOrdered; import org.springframework.core.ResolvableType; @@ -174,12 +173,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); - if (NativeDetector.inNativeImage()) { - this.instantiationStrategy = new SimpleInstantiationStrategy(); - } - else { - this.instantiationStrategy = new CglibSubclassingInstantiationStrategy(); - } + this.instantiationStrategy = new CglibSubclassingInstantiationStrategy(); } /** @@ -204,7 +198,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac /** * Return the instantiation strategy to use for creating bean instances. */ - protected InstantiationStrategy getInstantiationStrategy() { + public InstantiationStrategy getInstantiationStrategy() { return this.instantiationStrategy; } @@ -222,7 +216,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * names if needed. */ @Nullable - protected ParameterNameDiscoverer getParameterNameDiscoverer() { + public ParameterNameDiscoverer getParameterNameDiscoverer() { return this.parameterNameDiscoverer; } @@ -683,9 +677,15 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac protected Class determineTargetType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { Class targetType = mbd.getTargetType(); if (targetType == null) { - targetType = (mbd.getFactoryMethodName() != null ? - getTypeForFactoryMethod(beanName, mbd, typesToMatch) : - resolveBeanClass(mbd, beanName, typesToMatch)); + if (mbd.getFactoryMethodName() != null) { + targetType = getTypeForFactoryMethod(beanName, mbd, typesToMatch); + } + else { + targetType = resolveBeanClass(mbd, beanName, typesToMatch); + if (mbd.hasBeanClass()) { + targetType = getInstantiationStrategy().getActualBeanClass(mbd, beanName, this); + } + } if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) { mbd.resolvedTargetType = targetType; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 77f5301c48..39d7c180c5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -81,10 +81,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Constructor ctor, Object... args) { - // Must generate CGLIB subclass... return new CglibSubclassCreator(bd, owner).instantiate(ctor, args); } + @Override + public Class getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { + if (!bd.hasMethodOverrides()) { + return super.getActualBeanClass(bd, beanName, owner); + } + return new CglibSubclassCreator(bd, owner).createEnhancedSubclass(bd); + } + /** * An inner class created for historical reasons to avoid external CGLIB dependency @@ -142,10 +149,11 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt * Create an enhanced subclass of the bean class for the provided bean * definition, using CGLIB. */ - private Class createEnhancedSubclass(RootBeanDefinition beanDefinition) { + public Class createEnhancedSubclass(RootBeanDefinition beanDefinition) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(beanDefinition.getBeanClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); if (this.owner instanceof ConfigurableBeanFactory) { ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader(); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java index e9179585fc..450d85aa9e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-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. @@ -83,4 +83,12 @@ public interface InstantiationStrategy { @Nullable Object factoryBean, Method factoryMethod, Object... args) throws BeansException; + /** + * Determine the actual class for the given bean definition, as instantiated at runtime. + * @since 6.0 + */ + default Class getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { + return bd.getBeanClass(); + } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index b9b684ce74..37922364e5 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -422,6 +422,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { + String filename = resource.getFilename(); + if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { + // Ignore CGLIB-generated classes in the classpath + continue; + } if (traceEnabled) { logger.trace("Scanning " + resource); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 7130bdb511..e4d0d862c5 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -123,6 +123,7 @@ class ConfigurationClassEnhancer { enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); @@ -508,6 +509,7 @@ class ConfigurationClassEnhancer { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(factoryBean.getClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setCallbackType(MethodInterceptor.class); // Ideally create enhanced FactoryBean proxy without constructor side effects, diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 1419c1d40a..1443a92f27 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -64,7 +64,6 @@ import org.springframework.context.ApplicationStartupAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration; -import org.springframework.core.NativeDetector; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.env.Environment; @@ -286,11 +285,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } @Override - public BeanFactoryInitializationAotContribution processAheadOfTime( - ConfigurableListableBeanFactory beanFactory) { - - return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME) - ? new AotContribution(beanFactory) : null); + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME) ? new AotContribution(beanFactory) : null); } /** @@ -452,7 +448,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo configBeanDefs.put(beanName, abd); } } - if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) { + if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately enhanceConfigClasses.end(); return; @@ -514,8 +510,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE; - private static final ParameterizedTypeName STRING_STRING_MAP = ParameterizedTypeName - .get(Map.class, String.class, String.class); + private static final ParameterizedTypeName STRING_STRING_MAP = + ParameterizedTypeName.get(Map.class, String.class, String.class); private static final String MAPPINGS_VARIABLE = "mappings"; @@ -523,15 +519,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private static final String BEAN_NAME = "org.springframework.context.annotation.internalImportAwareAotProcessor"; - private final ConfigurableListableBeanFactory beanFactory; - public AotContribution(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; } - @Override public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { @@ -550,11 +543,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } } - private void generateAddPostProcessorMethod(MethodSpec.Builder method, - Map mappings) { - - method.addJavadoc( - "Add ImportAwareBeanPostProcessor to support ImportAware beans"); + private void generateAddPostProcessorMethod(MethodSpec.Builder method, Map mappings) { + method.addJavadoc("Add ImportAwareBeanPostProcessor to support ImportAware beans"); method.addModifiers(Modifier.PRIVATE); method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_VARIABLE); method.addCode(generateAddPostProcessorCode(mappings)); diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index 0cddc62692..dc332d97b8 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -18,6 +18,7 @@ package org.springframework.context.support; import java.io.IOException; import java.lang.reflect.Constructor; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -408,8 +410,29 @@ public class GenericApplicationContext extends AbstractApplicationContext implem invokeBeanFactoryPostProcessors(this.beanFactory); this.beanFactory.freezeConfiguration(); PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory); + preDetermineBeanTypes(); } + /** + * Pre-determine bean types in order to trigger early proxy class creation. + * @see org.springframework.beans.factory.BeanFactory#getType + * @see SmartInstantiationAwareBeanPostProcessor#determineBeanType + */ + private void preDetermineBeanTypes() { + List bpps = + PostProcessorRegistrationDelegate.loadBeanPostProcessors( + this.beanFactory, SmartInstantiationAwareBeanPostProcessor.class); + for (String beanName : this.beanFactory.getBeanDefinitionNames()) { + Class beanType = this.beanFactory.getType(beanName); + if (beanType != null) { + for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) { + beanType = bpp.determineBeanType(beanType, beanName); + } + } + } + } + + //--------------------------------------------------------------------- // Convenient methods for registering individual beans //--------------------------------------------------------------------- diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index efc33fcb4b..bb776c8383 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -135,6 +135,7 @@ jar { exclude "org/springframework/cglib/core/AsmApi*.class" exclude "org/springframework/cglib/core/KeyFactory.class" exclude "org/springframework/cglib/core/KeyFactory\$*.class" + exclude "org/springframework/cglib/core/MethodWrapper*.class" exclude "org/springframework/cglib/core/ReflectUtils*.class" exclude "org/springframework/cglib/proxy/Enhancer*.class" exclude "org/springframework/cglib/proxy/MethodProxy*.class" diff --git a/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java b/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java new file mode 100644 index 0000000000..119ffc48c6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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.cglib.core; + +import java.lang.reflect.Method; +import java.util.*; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class MethodWrapper { + + // SPRING PATCH BEGIN + private record MethodWrapperKey(String name, List parameterTypes, String returnType) { + } + // SPRING PATCH END + + private MethodWrapper() { + } + + public static Object create(Method method) { + // SPRING PATCH BEGIN + return new MethodWrapperKey(method.getName(), + Arrays.asList(ReflectUtils.getNames(method.getParameterTypes())), + method.getReturnType().getName()); + // SPRING PATCH END + } + + public static Set createSet(Collection methods) { + Set set = new HashSet(); + for (Iterator it = methods.iterator(); it.hasNext();) { + set.add(create((Method)it.next())); + } + return set; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java index 74b1ffe012..2fe4a85e68 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -20,12 +20,16 @@ import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; +import java.io.ByteArrayInputStream; +import java.io.OutputStream; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; @@ -444,6 +448,15 @@ public class ReflectUtils { Class c = null; Throwable t = THROWABLE; + String generatedClasses = System.getProperty("cglib.generatedClasses"); + if (generatedClasses != null) { + Path path = Path.of(generatedClasses + "/" + className.replace(".", "/") + ".class"); + Files.createDirectories(path.getParent()); + try (OutputStream os = Files.newOutputStream(path)) { + new ByteArrayInputStream(b).transferTo(os); + } + } + // Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches if (contextClass != null && contextClass.getClassLoader() == loader) { try { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java index 0266a2a009..a3ddcfe354 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-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. @@ -17,23 +17,36 @@ package org.springframework.cglib.core; /** - * Custom extension of CGLIB's {@link DefaultNamingPolicy}, modifying - * the tag in generated class names from "ByCGLIB" to "BySpringCGLIB". + * Custom variant of CGLIB's {@link DefaultNamingPolicy}, modifying the tag + * in generated class names from "EnhancerByCGLIB" etc to a "SpringCGLIB" tag + * and using a plain counter suffix instead of a hash code suffix (as of 6.0). * - *

This is primarily designed to avoid clashes between a regular CGLIB - * version (used by some other library) and Spring's embedded variant, - * in case the same class happens to get proxied for different purposes. + *

This allows for reliably discovering pre-generated Spring proxy classes + * in the classpath (as written at runtime when the "cglib.generatedClasses" + * system property points to a specific directory to store the proxy classes). * * @author Juergen Hoeller - * @since 3.2.8 + * @since 3.2.8 / 6.0 */ -public class SpringNamingPolicy extends DefaultNamingPolicy { +public final class SpringNamingPolicy implements NamingPolicy { public static final SpringNamingPolicy INSTANCE = new SpringNamingPolicy(); - @Override - protected String getTag() { - return "BySpringCGLIB"; + private SpringNamingPolicy() { + } + + public String getClassName(String prefix, String source, Object key, Predicate names) { + if (prefix == null) { + prefix = "org.springframework.cglib.empty.Object"; + } else if (prefix.startsWith("java")) { + prefix = "_" + prefix; + } + String base = prefix + "$$SpringCGLIB$$"; + int index = 0; + String attempt = base + index; + while (names.evaluate(attempt)) + attempt = base + index++; + return attempt; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java index 38920c8c21..d01914537e 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java @@ -100,9 +100,6 @@ public class Enhancer extends AbstractClassGenerator { private static final Source SOURCE = new Source(Enhancer.class.getName()); - private static final EnhancerKey KEY_FACTORY = - (EnhancerKey) KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null); - private static final String BOUND_FIELD = "CGLIB$BOUND"; private static final String FACTORY_DATA_FIELD = "CGLIB$FACTORY_DATA"; @@ -197,19 +194,16 @@ public class Enhancer extends AbstractClassGenerator { private Object currentKey; - /** - * Internal interface, only public due to ClassLoader issues. - */ - public interface EnhancerKey { - - public Object newInstance(String type, - String[] interfaces, + // SPRING PATCH BEGIN + private record EnhancerKey(String type, + List interfaces, WeakCacheKey filter, - Type[] callbackTypes, + List callbackTypes, boolean useFactory, boolean interceptDuringConstruction, - Long serialVersionUID); + Long serialVersionUID) { } + // SPRING PATCH END private Class[] interfaces; @@ -561,13 +555,15 @@ public class Enhancer extends AbstractClassGenerator { private Object createHelper() { preValidate(); - Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null, - ReflectUtils.getNames(interfaces), + // SPRING PATCH BEGIN + Object key = new EnhancerKey((superclass != null) ? superclass.getName() : null, + (interfaces != null ? Arrays.asList(ReflectUtils.getNames(interfaces)) :null), filter == ALL_ZERO ? null : new WeakCacheKey(filter), - callbackTypes, + Arrays.asList(callbackTypes), useFactory, interceptDuringConstruction, serialVersionUID); + // SPRING PATCH END this.currentKey = key; Object result = super.create(key); return result; diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java index 1abfbcfd8a..026e4a1162 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java @@ -660,6 +660,7 @@ public class ResolvableMethod { enhancer.setSuperclass(type); enhancer.setInterfaces(new Class[] {Supplier.class}); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); Class proxyClass = enhancer.createClass(); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java index 6ccef862b4..82f73b06ca 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java @@ -659,6 +659,7 @@ public class ResolvableMethod { enhancer.setSuperclass(type); enhancer.setInterfaces(new Class[] {Supplier.class}); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); Class proxyClass = enhancer.createClass(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index a59e21d3ed..2c087975af 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -786,6 +786,7 @@ public class MvcUriComponentsBuilder { enhancer.setSuperclass(controllerType); enhancer.setInterfaces(new Class[] {MethodInvocationInfo.class}); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setAttemptLoad(true); enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); Class proxyClass = enhancer.createClass();