ConfigurationClassPostProcessor detects covariant return type mismatch, avoiding infinite recursion
Issue: SPR-10261
This commit is contained in:
parent
c1c27e7142
commit
a86a77ad81
|
|
@ -17,17 +17,11 @@
|
||||||
package org.springframework.context.annotation;
|
package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.cglib.proxy.Callback;
|
|
||||||
import org.springframework.cglib.proxy.CallbackFilter;
|
|
||||||
import org.springframework.cglib.proxy.Enhancer;
|
|
||||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
|
||||||
import org.springframework.cglib.proxy.MethodProxy;
|
|
||||||
import org.springframework.cglib.proxy.NoOp;
|
|
||||||
|
|
||||||
import org.springframework.aop.scope.ScopedProxyFactoryBean;
|
import org.springframework.aop.scope.ScopedProxyFactoryBean;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
|
|
@ -35,6 +29,12 @@ import org.springframework.beans.factory.FactoryBean;
|
||||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
|
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
|
||||||
|
import org.springframework.cglib.proxy.Callback;
|
||||||
|
import org.springframework.cglib.proxy.CallbackFilter;
|
||||||
|
import org.springframework.cglib.proxy.Enhancer;
|
||||||
|
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||||
|
import org.springframework.cglib.proxy.MethodProxy;
|
||||||
|
import org.springframework.cglib.proxy.NoOp;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
@ -52,8 +52,8 @@ class ConfigurationClassEnhancer {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
|
private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
|
||||||
|
|
||||||
private static final Class<?>[] CALLBACK_TYPES = { BeanMethodInterceptor.class,
|
private static final Class<?>[] CALLBACK_TYPES = {BeanMethodInterceptor.class,
|
||||||
DisposableBeanMethodInterceptor.class, NoOp.class };
|
DisposableBeanMethodInterceptor.class, NoOp.class};
|
||||||
|
|
||||||
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
|
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
|
||||||
public int accept(Method candidateMethod) {
|
public int accept(Method candidateMethod) {
|
||||||
|
|
@ -80,10 +80,8 @@ class ConfigurationClassEnhancer {
|
||||||
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
|
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
|
||||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||||
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
|
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
|
||||||
this.callbackInstances = new Callback[] {
|
this.callbackInstances = new Callback[]
|
||||||
new BeanMethodInterceptor(beanFactory),
|
{new BeanMethodInterceptor(beanFactory), DISPOSABLE_BEAN_METHOD_INTERCEPTOR, NoOp.INSTANCE};
|
||||||
DISPOSABLE_BEAN_METHOD_INTERCEPTOR,
|
|
||||||
NoOp.INSTANCE };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -169,9 +167,8 @@ class ConfigurationClassEnhancer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
return beanFactory.getBean(beanName);
|
return this.beanFactory.getBean(this.beanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,19 +206,16 @@ class ConfigurationClassEnhancer {
|
||||||
*/
|
*/
|
||||||
private static class BeanMethodInterceptor implements MethodInterceptor {
|
private static class BeanMethodInterceptor implements MethodInterceptor {
|
||||||
|
|
||||||
private static final Class<?>[] CALLBACK_TYPES = {
|
private static final Class<?>[] CALLBACK_TYPES = {GetObjectMethodInterceptor.class, NoOp.class};
|
||||||
GetObjectMethodInterceptor.class, NoOp.class };
|
|
||||||
|
|
||||||
private static final CallbackFilter CALLBACK_FITLER = new CallbackFilter() {
|
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
|
||||||
public int accept(Method method) {
|
public int accept(Method method) {
|
||||||
return method.getName().equals("getObject") ? 0 : 1;
|
return (method.getName().equals("getObject") ? 0 : 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private final ConfigurableBeanFactory beanFactory;
|
private final ConfigurableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
|
public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
|
||||||
this.beanFactory = beanFactory;
|
this.beanFactory = beanFactory;
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +223,6 @@ class ConfigurationClassEnhancer {
|
||||||
/**
|
/**
|
||||||
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
|
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
|
||||||
* existence of this bean object.
|
* existence of this bean object.
|
||||||
*
|
|
||||||
* @throws Throwable as a catch-all for any exception that may be thrown when
|
* @throws Throwable as a catch-all for any exception that may be thrown when
|
||||||
* invoking the super implementation of the proxied method i.e., the actual
|
* invoking the super implementation of the proxied method i.e., the actual
|
||||||
* {@code @Bean} method.
|
* {@code @Bean} method.
|
||||||
|
|
@ -255,8 +248,8 @@ class ConfigurationClassEnhancer {
|
||||||
// proxy that intercepts calls to getObject() and returns any cached bean instance.
|
// proxy that intercepts calls to getObject() and returns any cached bean instance.
|
||||||
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
|
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
|
||||||
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
|
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
|
||||||
if (factoryContainsBean('&'+beanName) && factoryContainsBean(beanName)) {
|
if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) {
|
||||||
Object factoryBean = this.beanFactory.getBean('&'+beanName);
|
Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
|
||||||
if (factoryBean instanceof ScopedProxyFactoryBean) {
|
if (factoryBean instanceof ScopedProxyFactoryBean) {
|
||||||
// pass through - scoped proxy factory beans are a special case and should not
|
// pass through - scoped proxy factory beans are a special case and should not
|
||||||
// be further proxied
|
// be further proxied
|
||||||
|
|
@ -267,9 +260,7 @@ class ConfigurationClassEnhancer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean factoryIsCaller = beanMethod.equals(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
|
if (isCurrentlyInvokedFactoryMethod(beanMethod) && !this.beanFactory.containsSingleton(beanName)) {
|
||||||
boolean factoryAlreadyContainsSingleton = this.beanFactory.containsSingleton(beanName);
|
|
||||||
if (factoryIsCaller && !factoryAlreadyContainsSingleton) {
|
|
||||||
// the factory is calling the bean method in order to instantiate and register the bean
|
// the factory is calling the bean method in order to instantiate and register the bean
|
||||||
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
|
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
|
||||||
// create the bean instance.
|
// create the bean instance.
|
||||||
|
|
@ -306,7 +297,7 @@ class ConfigurationClassEnhancer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the beanFactory to see whether the bean named <var>beanName</var> already
|
* Check the BeanFactory to see whether the bean named <var>beanName</var> already
|
||||||
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
|
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
|
||||||
* we're in the middle of servicing the initial request for this bean. From an enhanced
|
* we're in the middle of servicing the initial request for this bean. From an enhanced
|
||||||
* factory method's perspective, this means that the bean does not actually yet exist,
|
* factory method's perspective, this means that the bean does not actually yet exist,
|
||||||
|
|
@ -319,9 +310,19 @@ class ConfigurationClassEnhancer {
|
||||||
* @return whether <var>beanName</var> already exists in the factory
|
* @return whether <var>beanName</var> already exists in the factory
|
||||||
*/
|
*/
|
||||||
private boolean factoryContainsBean(String beanName) {
|
private boolean factoryContainsBean(String beanName) {
|
||||||
boolean containsBean = this.beanFactory.containsBean(beanName);
|
return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName));
|
||||||
boolean currentlyInCreation = this.beanFactory.isCurrentlyInCreation(beanName);
|
}
|
||||||
return (containsBean && !currentlyInCreation);
|
|
||||||
|
/**
|
||||||
|
* Check whether the given method corresponds to the container's currently invoked
|
||||||
|
* factory method. Compares method name and parameter types only in order to work
|
||||||
|
* around a potential problem with covariant return types (currently only known
|
||||||
|
* to happen on Groovy classes).
|
||||||
|
*/
|
||||||
|
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
|
||||||
|
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
|
||||||
|
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
|
||||||
|
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -335,13 +336,12 @@ class ConfigurationClassEnhancer {
|
||||||
Enhancer enhancer = new Enhancer();
|
Enhancer enhancer = new Enhancer();
|
||||||
enhancer.setSuperclass(fbClass);
|
enhancer.setSuperclass(fbClass);
|
||||||
enhancer.setUseFactory(false);
|
enhancer.setUseFactory(false);
|
||||||
enhancer.setCallbackFilter(CALLBACK_FITLER);
|
enhancer.setCallbackFilter(CALLBACK_FILTER);
|
||||||
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
|
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
|
||||||
Callback[] callbackInstances = new Callback[] {
|
Callback[] callbackInstances = new Callback[] {
|
||||||
new GetObjectMethodInterceptor(this.beanFactory, beanName),
|
new GetObjectMethodInterceptor(this.beanFactory, beanName),
|
||||||
NoOp.INSTANCE
|
NoOp.INSTANCE
|
||||||
};
|
};
|
||||||
|
|
||||||
enhancer.setCallbackTypes(CALLBACK_TYPES);
|
enhancer.setCallbackTypes(CALLBACK_TYPES);
|
||||||
Class<?> fbSubclass = enhancer.createClass();
|
Class<?> fbSubclass = enhancer.createClass();
|
||||||
Enhancer.registerCallbacks(fbSubclass, callbackInstances);
|
Enhancer.registerCallbacks(fbSubclass, callbackInstances);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue