diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java new file mode 100644 index 0000000..b72baf5 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java @@ -0,0 +1,283 @@ +/* + * Copyright 2002-2018 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.beans.factory.config; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Simple template superclass for {@link FactoryBean} implementations that + * creates a singleton or a prototype object, depending on a flag. + * + *

If the "singleton" flag is {@code true} (the default), + * this class will create the object that it creates exactly once + * on initialization and subsequently return said singleton instance + * on all calls to the {@link #getObject()} method. + * + *

Else, this class will create a new instance every time the + * {@link #getObject()} method is invoked. Subclasses are responsible + * for implementing the abstract {@link #createInstance()} template + * method to actually create the object(s) to expose. + * + * @author Juergen Hoeller + * @author Keith Donald + * @since 1.0.2 + * @param the bean type + * @see #setSingleton + * @see #createInstance() + */ +public abstract class AbstractFactoryBean + implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean singleton = true; + + @Nullable + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + @Nullable + private BeanFactory beanFactory; + + private boolean initialized = false; + + @Nullable + private T singletonInstance; + + @Nullable + private T earlySingletonInstance; + + + /** + * Set if a singleton should be created, or a new object on each request + * otherwise. Default is {@code true} (a singleton). + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + @Override + public boolean isSingleton() { + return this.singleton; + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + @Override + public void setBeanFactory(@Nullable BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * Return the BeanFactory that this bean runs in. + */ + @Nullable + protected BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Obtain a bean type converter from the BeanFactory that this bean + * runs in. This is typically a fresh instance for each call, + * since TypeConverters are usually not thread-safe. + *

Falls back to a SimpleTypeConverter when not running in a BeanFactory. + * @see ConfigurableBeanFactory#getTypeConverter() + * @see org.springframework.beans.SimpleTypeConverter + */ + protected TypeConverter getBeanTypeConverter() { + BeanFactory beanFactory = getBeanFactory(); + if (beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) beanFactory).getTypeConverter(); + } + else { + return new SimpleTypeConverter(); + } + } + + /** + * Eagerly create the singleton instance, if necessary. + */ + @Override + public void afterPropertiesSet() throws Exception { + if (isSingleton()) { + this.initialized = true; + this.singletonInstance = createInstance(); + this.earlySingletonInstance = null; + } + } + + + /** + * Expose the singleton instance or create a new prototype instance. + * @see #createInstance() + * @see #getEarlySingletonInterfaces() + */ + @Override + public final T getObject() throws Exception { + if (isSingleton()) { + return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); + } + else { + return createInstance(); + } + } + + /** + * Determine an 'early singleton' instance, exposed in case of a + * circular reference. Not called in a non-circular scenario. + */ + @SuppressWarnings("unchecked") + private T getEarlySingletonInstance() throws Exception { + Class[] ifcs = getEarlySingletonInterfaces(); + if (ifcs == null) { + throw new FactoryBeanNotInitializedException( + getClass().getName() + " does not support circular references"); + } + if (this.earlySingletonInstance == null) { + this.earlySingletonInstance = (T) Proxy.newProxyInstance( + this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler()); + } + return this.earlySingletonInstance; + } + + /** + * Expose the singleton instance (for access through the 'early singleton' proxy). + * @return the singleton instance that this FactoryBean holds + * @throws IllegalStateException if the singleton instance is not initialized + */ + @Nullable + private T getSingletonInstance() throws IllegalStateException { + Assert.state(this.initialized, "Singleton instance not initialized yet"); + return this.singletonInstance; + } + + /** + * Destroy the singleton instance, if any. + * @see #destroyInstance(Object) + */ + @Override + public void destroy() throws Exception { + if (isSingleton()) { + destroyInstance(this.singletonInstance); + } + } + + + /** + * This abstract method declaration mirrors the method in the FactoryBean + * interface, for a consistent offering of abstract template methods. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + @Nullable + public abstract Class getObjectType(); + + /** + * Template method that subclasses must override to construct + * the object returned by this factory. + *

Invoked on initialization of this FactoryBean in case of + * a singleton; else, on each {@link #getObject()} call. + * @return the object returned by this factory + * @throws Exception if an exception occurred during object creation + * @see #getObject() + */ + protected abstract T createInstance() throws Exception; + + /** + * Return an array of interfaces that a singleton object exposed by this + * FactoryBean is supposed to implement, for use with an 'early singleton + * proxy' that will be exposed in case of a circular reference. + *

The default implementation returns this FactoryBean's object type, + * provided that it is an interface, or {@code null} otherwise. The latter + * indicates that early singleton access is not supported by this FactoryBean. + * This will lead to a FactoryBeanNotInitializedException getting thrown. + * @return the interfaces to use for 'early singletons', + * or {@code null} to indicate a FactoryBeanNotInitializedException + * @see org.springframework.beans.factory.FactoryBeanNotInitializedException + */ + @Nullable + protected Class[] getEarlySingletonInterfaces() { + Class type = getObjectType(); + return (type != null && type.isInterface() ? new Class[] {type} : null); + } + + /** + * Callback for destroying a singleton instance. Subclasses may + * override this to destroy the previously created instance. + *

The default implementation is empty. + * @param instance the singleton instance, as returned by + * {@link #createInstance()} + * @throws Exception in case of shutdown errors + * @see #createInstance() + */ + protected void destroyInstance(@Nullable T instance) throws Exception { + } + + + /** + * Reflective InvocationHandler for lazy access to the actual singleton object. + */ + private class EarlySingletonInvocationHandler implements InvocationHandler { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isEqualsMethod(method)) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (ReflectionUtils.isHashCodeMethod(method)) { + // Use hashCode of reference proxy. + return System.identityHashCode(proxy); + } + else if (!initialized && ReflectionUtils.isToStringMethod(method)) { + return "Early singleton proxy for interfaces " + + ObjectUtils.nullSafeToString(getEarlySingletonInterfaces()); + } + try { + return method.invoke(getSingletonInstance(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java new file mode 100644 index 0000000..69f4909 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanReference.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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.beans.factory.config; + +import org.springframework.beans.BeanMetadataElement; + +/** + * Interface that exposes a reference to a bean name in an abstract fashion. + * This interface does not necessarily imply a reference to an actual bean + * instance; it just expresses a logical reference to the name of a bean. + * + *

Serves as common interface implemented by any kind of bean reference + * holder, such as {@link RuntimeBeanReference RuntimeBeanReference} and + * {@link RuntimeBeanNameReference RuntimeBeanNameReference}. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public interface BeanReference extends BeanMetadataElement { + + /** + * Return the target bean name that this reference points to (never {@code null}). + */ + String getBeanName(); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DeprecatedBeanWarner.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DeprecatedBeanWarner.java new file mode 100644 index 0000000..6a1369a --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DeprecatedBeanWarner.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2017 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.beans.factory.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Bean factory post processor that logs a warning for {@link Deprecated @Deprecated} beans. + * + * @author Arjen Poutsma + * @since 3.0.3 + */ +public class DeprecatedBeanWarner implements BeanFactoryPostProcessor { + + /** + * Logger available to subclasses. + */ + protected transient Log logger = LogFactory.getLog(getClass()); + + /** + * Set the name of the logger to use. + * The name will be passed to the underlying logger implementation through Commons Logging, + * getting interpreted as log category according to the logger's configuration. + *

This can be specified to not log into the category of this warner class but rather + * into a specific named category. + * @see org.apache.commons.logging.LogFactory#getLog(String) + * @see java.util.logging.Logger#getLogger(String) + */ + public void setLoggerName(String loggerName) { + this.logger = LogFactory.getLog(loggerName); + } + + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (isLogEnabled()) { + String[] beanNames = beanFactory.getBeanDefinitionNames(); + for (String beanName : beanNames) { + String nameToLookup = beanName; + if (beanFactory.isFactoryBean(beanName)) { + nameToLookup = BeanFactory.FACTORY_BEAN_PREFIX + beanName; + } + Class beanType = beanFactory.getType(nameToLookup); + if (beanType != null) { + Class userClass = ClassUtils.getUserClass(beanType); + if (userClass.isAnnotationPresent(Deprecated.class)) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + logDeprecatedBean(beanName, beanType, beanDefinition); + } + } + } + } + } + + /** + * Logs a warning for a bean annotated with {@link Deprecated @Deprecated}. + * @param beanName the name of the deprecated bean + * @param beanType the user-specified type of the deprecated bean + * @param beanDefinition the definition of the deprecated bean + */ + protected void logDeprecatedBean(String beanName, Class beanType, BeanDefinition beanDefinition) { + StringBuilder builder = new StringBuilder(); + builder.append(beanType); + builder.append(" ['"); + builder.append(beanName); + builder.append('\''); + String resourceDescription = beanDefinition.getResourceDescription(); + if (StringUtils.hasLength(resourceDescription)) { + builder.append(" in "); + builder.append(resourceDescription); + } + builder.append("] has been deprecated"); + writeToLog(builder.toString()); + } + + /** + * Actually write to the underlying log. + *

The default implementations logs the message at "warn" level. + * @param message the message to write + */ + protected void writeToLog(String message) { + logger.warn(message); + } + + /** + * Determine whether the {@link #logger} field is enabled. + *

Default is {@code true} when the "warn" level is enabled. + * Subclasses can override this to change the level under which logging occurs. + */ + protected boolean isLogEnabled() { + return logger.isWarnEnabled(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java new file mode 100644 index 0000000..86a6be9 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2018 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.beans.factory.config; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.lang.Nullable; + +/** + * {@link FactoryBean} which returns a value which is the result of a static or instance + * method invocation. For most use cases it is better to just use the container's + * built-in factory method support for the same purpose, since that is smarter at + * converting arguments. This factory bean is still useful though when you need to + * call a method which doesn't return any value (for example, a static class method + * to force some sort of initialization to happen). This use case is not supported + * by factory methods, since a return value is needed to obtain the bean instance. + * + *

Note that as it is expected to be used mostly for accessing factory methods, + * this factory by default operates in a singleton fashion. The first request + * to {@link #getObject} by the owning bean factory will cause a method invocation, + * whose return value will be cached for subsequent requests. An internal + * {@link #setSingleton singleton} property may be set to "false", to cause this + * factory to invoke the target method each time it is asked for an object. + * + *

NOTE: If your target method does not produce a result to expose, consider + * {@link MethodInvokingBean} instead, which avoids the type determination and + * lifecycle limitations that this {@link MethodInvokingFactoryBean} comes with. + * + *

This invoker supports any kind of target method. A static method may be specified + * by setting the {@link #setTargetMethod targetMethod} property to a String representing + * the static method name, with {@link #setTargetClass targetClass} specifying the Class + * that the static method is defined on. Alternatively, a target instance method may be + * specified, by setting the {@link #setTargetObject targetObject} property as the target + * object, and the {@link #setTargetMethod targetMethod} property as the name of the + * method to call on that target object. Arguments for the method invocation may be + * specified by setting the {@link #setArguments arguments} property. + * + *

This class depends on {@link #afterPropertiesSet()} being called once + * all properties have been set, as per the InitializingBean contract. + * + *

An example (in an XML based bean factory definition) of a bean definition + * which uses this class to call a static factory method: + * + *

+ * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="staticMethod" value="com.whatever.MyClassFactory.getInstance"/>
+ * </bean>
+ * + *

An example of calling a static method then an instance method to get at a + * Java system property. Somewhat verbose, but it works. + * + *

+ * <bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="targetClass" value="java.lang.System"/>
+ *   <property name="targetMethod" value="getProperties"/>
+ * </bean>
+ *
+ * <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ *   <property name="targetObject" ref="sysProps"/>
+ *   <property name="targetMethod" value="getProperty"/>
+ *   <property name="arguments" value="java.version"/>
+ * </bean>
+ * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 21.11.2003 + * @see MethodInvokingBean + * @see org.springframework.util.MethodInvoker + */ +public class MethodInvokingFactoryBean extends MethodInvokingBean implements FactoryBean { + + private boolean singleton = true; + + private boolean initialized = false; + + /** Method call result in the singleton case. */ + @Nullable + private Object singletonObject; + + + /** + * Set if a singleton should be created, or a new object on each + * {@link #getObject()} request otherwise. Default is "true". + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + @Override + public void afterPropertiesSet() throws Exception { + prepare(); + if (this.singleton) { + this.initialized = true; + this.singletonObject = invokeWithTargetException(); + } + } + + + /** + * Returns the same value each time if the singleton property is set + * to "true", otherwise returns the value returned from invoking the + * specified method on the fly. + */ + @Override + @Nullable + public Object getObject() throws Exception { + if (this.singleton) { + if (!this.initialized) { + throw new FactoryBeanNotInitializedException(); + } + // Singleton: return shared object. + return this.singletonObject; + } + else { + // Prototype: new object on each call. + return invokeWithTargetException(); + } + } + + /** + * Return the type of object that this FactoryBean creates, + * or {@code null} if not known in advance. + */ + @Override + public Class getObjectType() { + if (!isPrepared()) { + // Not fully initialized yet -> return null to indicate "not known yet". + return null; + } + return getPreparedMethod().getReturnType(); + } + + @Override + public boolean isSingleton() { + return this.singleton; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java new file mode 100644 index 0000000..e1f9208 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2017 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.beans.factory.config; + +import java.io.Serializable; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link org.springframework.beans.factory.FactoryBean} implementation that + * returns a value which is an {@link org.springframework.beans.factory.ObjectFactory} + * that in turn returns a bean sourced from a {@link org.springframework.beans.factory.BeanFactory}. + * + *

As such, this may be used to avoid having a client object directly calling + * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} to get + * a (typically prototype) bean from a + * {@link org.springframework.beans.factory.BeanFactory}, which would be a + * violation of the inversion of control principle. Instead, with the use + * of this class, the client object can be fed an + * {@link org.springframework.beans.factory.ObjectFactory} instance as a + * property which directly returns only the one target bean (again, which is + * typically a prototype bean). + * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype bean since we have state -->
+ *   <bean id="myService" class="a.b.c.MyService" scope="prototype"/>
+ *
+ *   <bean id="myServiceFactory"
+ *       class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
+ *     <property name="targetBeanName"><idref local="myService"/></property>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant {@code MyClientBean} class implementation might look + * something like this: + * + *

package a.b.c;
+ *
+ * import org.springframework.beans.factory.ObjectFactory;
+ *
+ * public class MyClientBean {
+ *
+ *   private ObjectFactory<MyService> myServiceFactory;
+ *
+ *   public void setMyServiceFactory(ObjectFactory<MyService> myServiceFactory) {
+ *     this.myServiceFactory = myServiceFactory;
+ *   }
+ *
+ *   public void someBusinessMethod() {
+ *     // get a 'fresh', brand new MyService instance
+ *     MyService service = this.myServiceFactory.getObject();
+ *     // use the service object to effect the business logic...
+ *   }
+ * }
+ * + *

An alternate approach to this application of an object creational pattern + * would be to use the {@link ServiceLocatorFactoryBean} + * to source (prototype) beans. The {@link ServiceLocatorFactoryBean} approach + * has the advantage of the fact that one doesn't have to depend on any + * Spring-specific interface such as {@link org.springframework.beans.factory.ObjectFactory}, + * but has the disadvantage of requiring runtime class generation. Please do + * consult the {@link ServiceLocatorFactoryBean ServiceLocatorFactoryBean JavaDoc} + * for a fuller discussion of this issue. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.0.2 + * @see org.springframework.beans.factory.ObjectFactory + * @see ServiceLocatorFactoryBean + */ +public class ObjectFactoryCreatingFactoryBean extends AbstractFactoryBean> { + + @Nullable + private String targetBeanName; + + + /** + * Set the name of the target bean. + *

The target does not have to be a non-singleton bean, but realistically + * always will be (because if the target bean were a singleton, then said singleton + * bean could simply be injected straight into the dependent object, thus obviating + * the need for the extra level of indirection afforded by this factory approach). + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.hasText(this.targetBeanName, "Property 'targetBeanName' is required"); + super.afterPropertiesSet(); + } + + + @Override + public Class getObjectType() { + return ObjectFactory.class; + } + + @Override + protected ObjectFactory createInstance() { + BeanFactory beanFactory = getBeanFactory(); + Assert.state(beanFactory != null, "No BeanFactory available"); + Assert.state(this.targetBeanName != null, "No target bean name specified"); + return new TargetBeanObjectFactory(beanFactory, this.targetBeanName); + } + + + /** + * Independent inner class - for serialization purposes. + */ + @SuppressWarnings("serial") + private static class TargetBeanObjectFactory implements ObjectFactory, Serializable { + + private final BeanFactory beanFactory; + + private final String targetBeanName; + + public TargetBeanObjectFactory(BeanFactory beanFactory, String targetBeanName) { + this.beanFactory = beanFactory; + this.targetBeanName = targetBeanName; + } + + @Override + public Object getObject() throws BeansException { + return this.beanFactory.getBean(this.targetBeanName); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java new file mode 100644 index 0000000..fc616b8 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PreferencesPlaceholderConfigurer.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2019 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.beans.factory.config; + +import java.util.Properties; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.lang.Nullable; + +/** + * Subclass of PropertyPlaceholderConfigurer that supports JDK 1.4's + * Preferences API ({@code java.util.prefs}). + * + *

Tries to resolve placeholders as keys first in the user preferences, + * then in the system preferences, then in this configurer's properties. + * Thus, behaves like PropertyPlaceholderConfigurer if no corresponding + * preferences defined. + * + *

Supports custom paths for the system and user preferences trees. Also + * supports custom paths specified in placeholders ("myPath/myPlaceholderKey"). + * Uses the respective root node if not specified. + * + * @author Juergen Hoeller + * @since 16.02.2004 + * @see #setSystemTreePath + * @see #setUserTreePath + * @see java.util.prefs.Preferences + * @deprecated as of 5.2, along with {@link PropertyPlaceholderConfigurer} + */ +@Deprecated +public class PreferencesPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements InitializingBean { + + @Nullable + private String systemTreePath; + + @Nullable + private String userTreePath; + + private Preferences systemPrefs = Preferences.systemRoot(); + + private Preferences userPrefs = Preferences.userRoot(); + + + /** + * Set the path in the system preferences tree to use for resolving + * placeholders. Default is the root node. + */ + public void setSystemTreePath(String systemTreePath) { + this.systemTreePath = systemTreePath; + } + + /** + * Set the path in the system preferences tree to use for resolving + * placeholders. Default is the root node. + */ + public void setUserTreePath(String userTreePath) { + this.userTreePath = userTreePath; + } + + + /** + * This implementation eagerly fetches the Preferences instances + * for the required system and user tree nodes. + */ + @Override + public void afterPropertiesSet() { + if (this.systemTreePath != null) { + this.systemPrefs = this.systemPrefs.node(this.systemTreePath); + } + if (this.userTreePath != null) { + this.userPrefs = this.userPrefs.node(this.userTreePath); + } + } + + /** + * This implementation tries to resolve placeholders as keys first + * in the user preferences, then in the system preferences, then in + * the passed-in properties. + */ + @Override + protected String resolvePlaceholder(String placeholder, Properties props) { + String path = null; + String key = placeholder; + int endOfPath = placeholder.lastIndexOf('/'); + if (endOfPath != -1) { + path = placeholder.substring(0, endOfPath); + key = placeholder.substring(endOfPath + 1); + } + String value = resolvePlaceholder(path, key, this.userPrefs); + if (value == null) { + value = resolvePlaceholder(path, key, this.systemPrefs); + if (value == null) { + value = props.getProperty(placeholder); + } + } + return value; + } + + /** + * Resolve the given path and key against the given Preferences. + * @param path the preferences path (placeholder part before '/') + * @param key the preferences key (placeholder part after '/') + * @param preferences the Preferences to resolve against + * @return the value for the placeholder, or {@code null} if none found + */ + @Nullable + protected String resolvePlaceholder(@Nullable String path, String key, Preferences preferences) { + if (path != null) { + // Do not create the node if it does not exist... + try { + if (preferences.nodeExists(path)) { + return preferences.node(path).get(key, null); + } + else { + return null; + } + } + catch (BackingStoreException ex) { + throw new BeanDefinitionStoreException("Cannot access specified node path [" + path + "]", ex); + } + } + else { + return preferences.get(key, null); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java new file mode 100644 index 0000000..805f6bb --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanNameReference.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2017 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.beans.factory.config; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Immutable placeholder class used for a property value object when it's a + * reference to another bean name in the factory, to be resolved at runtime. + * + * @author Juergen Hoeller + * @since 2.0 + * @see RuntimeBeanReference + * @see BeanDefinition#getPropertyValues() + * @see org.springframework.beans.factory.BeanFactory#getBean + */ +public class RuntimeBeanNameReference implements BeanReference { + + private final String beanName; + + @Nullable + private Object source; + + + /** + * Create a new RuntimeBeanNameReference to the given bean name. + * @param beanName name of the target bean + */ + public RuntimeBeanNameReference(String beanName) { + Assert.hasText(beanName, "'beanName' must not be empty"); + this.beanName = beanName; + } + + @Override + public String getBeanName() { + return this.beanName; + } + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof RuntimeBeanNameReference)) { + return false; + } + RuntimeBeanNameReference that = (RuntimeBeanNameReference) other; + return this.beanName.equals(that.beanName); + } + + @Override + public int hashCode() { + return this.beanName.hashCode(); + } + + @Override + public String toString() { + return '<' + getBeanName() + '>'; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java new file mode 100644 index 0000000..e0ee01e --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java @@ -0,0 +1,427 @@ +/* + * Copyright 2002-2017 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.beans.factory.config; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Properties; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * A {@link FactoryBean} implementation that takes an interface which must have one or more + * methods with the signatures {@code MyType xxx()} or {@code MyType xxx(MyIdType id)} + * (typically, {@code MyService getService()} or {@code MyService getService(String id)}) + * and creates a dynamic proxy which implements that interface, delegating to an + * underlying {@link org.springframework.beans.factory.BeanFactory}. + * + *

Such service locators permit the decoupling of calling code from + * the {@link org.springframework.beans.factory.BeanFactory} API, by using an + * appropriate custom locator interface. They will typically be used for + * prototype beans, i.e. for factory methods that are supposed to + * return a new instance for each call. The client receives a reference to the + * service locator via setter or constructor injection, to be able to invoke + * the locator's factory methods on demand. For singleton beans, direct + * setter or constructor injection of the target bean is preferable. + * + *

On invocation of the no-arg factory method, or the single-arg factory + * method with a String id of {@code null} or empty String, if exactly + * one bean in the factory matches the return type of the factory + * method, that bean is returned, otherwise a + * {@link org.springframework.beans.factory.NoSuchBeanDefinitionException} + * is thrown. + * + *

On invocation of the single-arg factory method with a non-null (and + * non-empty) argument, the proxy returns the result of a + * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} call, + * using a stringified version of the passed-in id as bean name. + * + *

A factory method argument will usually be a String, but can also be an + * int or a custom enumeration type, for example, stringified via + * {@code toString}. The resulting String can be used as bean name as-is, + * provided that corresponding beans are defined in the bean factory. + * Alternatively, {@linkplain #setServiceMappings(java.util.Properties) a custom + * mapping} between service IDs and bean names can be defined. + * + *

By way of an example, consider the following service locator interface. + * Note that this interface is not dependent on any Spring APIs. + * + *

package a.b.c;
+ *
+ *public interface ServiceFactory {
+ *
+ *    public MyService getService();
+ *}
+ * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype bean since we have state -->
+ *   <bean id="myService" class="a.b.c.MyService" singleton="false"/>
+ *
+ *   <!-- will lookup the above 'myService' bean by *TYPE* -->
+ *   <bean id="myServiceFactory"
+ *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
+ *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant {@code MyClientBean} class implementation might then + * look something like this: + * + *

package a.b.c;
+ *
+ *public class MyClientBean {
+ *
+ *    private ServiceFactory myServiceFactory;
+ *
+ *    // actual implementation provided by the Spring container
+ *    public void setServiceFactory(ServiceFactory myServiceFactory) {
+ *        this.myServiceFactory = myServiceFactory;
+ *    }
+ *
+ *    public void someBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService();
+ *        // use the service object to effect the business logic...
+ *    }
+ *}
+ * + *

By way of an example that looks up a bean by name, consider + * the following service locator interface. Again, note that this + * interface is not dependent on any Spring APIs. + * + *

package a.b.c;
+ *
+ *public interface ServiceFactory {
+ *
+ *    public MyService getService (String serviceName);
+ *}
+ * + *

A sample config in an XML-based + * {@link org.springframework.beans.factory.BeanFactory} might look as follows: + * + *

<beans>
+ *
+ *   <!-- Prototype beans since we have state (both extend MyService) -->
+ *   <bean id="specialService" class="a.b.c.SpecialService" singleton="false"/>
+ *   <bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/>
+ *
+ *   <bean id="myServiceFactory"
+ *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
+ *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
+ *   </bean>
+ *
+ *   <bean id="clientBean" class="a.b.c.MyClientBean">
+ *     <property name="myServiceFactory" ref="myServiceFactory"/>
+ *   </bean>
+ *
+ *</beans>
+ * + *

The attendant {@code MyClientBean} class implementation might then + * look something like this: + * + *

package a.b.c;
+ *
+ *public class MyClientBean {
+ *
+ *    private ServiceFactory myServiceFactory;
+ *
+ *    // actual implementation provided by the Spring container
+ *    public void setServiceFactory(ServiceFactory myServiceFactory) {
+ *        this.myServiceFactory = myServiceFactory;
+ *    }
+ *
+ *    public void someBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService("specialService");
+ *        // use the service object to effect the business logic...
+ *    }
+ *
+ *    public void anotherBusinessMethod() {
+ *        // get a 'fresh', brand new MyService instance
+ *        MyService service = this.myServiceFactory.getService("anotherService");
+ *        // use the service object to effect the business logic...
+ *    }
+ *}
+ * + *

See {@link ObjectFactoryCreatingFactoryBean} for an alternate approach. + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @since 1.1.4 + * @see #setServiceLocatorInterface + * @see #setServiceMappings + * @see ObjectFactoryCreatingFactoryBean + */ +public class ServiceLocatorFactoryBean implements FactoryBean, BeanFactoryAware, InitializingBean { + + @Nullable + private Class serviceLocatorInterface; + + @Nullable + private Constructor serviceLocatorExceptionConstructor; + + @Nullable + private Properties serviceMappings; + + @Nullable + private ListableBeanFactory beanFactory; + + @Nullable + private Object proxy; + + + /** + * Set the service locator interface to use, which must have one or more methods with + * the signatures {@code MyType xxx()} or {@code MyType xxx(MyIdType id)} + * (typically, {@code MyService getService()} or {@code MyService getService(String id)}). + * See the {@link ServiceLocatorFactoryBean class-level Javadoc} for + * information on the semantics of such methods. + */ + public void setServiceLocatorInterface(Class interfaceType) { + this.serviceLocatorInterface = interfaceType; + } + + /** + * Set the exception class that the service locator should throw if service + * lookup failed. The specified exception class must have a constructor + * with one of the following parameter types: {@code (String, Throwable)} + * or {@code (Throwable)} or {@code (String)}. + *

If not specified, subclasses of Spring's BeansException will be thrown, + * for example NoSuchBeanDefinitionException. As those are unchecked, the + * caller does not need to handle them, so it might be acceptable that + * Spring exceptions get thrown as long as they are just handled generically. + * @see #determineServiceLocatorExceptionConstructor + * @see #createServiceLocatorException + */ + public void setServiceLocatorExceptionClass(Class serviceLocatorExceptionClass) { + this.serviceLocatorExceptionConstructor = + determineServiceLocatorExceptionConstructor(serviceLocatorExceptionClass); + } + + /** + * Set mappings between service ids (passed into the service locator) + * and bean names (in the bean factory). Service ids that are not defined + * here will be treated as bean names as-is. + *

The empty string as service id key defines the mapping for {@code null} and + * empty string, and for factory methods without parameter. If not defined, + * a single matching bean will be retrieved from the bean factory. + * @param serviceMappings mappings between service ids and bean names, + * with service ids as keys as bean names as values + */ + public void setServiceMappings(Properties serviceMappings) { + this.serviceMappings = serviceMappings; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new FatalBeanException( + "ServiceLocatorFactoryBean needs to run in a BeanFactory that is a ListableBeanFactory"); + } + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + @Override + public void afterPropertiesSet() { + if (this.serviceLocatorInterface == null) { + throw new IllegalArgumentException("Property 'serviceLocatorInterface' is required"); + } + + // Create service locator proxy. + this.proxy = Proxy.newProxyInstance( + this.serviceLocatorInterface.getClassLoader(), + new Class[] {this.serviceLocatorInterface}, + new ServiceLocatorInvocationHandler()); + } + + + /** + * Determine the constructor to use for the given service locator exception + * class. Only called in case of a custom service locator exception. + *

The default implementation looks for a constructor with one of the + * following parameter types: {@code (String, Throwable)} + * or {@code (Throwable)} or {@code (String)}. + * @param exceptionClass the exception class + * @return the constructor to use + * @see #setServiceLocatorExceptionClass + */ + @SuppressWarnings("unchecked") + protected Constructor determineServiceLocatorExceptionConstructor(Class exceptionClass) { + try { + return (Constructor) exceptionClass.getConstructor(String.class, Throwable.class); + } + catch (NoSuchMethodException ex) { + try { + return (Constructor) exceptionClass.getConstructor(Throwable.class); + } + catch (NoSuchMethodException ex2) { + try { + return (Constructor) exceptionClass.getConstructor(String.class); + } + catch (NoSuchMethodException ex3) { + throw new IllegalArgumentException( + "Service locator exception [" + exceptionClass.getName() + + "] neither has a (String, Throwable) constructor nor a (String) constructor"); + } + } + } + } + + /** + * Create a service locator exception for the given cause. + * Only called in case of a custom service locator exception. + *

The default implementation can handle all variations of + * message and exception arguments. + * @param exceptionConstructor the constructor to use + * @param cause the cause of the service lookup failure + * @return the service locator exception to throw + * @see #setServiceLocatorExceptionClass + */ + protected Exception createServiceLocatorException(Constructor exceptionConstructor, BeansException cause) { + Class[] paramTypes = exceptionConstructor.getParameterTypes(); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + if (String.class == paramTypes[i]) { + args[i] = cause.getMessage(); + } + else if (paramTypes[i].isInstance(cause)) { + args[i] = cause; + } + } + return BeanUtils.instantiateClass(exceptionConstructor, args); + } + + + @Override + @Nullable + public Object getObject() { + return this.proxy; + } + + @Override + public Class getObjectType() { + return this.serviceLocatorInterface; + } + + @Override + public boolean isSingleton() { + return true; + } + + + /** + * Invocation handler that delegates service locator calls to the bean factory. + */ + private class ServiceLocatorInvocationHandler implements InvocationHandler { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (ReflectionUtils.isEqualsMethod(method)) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (ReflectionUtils.isHashCodeMethod(method)) { + // Use hashCode of service locator proxy. + return System.identityHashCode(proxy); + } + else if (ReflectionUtils.isToStringMethod(method)) { + return "Service locator: " + serviceLocatorInterface; + } + else { + return invokeServiceLocatorMethod(method, args); + } + } + + private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception { + Class serviceLocatorMethodReturnType = getServiceLocatorMethodReturnType(method); + try { + String beanName = tryGetBeanName(args); + Assert.state(beanFactory != null, "No BeanFactory available"); + if (StringUtils.hasLength(beanName)) { + // Service locator for a specific bean name + return beanFactory.getBean(beanName, serviceLocatorMethodReturnType); + } + else { + // Service locator for a bean type + return beanFactory.getBean(serviceLocatorMethodReturnType); + } + } + catch (BeansException ex) { + if (serviceLocatorExceptionConstructor != null) { + throw createServiceLocatorException(serviceLocatorExceptionConstructor, ex); + } + throw ex; + } + } + + /** + * Check whether a service id was passed in. + */ + private String tryGetBeanName(@Nullable Object[] args) { + String beanName = ""; + if (args != null && args.length == 1 && args[0] != null) { + beanName = args[0].toString(); + } + // Look for explicit serviceId-to-beanName mappings. + if (serviceMappings != null) { + String mappedName = serviceMappings.getProperty(beanName); + if (mappedName != null) { + beanName = mappedName; + } + } + return beanName; + } + + private Class getServiceLocatorMethodReturnType(Method method) throws NoSuchMethodException { + Assert.state(serviceLocatorInterface != null, "No service locator interface specified"); + Class[] paramTypes = method.getParameterTypes(); + Method interfaceMethod = serviceLocatorInterface.getMethod(method.getName(), paramTypes); + Class serviceLocatorReturnType = interfaceMethod.getReturnType(); + + // Check whether the method is a valid service locator. + if (paramTypes.length > 1 || void.class == serviceLocatorReturnType) { + throw new UnsupportedOperationException( + "May only call methods with signature ' xxx()' or ' xxx( id)' " + + "on factory interface, but tried to call: " + interfaceMethod); + } + return serviceLocatorReturnType; + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java new file mode 100644 index 0000000..756cc1c --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2017 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.beans.factory.config; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.TypeConverter; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; + +/** + * Simple factory for shared Set instances. Allows for central setup + * of Sets via the "set" element in XML bean definitions. + * + * @author Juergen Hoeller + * @since 09.12.2003 + * @see ListFactoryBean + * @see MapFactoryBean + */ +public class SetFactoryBean extends AbstractFactoryBean> { + + @Nullable + private Set sourceSet; + + @SuppressWarnings("rawtypes") + @Nullable + private Class targetSetClass; + + + /** + * Set the source Set, typically populated via XML "set" elements. + */ + public void setSourceSet(Set sourceSet) { + this.sourceSet = sourceSet; + } + + /** + * Set the class to use for the target Set. Can be populated with a fully + * qualified class name when defined in a Spring application context. + *

Default is a linked HashSet, keeping the registration order. + * @see java.util.LinkedHashSet + */ + @SuppressWarnings("rawtypes") + public void setTargetSetClass(@Nullable Class targetSetClass) { + if (targetSetClass == null) { + throw new IllegalArgumentException("'targetSetClass' must not be null"); + } + if (!Set.class.isAssignableFrom(targetSetClass)) { + throw new IllegalArgumentException("'targetSetClass' must implement [java.util.Set]"); + } + this.targetSetClass = targetSetClass; + } + + + @Override + @SuppressWarnings("rawtypes") + public Class getObjectType() { + return Set.class; + } + + @Override + @SuppressWarnings("unchecked") + protected Set createInstance() { + if (this.sourceSet == null) { + throw new IllegalArgumentException("'sourceSet' is required"); + } + Set result = null; + if (this.targetSetClass != null) { + result = BeanUtils.instantiateClass(this.targetSetClass); + } + else { + result = new LinkedHashSet<>(this.sourceSet.size()); + } + Class valueType = null; + if (this.targetSetClass != null) { + valueType = ResolvableType.forClass(this.targetSetClass).asCollection().resolveGeneric(); + } + if (valueType != null) { + TypeConverter converter = getBeanTypeConverter(); + for (Object elem : this.sourceSet) { + result.add(converter.convertIfNecessary(elem, valueType)); + } + } + else { + result.addAll(this.sourceSet); + } + return result; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java new file mode 100644 index 0000000..ee6d4a7 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2015 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.beans.factory.config; + +import org.springframework.lang.Nullable; + +/** + * Interface that defines a registry for shared bean instances. + * Can be implemented by {@link org.springframework.beans.factory.BeanFactory} + * implementations in order to expose their singleton management facility + * in a uniform manner. + * + *

The {@link ConfigurableBeanFactory} interface extends this interface. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ConfigurableBeanFactory + * @see org.springframework.beans.factory.support.DefaultSingletonBeanRegistry + * @see org.springframework.beans.factory.support.AbstractBeanFactory + */ +public interface SingletonBeanRegistry { + + /** + * Register the given existing object as singleton in the bean registry, + * under the given bean name. + *

The given instance is supposed to be fully initialized; the registry + * will not perform any initialization callbacks (in particular, it won't + * call InitializingBean's {@code afterPropertiesSet} method). + * The given instance will not receive any destruction callbacks + * (like DisposableBean's {@code destroy} method) either. + *

When running within a full BeanFactory: Register a bean definition + * instead of an existing instance if your bean is supposed to receive + * initialization and/or destruction callbacks. + *

Typically invoked during registry configuration, but can also be used + * for runtime registration of singletons. As a consequence, a registry + * implementation should synchronize singleton access; it will have to do + * this anyway if it supports a BeanFactory's lazy initialization of singletons. + * @param beanName the name of the bean + * @param singletonObject the existing singleton object + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet + * @see org.springframework.beans.factory.DisposableBean#destroy + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition + */ + void registerSingleton(String beanName, Object singletonObject); + + /** + * Return the (raw) singleton object registered under the given name. + *

Only checks already instantiated singletons; does not return an Object + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to access manually registered singletons + * (see {@link #registerSingleton}). Can also be used to access a singleton + * defined by a bean definition that already been created, in a raw fashion. + *

NOTE: This lookup method is not aware of FactoryBean prefixes or aliases. + * You need to resolve the canonical bean name first before obtaining the singleton instance. + * @param beanName the name of the bean to look for + * @return the registered singleton object, or {@code null} if none found + * @see ConfigurableListableBeanFactory#getBeanDefinition + */ + @Nullable + Object getSingleton(String beanName); + + /** + * Check if this registry contains a singleton instance with the given name. + *

Only checks already instantiated singletons; does not return {@code true} + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to check whether a + * singleton defined by a bean definition has already been created. + *

To check whether a bean factory contains a bean definition with a given name, + * use ListableBeanFactory's {@code containsBeanDefinition}. Calling both + * {@code containsBeanDefinition} and {@code containsSingleton} answers + * whether a specific bean factory contains a local bean instance with the given name. + *

Use BeanFactory's {@code containsBean} for general checks whether the + * factory knows about a bean with a given name (whether manually registered singleton + * instance or created by bean definition), also checking ancestor factories. + *

NOTE: This lookup method is not aware of FactoryBean prefixes or aliases. + * You need to resolve the canonical bean name first before checking the singleton status. + * @param beanName the name of the bean to look for + * @return if this bean factory contains a singleton instance with the given name + * @see #registerSingleton + * @see org.springframework.beans.factory.ListableBeanFactory#containsBeanDefinition + * @see org.springframework.beans.factory.BeanFactory#containsBean + */ + boolean containsSingleton(String beanName); + + /** + * Return the names of singleton beans registered in this registry. + *

Only checks already instantiated singletons; does not return names + * for singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to check which singletons + * defined by a bean definition have already been created. + * @return the list of names as a String array (never {@code null}) + * @see #registerSingleton + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#getBeanDefinitionNames + * @see org.springframework.beans.factory.ListableBeanFactory#getBeanDefinitionNames + */ + String[] getSingletonNames(); + + /** + * Return the number of singleton beans registered in this registry. + *

Only checks already instantiated singletons; does not count + * singleton bean definitions which have not been instantiated yet. + *

The main purpose of this method is to check manually registered singletons + * (see {@link #registerSingleton}). Can also be used to count the number of + * singletons defined by a bean definition that have already been created. + * @return the number of singleton beans + * @see #registerSingleton + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry#getBeanDefinitionCount + * @see org.springframework.beans.factory.ListableBeanFactory#getBeanDefinitionCount + */ + int getSingletonCount(); + + /** + * Return the singleton mutex used by this registry (for external collaborators). + * @return the mutex object (never {@code null}) + * @since 4.2 + */ + Object getSingletonMutex(); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java new file mode 100644 index 0000000..70d4ab2 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -0,0 +1,240 @@ +/* + * Copyright 2002-2019 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.beans.factory.config; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Holder for a typed String value. Can be added to bean definitions + * in order to explicitly specify a target type for a String value, + * for example for collection elements. + * + *

This holder will just store the String value and the target type. + * The actual conversion will be performed by the bean factory. + * + * @author Juergen Hoeller + * @since 1.2 + * @see BeanDefinition#getPropertyValues + * @see org.springframework.beans.MutablePropertyValues#addPropertyValue + */ +public class TypedStringValue implements BeanMetadataElement { + + @Nullable + private String value; + + @Nullable + private volatile Object targetType; + + @Nullable + private Object source; + + @Nullable + private String specifiedTypeName; + + private volatile boolean dynamic; + + + /** + * Create a new {@link TypedStringValue} for the given String value. + * @param value the String value + */ + public TypedStringValue(@Nullable String value) { + setValue(value); + } + + /** + * Create a new {@link TypedStringValue} for the given String value + * and target type. + * @param value the String value + * @param targetType the type to convert to + */ + public TypedStringValue(@Nullable String value, Class targetType) { + setValue(value); + setTargetType(targetType); + } + + /** + * Create a new {@link TypedStringValue} for the given String value + * and target type. + * @param value the String value + * @param targetTypeName the type to convert to + */ + public TypedStringValue(@Nullable String value, String targetTypeName) { + setValue(value); + setTargetTypeName(targetTypeName); + } + + + /** + * Set the String value. + *

Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + */ + public void setValue(@Nullable String value) { + this.value = value; + } + + /** + * Return the String value. + */ + @Nullable + public String getValue() { + return this.value; + } + + /** + * Set the type to convert to. + *

Only necessary for manipulating a registered value, + * for example in BeanFactoryPostProcessors. + */ + public void setTargetType(Class targetType) { + Assert.notNull(targetType, "'targetType' must not be null"); + this.targetType = targetType; + } + + /** + * Return the type to convert to. + */ + public Class getTargetType() { + Object targetTypeValue = this.targetType; + if (!(targetTypeValue instanceof Class)) { + throw new IllegalStateException("Typed String value does not carry a resolved target type"); + } + return (Class) targetTypeValue; + } + + /** + * Specify the type to convert to. + */ + public void setTargetTypeName(@Nullable String targetTypeName) { + this.targetType = targetTypeName; + } + + /** + * Return the type to convert to. + */ + @Nullable + public String getTargetTypeName() { + Object targetTypeValue = this.targetType; + if (targetTypeValue instanceof Class) { + return ((Class) targetTypeValue).getName(); + } + else { + return (String) targetTypeValue; + } + } + + /** + * Return whether this typed String value carries a target type . + */ + public boolean hasTargetType() { + return (this.targetType instanceof Class); + } + + /** + * Determine the type to convert to, resolving it from a specified class name + * if necessary. Will also reload a specified Class from its name when called + * with the target type already resolved. + * @param classLoader the ClassLoader to use for resolving a (potential) class name + * @return the resolved type to convert to + * @throws ClassNotFoundException if the type cannot be resolved + */ + @Nullable + public Class resolveTargetType(@Nullable ClassLoader classLoader) throws ClassNotFoundException { + String typeName = getTargetTypeName(); + if (typeName == null) { + return null; + } + Class resolvedClass = ClassUtils.forName(typeName, classLoader); + this.targetType = resolvedClass; + return resolvedClass; + } + + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Set the type name as actually specified for this particular value, if any. + */ + public void setSpecifiedTypeName(@Nullable String specifiedTypeName) { + this.specifiedTypeName = specifiedTypeName; + } + + /** + * Return the type name as actually specified for this particular value, if any. + */ + @Nullable + public String getSpecifiedTypeName() { + return this.specifiedTypeName; + } + + /** + * Mark this value as dynamic, i.e. as containing an expression + * and hence not being subject to caching. + */ + public void setDynamic() { + this.dynamic = true; + } + + /** + * Return whether this value has been marked as dynamic. + */ + public boolean isDynamic() { + return this.dynamic; + } + + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TypedStringValue)) { + return false; + } + TypedStringValue otherValue = (TypedStringValue) other; + return (ObjectUtils.nullSafeEquals(this.value, otherValue.value) && + ObjectUtils.nullSafeEquals(this.targetType, otherValue.targetType)); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.targetType); + } + + @Override + public String toString() { + return "TypedStringValue: value [" + this.value + "], target type [" + this.targetType + "]"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java new file mode 100644 index 0000000..cf13a40 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java @@ -0,0 +1,859 @@ +/* + * Copyright 2002-2019 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.beans.factory.groovy; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import groovy.lang.Binding; +import groovy.lang.Closure; +import groovy.lang.GString; +import groovy.lang.GroovyObject; +import groovy.lang.GroovyObjectSupport; +import groovy.lang.GroovyShell; +import groovy.lang.GroovySystem; +import groovy.lang.MetaClass; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.InvokerHelper; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.beans.factory.parsing.Location; +import org.springframework.beans.factory.parsing.Problem; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; +import org.springframework.beans.factory.xml.NamespaceHandler; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.beans.factory.xml.XmlReaderContext; +import org.springframework.core.io.DescriptiveResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * A Groovy-based reader for Spring bean definitions: like a Groovy builder, + * but more of a DSL for Spring configuration. + * + *

This bean definition reader also understands XML bean definition files, + * allowing for seamless mixing and matching with Groovy bean definition files. + * + *

Typically applied to a + * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} + * or a {@link org.springframework.context.support.GenericApplicationContext}, + * but can be used against any {@link BeanDefinitionRegistry} implementation. + * + *

Example Syntax

+ *
+ * import org.hibernate.SessionFactory
+ * import org.apache.commons.dbcp.BasicDataSource
+ *
+ * def reader = new GroovyBeanDefinitionReader(myApplicationContext)
+ * reader.beans {
+ *     dataSource(BasicDataSource) {                  // <--- invokeMethod
+ *         driverClassName = "org.hsqldb.jdbcDriver"
+ *         url = "jdbc:hsqldb:mem:grailsDB"
+ *         username = "sa"                            // <-- setProperty
+ *         password = ""
+ *         settings = [mynew:"setting"]
+ *     }
+ *     sessionFactory(SessionFactory) {
+ *         dataSource = dataSource                    // <-- getProperty for retrieving references
+ *     }
+ *     myService(MyService) {
+ *         nestedBean = { AnotherBean bean ->         // <-- setProperty with closure for nested bean
+ *             dataSource = dataSource
+ *         }
+ *     }
+ * }
+ * + *

You can also load resources containing beans defined in a Groovy script using + * either the {@link #loadBeanDefinitions(Resource...)} or + * {@link #loadBeanDefinitions(String...)} method, with a script looking similar to + * the following. + * + *

+ * import org.hibernate.SessionFactory
+ * import org.apache.commons.dbcp.BasicDataSource
+ *
+ * beans {
+ *     dataSource(BasicDataSource) {
+ *         driverClassName = "org.hsqldb.jdbcDriver"
+ *         url = "jdbc:hsqldb:mem:grailsDB"
+ *         username = "sa"
+ *         password = ""
+ *         settings = [mynew:"setting"]
+ *     }
+ *     sessionFactory(SessionFactory) {
+ *         dataSource = dataSource
+ *     }
+ *     myService(MyService) {
+ *         nestedBean = { AnotherBean bean ->
+ *             dataSource = dataSource
+ *         }
+ *     }
+ * }
+ * + * @author Jeff Brown + * @author Graeme Rocher + * @author Juergen Hoeller + * @author Sam Brannen + * @since 4.0 + * @see BeanDefinitionRegistry + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory + * @see org.springframework.context.support.GenericApplicationContext + * @see org.springframework.context.support.GenericGroovyApplicationContext + */ +public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader implements GroovyObject { + + /** + * Standard {@code XmlBeanDefinitionReader} created with default + * settings for loading bean definitions from XML files. + */ + private final XmlBeanDefinitionReader standardXmlBeanDefinitionReader; + + /** + * Groovy DSL {@code XmlBeanDefinitionReader} for loading bean definitions + * via the Groovy DSL, typically configured with XML validation disabled. + */ + private final XmlBeanDefinitionReader groovyDslXmlBeanDefinitionReader; + + private final Map namespaces = new HashMap<>(); + + private final Map deferredProperties = new HashMap<>(); + + private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass()); + + private Binding binding; + + private GroovyBeanDefinitionWrapper currentBeanDefinition; + + + /** + * Create a new {@code GroovyBeanDefinitionReader} for the given + * {@link BeanDefinitionRegistry}. + * @param registry the {@code BeanDefinitionRegistry} to load bean definitions into + */ + public GroovyBeanDefinitionReader(BeanDefinitionRegistry registry) { + super(registry); + this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry); + this.groovyDslXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry); + this.groovyDslXmlBeanDefinitionReader.setValidating(false); + } + + /** + * Create a new {@code GroovyBeanDefinitionReader} based on the given + * {@link XmlBeanDefinitionReader}, loading bean definitions into its + * {@code BeanDefinitionRegistry} and delegating Groovy DSL loading to it. + *

The supplied {@code XmlBeanDefinitionReader} should typically + * be pre-configured with XML validation disabled. + * @param xmlBeanDefinitionReader the {@code XmlBeanDefinitionReader} to + * derive the registry from and to delegate Groovy DSL loading to + */ + public GroovyBeanDefinitionReader(XmlBeanDefinitionReader xmlBeanDefinitionReader) { + super(xmlBeanDefinitionReader.getRegistry()); + this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(xmlBeanDefinitionReader.getRegistry()); + this.groovyDslXmlBeanDefinitionReader = xmlBeanDefinitionReader; + } + + + @Override + public void setMetaClass(MetaClass metaClass) { + this.metaClass = metaClass; + } + + @Override + public MetaClass getMetaClass() { + return this.metaClass; + } + + /** + * Set the binding, i.e. the Groovy variables available in the scope + * of a {@code GroovyBeanDefinitionReader} closure. + */ + public void setBinding(Binding binding) { + this.binding = binding; + } + + /** + * Return a specified binding for Groovy variables, if any. + */ + public Binding getBinding() { + return this.binding; + } + + + // TRADITIONAL BEAN DEFINITION READER METHODS + + /** + * Load bean definitions from the specified Groovy script or XML file. + *

Note that {@code ".xml"} files will be parsed as XML content; all other kinds + * of resources will be parsed as Groovy scripts. + * @param resource the resource descriptor for the Groovy script or XML file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + @Override + public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { + return loadBeanDefinitions(new EncodedResource(resource)); + } + + /** + * Load bean definitions from the specified Groovy script or XML file. + *

Note that {@code ".xml"} files will be parsed as XML content; all other kinds + * of resources will be parsed as Groovy scripts. + * @param encodedResource the resource descriptor for the Groovy script or XML file, + * allowing specification of an encoding to use for parsing the file + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + */ + public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { + // Check for XML files and redirect them to the "standard" XmlBeanDefinitionReader + String filename = encodedResource.getResource().getFilename(); + if (StringUtils.endsWithIgnoreCase(filename, ".xml")) { + return this.standardXmlBeanDefinitionReader.loadBeanDefinitions(encodedResource); + } + + if (logger.isTraceEnabled()) { + logger.trace("Loading Groovy bean definitions from " + encodedResource); + } + + @SuppressWarnings("serial") + Closure beans = new Closure(this) { + @Override + public Object call(Object... args) { + invokeBeanDefiningClosure((Closure) args[0]); + return null; + } + }; + Binding binding = new Binding() { + @Override + public void setVariable(String name, Object value) { + if (currentBeanDefinition != null) { + applyPropertyToBeanDefinition(name, value); + } + else { + super.setVariable(name, value); + } + } + }; + binding.setVariable("beans", beans); + + int countBefore = getRegistry().getBeanDefinitionCount(); + try { + GroovyShell shell = new GroovyShell(getBeanClassLoader(), binding); + shell.evaluate(encodedResource.getReader(), "beans"); + } + catch (Throwable ex) { + throw new BeanDefinitionParsingException(new Problem("Error evaluating Groovy script: " + ex.getMessage(), + new Location(encodedResource.getResource()), null, ex)); + } + + int count = getRegistry().getBeanDefinitionCount() - countBefore; + if (logger.isDebugEnabled()) { + logger.debug("Loaded " + count + " bean definitions from " + encodedResource); + } + return count; + } + + + // METHODS FOR CONSUMPTION IN A GROOVY CLOSURE + + /** + * Defines a set of beans for the given block or closure. + * @param closure the block or closure + * @return this {@code GroovyBeanDefinitionReader} instance + */ + public GroovyBeanDefinitionReader beans(Closure closure) { + return invokeBeanDefiningClosure(closure); + } + + /** + * Define an inner bean definition. + * @param type the bean type + * @return the bean definition + */ + public GenericBeanDefinition bean(Class type) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(type); + return beanDefinition; + } + + /** + * Define an inner bean definition. + * @param type the bean type + * @param args the constructors arguments and closure configurer + * @return the bean definition + */ + public AbstractBeanDefinition bean(Class type, Object...args) { + GroovyBeanDefinitionWrapper current = this.currentBeanDefinition; + try { + Closure callable = null; + Collection constructorArgs = null; + if (!ObjectUtils.isEmpty(args)) { + int index = args.length; + Object lastArg = args[index - 1]; + if (lastArg instanceof Closure) { + callable = (Closure) lastArg; + index--; + } + constructorArgs = resolveConstructorArguments(args, 0, index); + } + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, type, constructorArgs); + if (callable != null) { + callable.call(this.currentBeanDefinition); + } + return this.currentBeanDefinition.getBeanDefinition(); + } + finally { + this.currentBeanDefinition = current; + } + } + + /** + * Define a Spring XML namespace definition to use. + * @param definition the namespace definition + */ + public void xmlns(Map definition) { + if (!definition.isEmpty()) { + for (Map.Entry entry : definition.entrySet()) { + String namespace = entry.getKey(); + String uri = entry.getValue(); + if (uri == null) { + throw new IllegalArgumentException("Namespace definition must supply a non-null URI"); + } + NamespaceHandler namespaceHandler = + this.groovyDslXmlBeanDefinitionReader.getNamespaceHandlerResolver().resolve(uri); + if (namespaceHandler == null) { + throw new BeanDefinitionParsingException(new Problem("No namespace handler found for URI: " + uri, + new Location(new DescriptiveResource(("Groovy"))))); + } + this.namespaces.put(namespace, uri); + } + } + } + + /** + * Import Spring bean definitions from either XML or Groovy sources into the + * current bean builder instance. + * @param resourcePattern the resource pattern + */ + public void importBeans(String resourcePattern) throws IOException { + loadBeanDefinitions(resourcePattern); + } + + + // INTERNAL HANDLING OF GROOVY CLOSURES AND PROPERTIES + + /** + * This method overrides method invocation to create beans for each method name that + * takes a class argument. + */ + @Override + public Object invokeMethod(String name, Object arg) { + Object[] args = (Object[])arg; + if ("beans".equals(name) && args.length == 1 && args[0] instanceof Closure) { + return beans((Closure) args[0]); + } + else if ("ref".equals(name)) { + String refName; + if (args[0] == null) { + throw new IllegalArgumentException("Argument to ref() is not a valid bean or was not found"); + } + if (args[0] instanceof RuntimeBeanReference) { + refName = ((RuntimeBeanReference) args[0]).getBeanName(); + } + else { + refName = args[0].toString(); + } + boolean parentRef = false; + if (args.length > 1 && args[1] instanceof Boolean) { + parentRef = (Boolean) args[1]; + } + return new RuntimeBeanReference(refName, parentRef); + } + else if (this.namespaces.containsKey(name) && args.length > 0 && args[0] instanceof Closure) { + GroovyDynamicElementReader reader = createDynamicElementReader(name); + reader.invokeMethod("doCall", args); + } + else if (args.length > 0 && args[0] instanceof Closure) { + // abstract bean definition + return invokeBeanDefiningMethod(name, args); + } + else if (args.length > 0 && + (args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map)) { + return invokeBeanDefiningMethod(name, args); + } + else if (args.length > 1 && args[args.length -1] instanceof Closure) { + return invokeBeanDefiningMethod(name, args); + } + MetaClass mc = DefaultGroovyMethods.getMetaClass(getRegistry()); + if (!mc.respondsTo(getRegistry(), name, args).isEmpty()){ + return mc.invokeMethod(getRegistry(), name, args); + } + return this; + } + + private boolean addDeferredProperty(String property, Object newValue) { + if (newValue instanceof List || newValue instanceof Map) { + this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property, + new DeferredProperty(this.currentBeanDefinition, property, newValue)); + return true; + } + return false; + } + + private void finalizeDeferredProperties() { + for (DeferredProperty dp : this.deferredProperties.values()) { + if (dp.value instanceof List) { + dp.value = manageListIfNecessary((List) dp.value); + } + else if (dp.value instanceof Map) { + dp.value = manageMapIfNecessary((Map) dp.value); + } + dp.apply(); + } + this.deferredProperties.clear(); + } + + /** + * When a method argument is only a closure it is a set of bean definitions. + * @param callable the closure argument + * @return this {@code GroovyBeanDefinitionReader} instance + */ + protected GroovyBeanDefinitionReader invokeBeanDefiningClosure(Closure callable) { + callable.setDelegate(this); + callable.call(); + finalizeDeferredProperties(); + return this; + } + + /** + * This method is called when a bean definition node is called. + * @param beanName the name of the bean to define + * @param args the arguments to the bean. The first argument is the class name, the last + * argument is sometimes a closure. All the arguments in between are constructor arguments. + * @return the bean definition wrapper + */ + private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Object[] args) { + boolean hasClosureArgument = (args[args.length - 1] instanceof Closure); + if (args[0] instanceof Class) { + Class beanClass = (Class) args[0]; + if (hasClosureArgument) { + if (args.length - 1 != 1) { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( + beanName, beanClass, resolveConstructorArguments(args, 1, args.length - 1)); + } + else { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, beanClass); + } + } + else { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( + beanName, beanClass, resolveConstructorArguments(args, 1, args.length)); + } + } + else if (args[0] instanceof RuntimeBeanReference) { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); + this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(((RuntimeBeanReference) args[0]).getBeanName()); + } + else if (args[0] instanceof Map) { + // named constructor arguments + if (args.length > 1 && args[1] instanceof Class) { + List constructorArgs = + resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length); + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class) args[1], constructorArgs); + Map namedArgs = (Map) args[0]; + for (Object o : namedArgs.keySet()) { + String propName = (String) o; + setProperty(propName, namedArgs.get(propName)); + } + } + // factory method syntax + else { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); + // First arg is the map containing factoryBean : factoryMethod + Map.Entry factoryBeanEntry = ((Map) args[0]).entrySet().iterator().next(); + // If we have a closure body, that will be the last argument. + // In between are the constructor args + int constructorArgsTest = (hasClosureArgument ? 2 : 1); + // If we have more than this number of args, we have constructor args + if (args.length > constructorArgsTest){ + // factory-method requires args + int endOfConstructArgs = (hasClosureArgument ? args.length - 1 : args.length); + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, + resolveConstructorArguments(args, 1, endOfConstructArgs)); + } + else { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); + } + this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(factoryBeanEntry.getKey().toString()); + this.currentBeanDefinition.getBeanDefinition().setFactoryMethodName(factoryBeanEntry.getValue().toString()); + } + + } + else if (args[0] instanceof Closure) { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); + this.currentBeanDefinition.getBeanDefinition().setAbstract(true); + } + else { + List constructorArgs = + resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length); + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, constructorArgs); + } + + if (hasClosureArgument) { + Closure callable = (Closure) args[args.length - 1]; + callable.setDelegate(this); + callable.setResolveStrategy(Closure.DELEGATE_FIRST); + callable.call(this.currentBeanDefinition); + } + + GroovyBeanDefinitionWrapper beanDefinition = this.currentBeanDefinition; + this.currentBeanDefinition = null; + beanDefinition.getBeanDefinition().setAttribute(GroovyBeanDefinitionWrapper.class.getName(), beanDefinition); + getRegistry().registerBeanDefinition(beanName, beanDefinition.getBeanDefinition()); + return beanDefinition; + } + + protected List resolveConstructorArguments(Object[] args, int start, int end) { + Object[] constructorArgs = Arrays.copyOfRange(args, start, end); + for (int i = 0; i < constructorArgs.length; i++) { + if (constructorArgs[i] instanceof GString) { + constructorArgs[i] = constructorArgs[i].toString(); + } + else if (constructorArgs[i] instanceof List) { + constructorArgs[i] = manageListIfNecessary((List) constructorArgs[i]); + } + else if (constructorArgs[i] instanceof Map){ + constructorArgs[i] = manageMapIfNecessary((Map) constructorArgs[i]); + } + } + return Arrays.asList(constructorArgs); + } + + /** + * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences} + * inside the {@link Map} and converts it to a {@link ManagedMap} if necessary. + * @param map the original Map + * @return either the original map or a managed copy of it + */ + private Object manageMapIfNecessary(Map map) { + boolean containsRuntimeRefs = false; + for (Object element : map.values()) { + if (element instanceof RuntimeBeanReference) { + containsRuntimeRefs = true; + break; + } + } + if (containsRuntimeRefs) { + Map managedMap = new ManagedMap<>(); + managedMap.putAll(map); + return managedMap; + } + return map; + } + + /** + * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences} + * inside the {@link List} and converts it to a {@link ManagedList} if necessary. + * @param list the original List + * @return either the original list or a managed copy of it + */ + private Object manageListIfNecessary(List list) { + boolean containsRuntimeRefs = false; + for (Object element : list) { + if (element instanceof RuntimeBeanReference) { + containsRuntimeRefs = true; + break; + } + } + if (containsRuntimeRefs) { + List managedList = new ManagedList<>(); + managedList.addAll(list); + return managedList; + } + return list; + } + + /** + * This method overrides property setting in the scope of the {@code GroovyBeanDefinitionReader} + * to set properties on the current bean definition. + */ + @Override + public void setProperty(String name, Object value) { + if (this.currentBeanDefinition != null) { + applyPropertyToBeanDefinition(name, value); + } + } + + protected void applyPropertyToBeanDefinition(String name, Object value) { + if (value instanceof GString) { + value = value.toString(); + } + if (addDeferredProperty(name, value)) { + return; + } + else if (value instanceof Closure) { + GroovyBeanDefinitionWrapper current = this.currentBeanDefinition; + try { + Closure callable = (Closure) value; + Class parameterType = callable.getParameterTypes()[0]; + if (Object.class == parameterType) { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(""); + callable.call(this.currentBeanDefinition); + } + else { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, parameterType); + callable.call((Object) null); + } + + value = this.currentBeanDefinition.getBeanDefinition(); + } + finally { + this.currentBeanDefinition = current; + } + } + this.currentBeanDefinition.addProperty(name, value); + } + + /** + * This method overrides property retrieval in the scope of the + * {@code GroovyBeanDefinitionReader}. A property retrieval will either: + *
    + *
  • Retrieve a variable from the bean builder's binding if it exists + *
  • Retrieve a RuntimeBeanReference for a specific bean if it exists + *
  • Otherwise just delegate to MetaClass.getProperty which will resolve + * properties from the {@code GroovyBeanDefinitionReader} itself + *
+ */ + @Override + public Object getProperty(String name) { + Binding binding = getBinding(); + if (binding != null && binding.hasVariable(name)) { + return binding.getVariable(name); + } + else { + if (this.namespaces.containsKey(name)) { + return createDynamicElementReader(name); + } + if (getRegistry().containsBeanDefinition(name)) { + GroovyBeanDefinitionWrapper beanDefinition = (GroovyBeanDefinitionWrapper) + getRegistry().getBeanDefinition(name).getAttribute(GroovyBeanDefinitionWrapper.class.getName()); + if (beanDefinition != null) { + return new GroovyRuntimeBeanReference(name, beanDefinition, false); + } + else { + return new RuntimeBeanReference(name, false); + } + } + // This is to deal with the case where the property setter is the last + // statement in a closure (hence the return value) + else if (this.currentBeanDefinition != null) { + MutablePropertyValues pvs = this.currentBeanDefinition.getBeanDefinition().getPropertyValues(); + if (pvs.contains(name)) { + return pvs.get(name); + } + else { + DeferredProperty dp = this.deferredProperties.get(this.currentBeanDefinition.getBeanName() + name); + if (dp != null) { + return dp.value; + } + else { + return getMetaClass().getProperty(this, name); + } + } + } + else { + return getMetaClass().getProperty(this, name); + } + } + } + + private GroovyDynamicElementReader createDynamicElementReader(String namespace) { + XmlReaderContext readerContext = this.groovyDslXmlBeanDefinitionReader.createReaderContext( + new DescriptiveResource("Groovy")); + BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); + boolean decorating = (this.currentBeanDefinition != null); + if (!decorating) { + this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(namespace); + } + return new GroovyDynamicElementReader(namespace, this.namespaces, delegate, this.currentBeanDefinition, decorating) { + @Override + protected void afterInvocation() { + if (!this.decorating) { + currentBeanDefinition = null; + } + } + }; + } + + + /** + * This class is used to defer the adding of a property to a bean definition + * until later. This is for a case where you assign a property to a list that + * may not contain bean references at that point of assignment, but may later; + * hence, it would need to be managed. + */ + private static class DeferredProperty { + + private final GroovyBeanDefinitionWrapper beanDefinition; + + private final String name; + + public Object value; + + public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) { + this.beanDefinition = beanDefinition; + this.name = name; + this.value = value; + } + + public void apply() { + this.beanDefinition.addProperty(this.name, this.value); + } + } + + + /** + * A RuntimeBeanReference that takes care of adding new properties to runtime references. + */ + private class GroovyRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject { + + private final GroovyBeanDefinitionWrapper beanDefinition; + + private MetaClass metaClass; + + public GroovyRuntimeBeanReference(String beanName, GroovyBeanDefinitionWrapper beanDefinition, boolean toParent) { + super(beanName, toParent); + this.beanDefinition = beanDefinition; + this.metaClass = InvokerHelper.getMetaClass(this); + } + + @Override + public MetaClass getMetaClass() { + return this.metaClass; + } + + @Override + public Object getProperty(String property) { + if (property.equals("beanName")) { + return getBeanName(); + } + else if (property.equals("source")) { + return getSource(); + } + else if (this.beanDefinition != null) { + return new GroovyPropertyValue( + property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property)); + } + else { + return this.metaClass.getProperty(this, property); + } + } + + @Override + public Object invokeMethod(String name, Object args) { + return this.metaClass.invokeMethod(this, name, args); + } + + @Override + public void setMetaClass(MetaClass metaClass) { + this.metaClass = metaClass; + } + + @Override + public void setProperty(String property, Object newValue) { + if (!addDeferredProperty(property, newValue)) { + this.beanDefinition.getBeanDefinition().getPropertyValues().add(property, newValue); + } + } + + + /** + * Wraps a bean definition property and ensures that any RuntimeBeanReference + * additions to it are deferred for resolution later. + */ + private class GroovyPropertyValue extends GroovyObjectSupport { + + private final String propertyName; + + private final Object propertyValue; + + public GroovyPropertyValue(String propertyName, Object propertyValue) { + this.propertyName = propertyName; + this.propertyValue = propertyValue; + } + + @SuppressWarnings("unused") + public void leftShift(Object value) { + InvokerHelper.invokeMethod(this.propertyValue, "leftShift", value); + updateDeferredProperties(value); + } + + @SuppressWarnings("unused") + public boolean add(Object value) { + boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "add", value); + updateDeferredProperties(value); + return retVal; + } + + @SuppressWarnings("unused") + public boolean addAll(Collection values) { + boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "addAll", values); + for (Object value : values) { + updateDeferredProperties(value); + } + return retVal; + } + + @Override + public Object invokeMethod(String name, Object args) { + return InvokerHelper.invokeMethod(this.propertyValue, name, args); + } + + @Override + public Object getProperty(String name) { + return InvokerHelper.getProperty(this.propertyValue, name); + } + + @Override + public void setProperty(String name, Object value) { + InvokerHelper.setProperty(this.propertyValue, name, value); + } + + private void updateDeferredProperties(Object value) { + if (value instanceof RuntimeBeanReference) { + deferredProperties.put(beanDefinition.getBeanName(), + new DeferredProperty(beanDefinition, this.propertyName, this.propertyValue)); + } + } + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java new file mode 100644 index 0000000..d1aeff7 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java @@ -0,0 +1,243 @@ +/* + * Copyright 2002-2019 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.beans.factory.groovy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import groovy.lang.GroovyObjectSupport; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.util.CollectionUtils; + +/** + * Internal wrapper for a Spring BeanDefinition, allowing for Groovy-style + * property access within a {@link GroovyBeanDefinitionReader} closure. + * + * @author Jeff Brown + * @author Juergen Hoeller + * @since 4.0 + */ +class GroovyBeanDefinitionWrapper extends GroovyObjectSupport { + + private static final String PARENT = "parent"; + private static final String AUTOWIRE = "autowire"; + private static final String CONSTRUCTOR_ARGS = "constructorArgs"; + private static final String FACTORY_BEAN = "factoryBean"; + private static final String FACTORY_METHOD = "factoryMethod"; + private static final String INIT_METHOD = "initMethod"; + private static final String DESTROY_METHOD = "destroyMethod"; + private static final String SINGLETON = "singleton"; + + private static final List dynamicProperties = new ArrayList<>(8); + + static { + dynamicProperties.add(PARENT); + dynamicProperties.add(AUTOWIRE); + dynamicProperties.add(CONSTRUCTOR_ARGS); + dynamicProperties.add(FACTORY_BEAN); + dynamicProperties.add(FACTORY_METHOD); + dynamicProperties.add(INIT_METHOD); + dynamicProperties.add(DESTROY_METHOD); + dynamicProperties.add(SINGLETON); + } + + + private String beanName; + + private Class clazz; + + private Collection constructorArgs; + + private AbstractBeanDefinition definition; + + private BeanWrapper definitionWrapper; + + private String parentName; + + + public GroovyBeanDefinitionWrapper(String beanName) { + this.beanName = beanName; + } + + public GroovyBeanDefinitionWrapper(String beanName, Class clazz) { + this.beanName = beanName; + this.clazz = clazz; + } + + public GroovyBeanDefinitionWrapper(String beanName, Class clazz, Collection constructorArgs) { + this.beanName = beanName; + this.clazz = clazz; + this.constructorArgs = constructorArgs; + } + + + public String getBeanName() { + return this.beanName; + } + + public void setBeanDefinition(AbstractBeanDefinition definition) { + this.definition = definition; + } + + public AbstractBeanDefinition getBeanDefinition() { + if (this.definition == null) { + this.definition = createBeanDefinition(); + } + return this.definition; + } + + protected AbstractBeanDefinition createBeanDefinition() { + AbstractBeanDefinition bd = new GenericBeanDefinition(); + bd.setBeanClass(this.clazz); + if (!CollectionUtils.isEmpty(this.constructorArgs)) { + ConstructorArgumentValues cav = new ConstructorArgumentValues(); + for (Object constructorArg : this.constructorArgs) { + cav.addGenericArgumentValue(constructorArg); + } + bd.setConstructorArgumentValues(cav); + } + if (this.parentName != null) { + bd.setParentName(this.parentName); + } + this.definitionWrapper = new BeanWrapperImpl(bd); + return bd; + } + + public void setBeanDefinitionHolder(BeanDefinitionHolder holder) { + this.definition = (AbstractBeanDefinition) holder.getBeanDefinition(); + this.beanName = holder.getBeanName(); + } + + public BeanDefinitionHolder getBeanDefinitionHolder() { + return new BeanDefinitionHolder(getBeanDefinition(), getBeanName()); + } + + public void setParent(Object obj) { + if (obj == null) { + throw new IllegalArgumentException("Parent bean cannot be set to a null runtime bean reference!"); + } + if (obj instanceof String) { + this.parentName = (String) obj; + } + else if (obj instanceof RuntimeBeanReference) { + this.parentName = ((RuntimeBeanReference) obj).getBeanName(); + } + else if (obj instanceof GroovyBeanDefinitionWrapper) { + this.parentName = ((GroovyBeanDefinitionWrapper) obj).getBeanName(); + } + getBeanDefinition().setParentName(this.parentName); + getBeanDefinition().setAbstract(false); + } + + public GroovyBeanDefinitionWrapper addProperty(String propertyName, Object propertyValue) { + if (propertyValue instanceof GroovyBeanDefinitionWrapper) { + propertyValue = ((GroovyBeanDefinitionWrapper) propertyValue).getBeanDefinition(); + } + getBeanDefinition().getPropertyValues().add(propertyName, propertyValue); + return this; + } + + + @Override + public Object getProperty(String property) { + if (this.definitionWrapper.isReadableProperty(property)) { + return this.definitionWrapper.getPropertyValue(property); + } + else if (dynamicProperties.contains(property)) { + return null; + } + return super.getProperty(property); + } + + @Override + public void setProperty(String property, Object newValue) { + if (PARENT.equals(property)) { + setParent(newValue); + } + else { + AbstractBeanDefinition bd = getBeanDefinition(); + if (AUTOWIRE.equals(property)) { + if ("byName".equals(newValue)) { + bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); + } + else if ("byType".equals(newValue)) { + bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + } + else if ("constructor".equals(newValue)) { + bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); + } + else if (Boolean.TRUE.equals(newValue)) { + bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); + } + } + // constructorArgs + else if (CONSTRUCTOR_ARGS.equals(property) && newValue instanceof List) { + ConstructorArgumentValues cav = new ConstructorArgumentValues(); + List args = (List) newValue; + for (Object arg : args) { + cav.addGenericArgumentValue(arg); + } + bd.setConstructorArgumentValues(cav); + } + // factoryBean + else if (FACTORY_BEAN.equals(property)) { + if (newValue != null) { + bd.setFactoryBeanName(newValue.toString()); + } + } + // factoryMethod + else if (FACTORY_METHOD.equals(property)) { + if (newValue != null) { + bd.setFactoryMethodName(newValue.toString()); + } + } + // initMethod + else if (INIT_METHOD.equals(property)) { + if (newValue != null) { + bd.setInitMethodName(newValue.toString()); + } + } + // destroyMethod + else if (DESTROY_METHOD.equals(property)) { + if (newValue != null) { + bd.setDestroyMethodName(newValue.toString()); + } + } + // singleton property + else if (SINGLETON.equals(property)) { + bd.setScope(Boolean.TRUE.equals(newValue) ? + BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE); + } + else if (this.definitionWrapper.isWritableProperty(property)) { + this.definitionWrapper.setPropertyValue(property, newValue); + } + else { + super.setProperty(property, newValue); + } + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/package-info.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/package-info.java new file mode 100644 index 0000000..d956cd5 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/package-info.java @@ -0,0 +1,4 @@ +/** + * Support package for Groovy-based bean definitions. + */ +package org.springframework.beans.factory.groovy; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java new file mode 100644 index 0000000..d4fdc29 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AbstractComponentDefinition.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; + +/** + * Base implementation of {@link ComponentDefinition} that provides a basic implementation of + * {@link #getDescription} which delegates to {@link #getName}. Also provides a base implementation + * of {@link #toString} which delegates to {@link #getDescription} in keeping with the recommended + * implementation strategy. Also provides default implementations of {@link #getInnerBeanDefinitions} + * and {@link #getBeanReferences} that return an empty array. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AbstractComponentDefinition implements ComponentDefinition { + + /** + * Delegates to {@link #getName}. + */ + @Override + public String getDescription() { + return getName(); + } + + /** + * Returns an empty array. + */ + @Override + public BeanDefinition[] getBeanDefinitions() { + return new BeanDefinition[0]; + } + + /** + * Returns an empty array. + */ + @Override + public BeanDefinition[] getInnerBeanDefinitions() { + return new BeanDefinition[0]; + } + + /** + * Returns an empty array. + */ + @Override + public BeanReference[] getBeanReferences() { + return new BeanReference[0]; + } + + /** + * Delegates to {@link #getDescription}. + */ + @Override + public String toString() { + return getDescription(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java new file mode 100644 index 0000000..27bed9e --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/AliasDefinition.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2017 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.beans.factory.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Representation of an alias that has been registered during the parsing process. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderEventListener#aliasRegistered(AliasDefinition) + */ +public class AliasDefinition implements BeanMetadataElement { + + private final String beanName; + + private final String alias; + + @Nullable + private final Object source; + + + /** + * Create a new AliasDefinition. + * @param beanName the canonical name of the bean + * @param alias the alias registered for the bean + */ + public AliasDefinition(String beanName, String alias) { + this(beanName, alias, null); + } + + /** + * Create a new AliasDefinition. + * @param beanName the canonical name of the bean + * @param alias the alias registered for the bean + * @param source the source object (may be {@code null}) + */ + public AliasDefinition(String beanName, String alias, @Nullable Object source) { + Assert.notNull(beanName, "Bean name must not be null"); + Assert.notNull(alias, "Alias must not be null"); + this.beanName = beanName; + this.alias = alias; + this.source = source; + } + + + /** + * Return the canonical name of the bean. + */ + public final String getBeanName() { + return this.beanName; + } + + /** + * Return the alias registered for the bean. + */ + public final String getAlias() { + return this.alias; + } + + @Override + @Nullable + public final Object getSource() { + return this.source; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java new file mode 100644 index 0000000..32305ab --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2018 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.beans.factory.parsing; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.lang.Nullable; + +/** + * ComponentDefinition based on a standard BeanDefinition, exposing the given bean + * definition as well as inner bean definitions and bean references for the given bean. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class BeanComponentDefinition extends BeanDefinitionHolder implements ComponentDefinition { + + private BeanDefinition[] innerBeanDefinitions; + + private BeanReference[] beanReferences; + + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param beanDefinition the BeanDefinition + * @param beanName the name of the bean + */ + public BeanComponentDefinition(BeanDefinition beanDefinition, String beanName) { + this(new BeanDefinitionHolder(beanDefinition, beanName)); + } + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param beanDefinition the BeanDefinition + * @param beanName the name of the bean + * @param aliases alias names for the bean, or {@code null} if none + */ + public BeanComponentDefinition(BeanDefinition beanDefinition, String beanName, @Nullable String[] aliases) { + this(new BeanDefinitionHolder(beanDefinition, beanName, aliases)); + } + + /** + * Create a new BeanComponentDefinition for the given bean. + * @param beanDefinitionHolder the BeanDefinitionHolder encapsulating + * the bean definition as well as the name of the bean + */ + public BeanComponentDefinition(BeanDefinitionHolder beanDefinitionHolder) { + super(beanDefinitionHolder); + + List innerBeans = new ArrayList<>(); + List references = new ArrayList<>(); + PropertyValues propertyValues = beanDefinitionHolder.getBeanDefinition().getPropertyValues(); + for (PropertyValue propertyValue : propertyValues.getPropertyValues()) { + Object value = propertyValue.getValue(); + if (value instanceof BeanDefinitionHolder) { + innerBeans.add(((BeanDefinitionHolder) value).getBeanDefinition()); + } + else if (value instanceof BeanDefinition) { + innerBeans.add((BeanDefinition) value); + } + else if (value instanceof BeanReference) { + references.add((BeanReference) value); + } + } + this.innerBeanDefinitions = innerBeans.toArray(new BeanDefinition[0]); + this.beanReferences = references.toArray(new BeanReference[0]); + } + + + @Override + public String getName() { + return getBeanName(); + } + + @Override + public String getDescription() { + return getShortDescription(); + } + + @Override + public BeanDefinition[] getBeanDefinitions() { + return new BeanDefinition[] {getBeanDefinition()}; + } + + @Override + public BeanDefinition[] getInnerBeanDefinitions() { + return this.innerBeanDefinitions; + } + + @Override + public BeanReference[] getBeanReferences() { + return this.beanReferences; + } + + + /** + * This implementation returns this ComponentDefinition's description. + * @see #getDescription() + */ + @Override + public String toString() { + return getDescription(); + } + + /** + * This implementations expects the other object to be of type BeanComponentDefinition + * as well, in addition to the superclass's equality requirements. + */ + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof BeanComponentDefinition && super.equals(other))); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java new file mode 100644 index 0000000..6cdf858 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionParsingException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.beans.factory.BeanDefinitionStoreException; + +/** + * Exception thrown when a bean definition reader encounters an error + * during the parsing process. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.0 + */ +@SuppressWarnings("serial") +public class BeanDefinitionParsingException extends BeanDefinitionStoreException { + + /** + * Create a new BeanDefinitionParsingException. + * @param problem the configuration problem that was detected during the parsing process + */ + public BeanDefinitionParsingException(Problem problem) { + super(problem.getResourceDescription(), problem.toString(), problem.getRootCause()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java new file mode 100644 index 0000000..ccba6d7 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanEntry.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2020 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.beans.factory.parsing; + +/** + * {@link ParseState} entry representing a bean definition. + * + * @author Rob Harrop + * @since 2.0 + */ +public class BeanEntry implements ParseState.Entry { + + private final String beanDefinitionName; + + + /** + * Create a new {@code BeanEntry} instance. + * @param beanDefinitionName the name of the associated bean definition + */ + public BeanEntry(String beanDefinitionName) { + this.beanDefinitionName = beanDefinitionName; + } + + + @Override + public String toString() { + return "Bean '" + this.beanDefinitionName + "'"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java new file mode 100644 index 0000000..a899025 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; + +/** + * Interface that describes the logical view of a set of {@link BeanDefinition BeanDefinitions} + * and {@link BeanReference BeanReferences} as presented in some configuration context. + * + *

With the introduction of {@link org.springframework.beans.factory.xml.NamespaceHandler pluggable custom XML tags}, + * it is now possible for a single logical configuration entity, in this case an XML tag, to + * create multiple {@link BeanDefinition BeanDefinitions} and {@link BeanReference RuntimeBeanReferences} + * in order to provide more succinct configuration and greater convenience to end users. As such, it can + * no longer be assumed that each configuration entity (e.g. XML tag) maps to one {@link BeanDefinition}. + * For tool vendors and other users who wish to present visualization or support for configuring Spring + * applications it is important that there is some mechanism in place to tie the {@link BeanDefinition BeanDefinitions} + * in the {@link org.springframework.beans.factory.BeanFactory} back to the configuration data in a way + * that has concrete meaning to the end user. As such, {@link org.springframework.beans.factory.xml.NamespaceHandler} + * implementations are able to publish events in the form of a {@code ComponentDefinition} for each + * logical entity being configured. Third parties can then {@link ReaderEventListener subscribe to these events}, + * allowing for a user-centric view of the bean metadata. + * + *

Each {@code ComponentDefinition} has a {@link #getSource source object} which is configuration-specific. + * In the case of XML-based configuration this is typically the {@link org.w3c.dom.Node} which contains the user + * supplied configuration information. In addition to this, each {@link BeanDefinition} enclosed in a + * {@code ComponentDefinition} has its own {@link BeanDefinition#getSource() source object} which may point + * to a different, more specific, set of configuration data. Beyond this, individual pieces of bean metadata such + * as the {@link org.springframework.beans.PropertyValue PropertyValues} may also have a source object giving an + * even greater level of detail. Source object extraction is handled through the + * {@link SourceExtractor} which can be customized as required. + * + *

Whilst direct access to important {@link BeanReference BeanReferences} is provided through + * {@link #getBeanReferences}, tools may wish to inspect all {@link BeanDefinition BeanDefinitions} to gather + * the full set of {@link BeanReference BeanReferences}. Implementations are required to provide + * all {@link BeanReference BeanReferences} that are required to validate the configuration of the + * overall logical entity as well as those required to provide full user visualisation of the configuration. + * It is expected that certain {@link BeanReference BeanReferences} will not be important to + * validation or to the user view of the configuration and as such these may be omitted. A tool may wish to + * display any additional {@link BeanReference BeanReferences} sourced through the supplied + * {@link BeanDefinition BeanDefinitions} but this is not considered to be a typical case. + * + *

Tools can determine the important of contained {@link BeanDefinition BeanDefinitions} by checking the + * {@link BeanDefinition#getRole role identifier}. The role is essentially a hint to the tool as to how + * important the configuration provider believes a {@link BeanDefinition} is to the end user. It is expected + * that tools will not display all {@link BeanDefinition BeanDefinitions} for a given + * {@code ComponentDefinition} choosing instead to filter based on the role. Tools may choose to make + * this filtering user configurable. Particular notice should be given to the + * {@link BeanDefinition#ROLE_INFRASTRUCTURE INFRASTRUCTURE role identifier}. {@link BeanDefinition BeanDefinitions} + * classified with this role are completely unimportant to the end user and are required only for + * internal implementation reasons. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see AbstractComponentDefinition + * @see CompositeComponentDefinition + * @see BeanComponentDefinition + * @see ReaderEventListener#componentRegistered(ComponentDefinition) + */ +public interface ComponentDefinition extends BeanMetadataElement { + + /** + * Get the user-visible name of this {@code ComponentDefinition}. + *

This should link back directly to the corresponding configuration data + * for this component in a given context. + */ + String getName(); + + /** + * Return a friendly description of the described component. + *

Implementations are encouraged to return the same value from + * {@code toString()}. + */ + String getDescription(); + + /** + * Return the {@link BeanDefinition BeanDefinitions} that were registered + * to form this {@code ComponentDefinition}. + *

It should be noted that a {@code ComponentDefinition} may well be related with + * other {@link BeanDefinition BeanDefinitions} via {@link BeanReference references}, + * however these are not included as they may be not available immediately. + * Important {@link BeanReference BeanReferences} are available from {@link #getBeanReferences()}. + * @return the array of BeanDefinitions, or an empty array if none + */ + BeanDefinition[] getBeanDefinitions(); + + /** + * Return the {@link BeanDefinition BeanDefinitions} that represent all relevant + * inner beans within this component. + *

Other inner beans may exist within the associated {@link BeanDefinition BeanDefinitions}, + * however these are not considered to be needed for validation or for user visualization. + * @return the array of BeanDefinitions, or an empty array if none + */ + BeanDefinition[] getInnerBeanDefinitions(); + + /** + * Return the set of {@link BeanReference BeanReferences} that are considered + * to be important to this {@code ComponentDefinition}. + *

Other {@link BeanReference BeanReferences} may exist within the associated + * {@link BeanDefinition BeanDefinitions}, however these are not considered + * to be needed for validation or for user visualization. + * @return the array of BeanReferences, or an empty array if none + */ + BeanReference[] getBeanReferences(); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java new file mode 100644 index 0000000..efd846c --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/CompositeComponentDefinition.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2018 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.beans.factory.parsing; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * {@link ComponentDefinition} implementation that holds one or more nested + * {@link ComponentDefinition} instances, aggregating them into a named group + * of components. + * + * @author Juergen Hoeller + * @since 2.0.1 + * @see #getNestedComponents() + */ +public class CompositeComponentDefinition extends AbstractComponentDefinition { + + private final String name; + + @Nullable + private final Object source; + + private final List nestedComponents = new ArrayList<>(); + + + /** + * Create a new CompositeComponentDefinition. + * @param name the name of the composite component + * @param source the source element that defines the root of the composite component + */ + public CompositeComponentDefinition(String name, @Nullable Object source) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + this.source = source; + } + + + @Override + public String getName() { + return this.name; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + + /** + * Add the given component as nested element of this composite component. + * @param component the nested component to add + */ + public void addNestedComponent(ComponentDefinition component) { + Assert.notNull(component, "ComponentDefinition must not be null"); + this.nestedComponents.add(component); + } + + /** + * Return the nested components that this composite component holds. + * @return the array of nested components, or an empty array if none + */ + public ComponentDefinition[] getNestedComponents() { + return this.nestedComponents.toArray(new ComponentDefinition[0]); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java new file mode 100644 index 0000000..b6c5956 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ConstructorArgumentEntry.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.util.Assert; + +/** + * {@link ParseState} entry representing a (possibly indexed) + * constructor argument. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class ConstructorArgumentEntry implements ParseState.Entry { + + private final int index; + + + /** + * Creates a new instance of the {@link ConstructorArgumentEntry} class + * representing a constructor argument with a (currently) unknown index. + */ + public ConstructorArgumentEntry() { + this.index = -1; + } + + /** + * Creates a new instance of the {@link ConstructorArgumentEntry} class + * representing a constructor argument at the supplied {@code index}. + * @param index the index of the constructor argument + * @throws IllegalArgumentException if the supplied {@code index} + * is less than zero + */ + public ConstructorArgumentEntry(int index) { + Assert.isTrue(index >= 0, "Constructor argument index must be greater than or equal to zero"); + this.index = index; + } + + + @Override + public String toString() { + return "Constructor-arg" + (this.index >= 0 ? " #" + this.index : ""); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java new file mode 100644 index 0000000..90b2442 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/DefaultsDefinition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 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.beans.factory.parsing; + +import org.springframework.beans.BeanMetadataElement; + +/** + * Marker interface for a defaults definition, + * extending BeanMetadataElement to inherit source exposure. + * + *

Concrete implementations are typically based on 'document defaults', + * for example specified at the root tag level within an XML document. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition + * @see ReaderEventListener#defaultsRegistered(DefaultsDefinition) + */ +public interface DefaultsDefinition extends BeanMetadataElement { + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java new file mode 100644 index 0000000..397db05 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/EmptyReaderEventListener.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +/** + * Empty implementation of the {@link ReaderEventListener} interface, + * providing no-op implementations of all callback methods. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class EmptyReaderEventListener implements ReaderEventListener { + + @Override + public void defaultsRegistered(DefaultsDefinition defaultsDefinition) { + // no-op + } + + @Override + public void componentRegistered(ComponentDefinition componentDefinition) { + // no-op + } + + @Override + public void aliasRegistered(AliasDefinition aliasDefinition) { + // no-op + } + + @Override + public void importProcessed(ImportDefinition importDefinition) { + // no-op + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java new file mode 100644 index 0000000..3dc71bb --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/FailFastProblemReporter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2018 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.beans.factory.parsing; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.lang.Nullable; + +/** + * Simple {@link ProblemReporter} implementation that exhibits fail-fast + * behavior when errors are encountered. + * + *

The first error encountered results in a {@link BeanDefinitionParsingException} + * being thrown. + * + *

Warnings are written to + * {@link #setLogger(org.apache.commons.logging.Log) the log} for this class. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Rick Evans + * @since 2.0 + */ +public class FailFastProblemReporter implements ProblemReporter { + + private Log logger = LogFactory.getLog(getClass()); + + + /** + * Set the {@link Log logger} that is to be used to report warnings. + *

If set to {@code null} then a default {@link Log logger} set to + * the name of the instance class will be used. + * @param logger the {@link Log logger} that is to be used to report warnings + */ + public void setLogger(@Nullable Log logger) { + this.logger = (logger != null ? logger : LogFactory.getLog(getClass())); + } + + + /** + * Throws a {@link BeanDefinitionParsingException} detailing the error + * that has occurred. + * @param problem the source of the error + */ + @Override + public void fatal(Problem problem) { + throw new BeanDefinitionParsingException(problem); + } + + /** + * Throws a {@link BeanDefinitionParsingException} detailing the error + * that has occurred. + * @param problem the source of the error + */ + @Override + public void error(Problem problem) { + throw new BeanDefinitionParsingException(problem); + } + + /** + * Writes the supplied {@link Problem} to the {@link Log} at {@code WARN} level. + * @param problem the source of the warning + */ + @Override + public void warning(Problem problem) { + logger.warn(problem, problem.getRootCause()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java new file mode 100644 index 0000000..3740250 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ImportDefinition.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2017 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.beans.factory.parsing; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Representation of an import that has been processed during the parsing process. + * + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderEventListener#importProcessed(ImportDefinition) + */ +public class ImportDefinition implements BeanMetadataElement { + + private final String importedResource; + + @Nullable + private final Resource[] actualResources; + + @Nullable + private final Object source; + + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + */ + public ImportDefinition(String importedResource) { + this(importedResource, null, null); + } + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + * @param source the source object (may be {@code null}) + */ + public ImportDefinition(String importedResource, @Nullable Object source) { + this(importedResource, null, source); + } + + /** + * Create a new ImportDefinition. + * @param importedResource the location of the imported resource + * @param source the source object (may be {@code null}) + */ + public ImportDefinition(String importedResource, @Nullable Resource[] actualResources, @Nullable Object source) { + Assert.notNull(importedResource, "Imported resource must not be null"); + this.importedResource = importedResource; + this.actualResources = actualResources; + this.source = source; + } + + + /** + * Return the location of the imported resource. + */ + public final String getImportedResource() { + return this.importedResource; + } + + @Nullable + public final Resource[] getActualResources() { + return this.actualResources; + } + + @Override + @Nullable + public final Object getSource() { + return this.source; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Location.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Location.java new file mode 100644 index 0000000..b06c524 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Location.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2017 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.beans.factory.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Class that models an arbitrary location in a {@link Resource resource}. + * + *

Typically used to track the location of problematic or erroneous + * metadata in XML configuration files. For example, a + * {@link #getSource() source} location might be 'The bean defined on + * line 76 of beans.properties has an invalid Class'; another source might + * be the actual DOM Element from a parsed XML {@link org.w3c.dom.Document}; + * or the source object might simply be {@code null}. + * + * @author Rob Harrop + * @since 2.0 + */ +public class Location { + + private final Resource resource; + + @Nullable + private final Object source; + + + /** + * Create a new instance of the {@link Location} class. + * @param resource the resource with which this location is associated + */ + public Location(Resource resource) { + this(resource, null); + } + + /** + * Create a new instance of the {@link Location} class. + * @param resource the resource with which this location is associated + * @param source the actual location within the associated resource + * (may be {@code null}) + */ + public Location(Resource resource, @Nullable Object source) { + Assert.notNull(resource, "Resource must not be null"); + this.resource = resource; + this.source = source; + } + + + /** + * Get the resource with which this location is associated. + */ + public Resource getResource() { + return this.resource; + } + + /** + * Get the actual location within the associated {@link #getResource() resource} + * (may be {@code null}). + *

See the {@link Location class level javadoc for this class} for examples + * of what the actual type of the returned object may be. + */ + @Nullable + public Object getSource() { + return this.source; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java new file mode 100644 index 0000000..1205b3f --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/NullSourceExtractor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +/** + * Simple implementation of {@link SourceExtractor} that returns {@code null} + * as the source metadata. + * + *

This is the default implementation and prevents too much metadata from being + * held in memory during normal (non-tooled) runtime usage. + * + * @author Rob Harrop + * @since 2.0 + */ +public class NullSourceExtractor implements SourceExtractor { + + /** + * This implementation simply returns {@code null} for any input. + */ + @Override + @Nullable + public Object extractSource(Object sourceCandidate, @Nullable Resource definitionResource) { + return null; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java new file mode 100644 index 0000000..0277bc0 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2020 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.beans.factory.parsing; + +import java.util.ArrayDeque; + +import org.springframework.lang.Nullable; + +/** + * Simple {@link ArrayDeque}-based structure for tracking the logical position during + * a parsing process. {@link Entry entries} are added to the ArrayDeque at each point + * during the parse phase in a reader-specific manner. + * + *

Calling {@link #toString()} will render a tree-style view of the current logical + * position in the parse phase. This representation is intended for use in error messages. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public final class ParseState { + + /** + * Internal {@link ArrayDeque} storage. + */ + private final ArrayDeque state; + + + /** + * Create a new {@code ParseState} with an empty {@link ArrayDeque}. + */ + public ParseState() { + this.state = new ArrayDeque<>(); + } + + /** + * Create a new {@code ParseState} whose {@link ArrayDeque} is a clone + * of the state in the passed-in {@code ParseState}. + */ + private ParseState(ParseState other) { + this.state = other.state.clone(); + } + + + /** + * Add a new {@link Entry} to the {@link ArrayDeque}. + */ + public void push(Entry entry) { + this.state.push(entry); + } + + /** + * Remove an {@link Entry} from the {@link ArrayDeque}. + */ + public void pop() { + this.state.pop(); + } + + /** + * Return the {@link Entry} currently at the top of the {@link ArrayDeque} or + * {@code null} if the {@link ArrayDeque} is empty. + */ + @Nullable + public Entry peek() { + return this.state.peek(); + } + + /** + * Create a new instance of {@link ParseState} which is an independent snapshot + * of this instance. + */ + public ParseState snapshot() { + return new ParseState(this); + } + + + /** + * Returns a tree-style representation of the current {@code ParseState}. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + int i = 0; + for (ParseState.Entry entry : this.state) { + if (i > 0) { + sb.append('\n'); + for (int j = 0; j < i; j++) { + sb.append('\t'); + } + sb.append("-> "); + } + sb.append(entry); + i++; + } + return sb.toString(); + } + + + /** + * Marker interface for entries into the {@link ParseState}. + */ + public interface Entry { + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java new file mode 100644 index 0000000..1365c99 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PassThroughSourceExtractor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +/** + * Simple {@link SourceExtractor} implementation that just passes + * the candidate source metadata object through for attachment. + * + *

Using this implementation means that tools will get raw access to the + * underlying configuration source metadata provided by the tool. + * + *

This implementation should not be used in a production + * application since it is likely to keep too much metadata in memory + * (unnecessarily). + * + * @author Rob Harrop + * @since 2.0 + */ +public class PassThroughSourceExtractor implements SourceExtractor { + + /** + * Simply returns the supplied {@code sourceCandidate} as-is. + * @param sourceCandidate the source metadata + * @return the supplied {@code sourceCandidate} + */ + @Override + public Object extractSource(Object sourceCandidate, @Nullable Resource definingResource) { + return sourceCandidate; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java new file mode 100644 index 0000000..86d5800 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2017 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.beans.factory.parsing; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Represents a problem with a bean definition configuration. + * Mainly serves as common argument passed into a {@link ProblemReporter}. + * + *

May indicate a potentially fatal problem (an error) or just a warning. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see ProblemReporter + */ +public class Problem { + + private final String message; + + private final Location location; + + @Nullable + private final ParseState parseState; + + @Nullable + private final Throwable rootCause; + + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location) { + this(message, location, null, null); + } + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param parseState the {@link ParseState} at the time of the error + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location, ParseState parseState) { + this(message, location, parseState, null); + } + + /** + * Create a new instance of the {@link Problem} class. + * @param message a message detailing the problem + * @param rootCause the underlying exception that caused the error (may be {@code null}) + * @param parseState the {@link ParseState} at the time of the error + * @param location the location within a bean configuration source that triggered the error + */ + public Problem(String message, Location location, @Nullable ParseState parseState, @Nullable Throwable rootCause) { + Assert.notNull(message, "Message must not be null"); + Assert.notNull(location, "Location must not be null"); + this.message = message; + this.location = location; + this.parseState = parseState; + this.rootCause = rootCause; + } + + + /** + * Get the message detailing the problem. + */ + public String getMessage() { + return this.message; + } + + /** + * Get the location within a bean configuration source that triggered the error. + */ + public Location getLocation() { + return this.location; + } + + /** + * Get the description of the bean configuration source that triggered the error, + * as contained within this Problem's Location object. + * @see #getLocation() + */ + public String getResourceDescription() { + return getLocation().getResource().getDescription(); + } + + /** + * Get the {@link ParseState} at the time of the error (may be {@code null}). + */ + @Nullable + public ParseState getParseState() { + return this.parseState; + } + + /** + * Get the underlying exception that caused the error (may be {@code null}). + */ + @Nullable + public Throwable getRootCause() { + return this.rootCause; + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Configuration problem: "); + sb.append(getMessage()); + sb.append("\nOffending resource: ").append(getResourceDescription()); + if (getParseState() != null) { + sb.append('\n').append(getParseState()); + } + return sb.toString(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java new file mode 100644 index 0000000..b9ded86 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ProblemReporter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 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.beans.factory.parsing; + +/** + * SPI interface allowing tools and other external processes to handle errors + * and warnings reported during bean definition parsing. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see Problem + */ +public interface ProblemReporter { + + /** + * Called when a fatal error is encountered during the parsing process. + *

Implementations must treat the given problem as fatal, + * i.e. they have to eventually raise an exception. + * @param problem the source of the error (never {@code null}) + */ + void fatal(Problem problem); + + /** + * Called when an error is encountered during the parsing process. + *

Implementations may choose to treat errors as fatal. + * @param problem the source of the error (never {@code null}) + */ + void error(Problem problem); + + /** + * Called when a warning is raised during the parsing process. + *

Warnings are never considered to be fatal. + * @param problem the source of the warning (never {@code null}) + */ + void warning(Problem problem); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java new file mode 100644 index 0000000..c20235a --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/PropertyEntry.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2020 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.beans.factory.parsing; + +import org.springframework.util.StringUtils; + +/** + * {@link ParseState} entry representing a JavaBean property. + * + * @author Rob Harrop + * @since 2.0 + */ +public class PropertyEntry implements ParseState.Entry { + + private final String name; + + + /** + * Create a new {@code PropertyEntry} instance. + * @param name the name of the JavaBean property represented by this instance + */ + public PropertyEntry(String name) { + if (!StringUtils.hasText(name)) { + throw new IllegalArgumentException("Invalid property name '" + name + "'"); + } + this.name = name; + } + + + @Override + public String toString() { + return "Property '" + this.name + "'"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java new file mode 100644 index 0000000..45283e5 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/QualifierEntry.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2020 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.beans.factory.parsing; + +import org.springframework.util.StringUtils; + +/** + * {@link ParseState} entry representing an autowire candidate qualifier. + * + * @author Mark Fisher + * @since 2.5 + */ +public class QualifierEntry implements ParseState.Entry { + + private final String typeName; + + + /** + * Create a new {@code QualifierEntry} instance. + * @param typeName the name of the qualifier type + */ + public QualifierEntry(String typeName) { + if (!StringUtils.hasText(typeName)) { + throw new IllegalArgumentException("Invalid qualifier type '" + typeName + "'"); + } + this.typeName = typeName; + } + + + @Override + public String toString() { + return "Qualifier '" + this.typeName + "'"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java new file mode 100644 index 0000000..2b95aa8 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java @@ -0,0 +1,211 @@ +/* + * Copyright 2002-2020 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.beans.factory.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +/** + * Context that gets passed along a bean definition reading process, + * encapsulating all relevant configuration as well as state. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class ReaderContext { + + private final Resource resource; + + private final ProblemReporter problemReporter; + + private final ReaderEventListener eventListener; + + private final SourceExtractor sourceExtractor; + + + /** + * Construct a new {@code ReaderContext}. + * @param resource the XML bean definition resource + * @param problemReporter the problem reporter in use + * @param eventListener the event listener in use + * @param sourceExtractor the source extractor in use + */ + public ReaderContext(Resource resource, ProblemReporter problemReporter, + ReaderEventListener eventListener, SourceExtractor sourceExtractor) { + + this.resource = resource; + this.problemReporter = problemReporter; + this.eventListener = eventListener; + this.sourceExtractor = sourceExtractor; + } + + public final Resource getResource() { + return this.resource; + } + + + // Errors and warnings + + /** + * Raise a fatal error. + */ + public void fatal(String message, @Nullable Object source) { + fatal(message, source, null, null); + } + + /** + * Raise a fatal error. + */ + public void fatal(String message, @Nullable Object source, @Nullable Throwable cause) { + fatal(message, source, null, cause); + } + + /** + * Raise a fatal error. + */ + public void fatal(String message, @Nullable Object source, @Nullable ParseState parseState) { + fatal(message, source, parseState, null); + } + + /** + * Raise a fatal error. + */ + public void fatal(String message, @Nullable Object source, @Nullable ParseState parseState, @Nullable Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.fatal(new Problem(message, location, parseState, cause)); + } + + /** + * Raise a regular error. + */ + public void error(String message, @Nullable Object source) { + error(message, source, null, null); + } + + /** + * Raise a regular error. + */ + public void error(String message, @Nullable Object source, @Nullable Throwable cause) { + error(message, source, null, cause); + } + + /** + * Raise a regular error. + */ + public void error(String message, @Nullable Object source, @Nullable ParseState parseState) { + error(message, source, parseState, null); + } + + /** + * Raise a regular error. + */ + public void error(String message, @Nullable Object source, @Nullable ParseState parseState, @Nullable Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.error(new Problem(message, location, parseState, cause)); + } + + /** + * Raise a non-critical warning. + */ + public void warning(String message, @Nullable Object source) { + warning(message, source, null, null); + } + + /** + * Raise a non-critical warning. + */ + public void warning(String message, @Nullable Object source, @Nullable Throwable cause) { + warning(message, source, null, cause); + } + + /** + * Raise a non-critical warning. + */ + public void warning(String message, @Nullable Object source, @Nullable ParseState parseState) { + warning(message, source, parseState, null); + } + + /** + * Raise a non-critical warning. + */ + public void warning(String message, @Nullable Object source, @Nullable ParseState parseState, @Nullable Throwable cause) { + Location location = new Location(getResource(), source); + this.problemReporter.warning(new Problem(message, location, parseState, cause)); + } + + + // Explicit parse events + + /** + * Fire a defaults-registered event. + */ + public void fireDefaultsRegistered(DefaultsDefinition defaultsDefinition) { + this.eventListener.defaultsRegistered(defaultsDefinition); + } + + /** + * Fire a component-registered event. + */ + public void fireComponentRegistered(ComponentDefinition componentDefinition) { + this.eventListener.componentRegistered(componentDefinition); + } + + /** + * Fire an alias-registered event. + */ + public void fireAliasRegistered(String beanName, String alias, @Nullable Object source) { + this.eventListener.aliasRegistered(new AliasDefinition(beanName, alias, source)); + } + + /** + * Fire an import-processed event. + */ + public void fireImportProcessed(String importedResource, @Nullable Object source) { + this.eventListener.importProcessed(new ImportDefinition(importedResource, source)); + } + + /** + * Fire an import-processed event. + */ + public void fireImportProcessed(String importedResource, Resource[] actualResources, @Nullable Object source) { + this.eventListener.importProcessed(new ImportDefinition(importedResource, actualResources, source)); + } + + + // Source extraction + + /** + * Return the source extractor in use. + */ + public SourceExtractor getSourceExtractor() { + return this.sourceExtractor; + } + + /** + * Call the source extractor for the given source object. + * @param sourceCandidate the original source object + * @return the source object to store, or {@code null} for none. + * @see #getSourceExtractor() + * @see SourceExtractor#extractSource + */ + @Nullable + public Object extractSource(Object sourceCandidate) { + return this.sourceExtractor.extractSource(sourceCandidate, this.resource); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java new file mode 100644 index 0000000..24a4050 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderEventListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2007 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.beans.factory.parsing; + +import java.util.EventListener; + +/** + * Interface that receives callbacks for component, alias and import + * registrations during a bean definition reading process. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see ReaderContext + */ +public interface ReaderEventListener extends EventListener { + + /** + * Notification that the given defaults has been registered. + * @param defaultsDefinition a descriptor for the defaults + * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition + */ + void defaultsRegistered(DefaultsDefinition defaultsDefinition); + + /** + * Notification that the given component has been registered. + * @param componentDefinition a descriptor for the new component + * @see BeanComponentDefinition + */ + void componentRegistered(ComponentDefinition componentDefinition); + + /** + * Notification that the given alias has been registered. + * @param aliasDefinition a descriptor for the new alias + */ + void aliasRegistered(AliasDefinition aliasDefinition); + + /** + * Notification that the given import has been processed. + * @param importDefinition a descriptor for the import + */ + void importProcessed(ImportDefinition importDefinition); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java new file mode 100644 index 0000000..8809cd2 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/SourceExtractor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2016 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.beans.factory.parsing; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +/** + * Simple strategy allowing tools to control how source metadata is attached + * to the bean definition metadata. + * + *

Configuration parsers may provide the ability to attach + * source metadata during the parse phase. They will offer this metadata in a + * generic format which can be further modified by a {@link SourceExtractor} + * before being attached to the bean definition metadata. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.BeanMetadataElement#getSource() + * @see org.springframework.beans.factory.config.BeanDefinition + */ +@FunctionalInterface +public interface SourceExtractor { + + /** + * Extract the source metadata from the candidate object supplied + * by the configuration parser. + * @param sourceCandidate the original source metadata (never {@code null}) + * @param definingResource the resource that defines the given source object + * (may be {@code null}) + * @return the source metadata object to store (may be {@code null}) + */ + @Nullable + Object extractSource(Object sourceCandidate, @Nullable Resource definingResource); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/package-info.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/package-info.java new file mode 100644 index 0000000..0f57ef1 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/package-info.java @@ -0,0 +1,9 @@ +/** + * Support infrastructure for bean definition parsing. + */ +@NonNullApi +@NonNullFields +package org.springframework.beans.factory.parsing; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; 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 new file mode 100644 index 0000000..4defe32 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -0,0 +1,2026 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; + +import org.apache.commons.logging.Log; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyAccessorUtils; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.Aware; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.InjectionPoint; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.AutowiredPropertyMarker; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +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.ParameterNameDiscoverer; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.StringUtils; + +/** + * Abstract bean factory superclass that implements default bean creation, + * with the full capabilities specified by the {@link RootBeanDefinition} class. + * Implements the {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory} + * interface in addition to AbstractBeanFactory's {@link #createBean} method. + * + *

Provides bean creation (with constructor resolution), property population, + * wiring (including autowiring), and initialization. Handles runtime bean + * references, resolves managed collections, calls initialization methods, etc. + * Supports autowiring constructors, properties by name, and properties by type. + * + *

The main template method to be implemented by subclasses is + * {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)}, + * used for autowiring by type. In case of a factory which is capable of searching + * its bean definitions, matching beans will typically be implemented through such + * a search. For other factory styles, simplified matching algorithms can be implemented. + * + *

Note that this class does not assume or implement bean definition + * registry capabilities. See {@link DefaultListableBeanFactory} for an implementation + * of the {@link org.springframework.beans.factory.ListableBeanFactory} and + * {@link BeanDefinitionRegistry} interfaces, which represent the API and SPI + * view of such a factory, respectively. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @author Mark Fisher + * @author Costin Leau + * @author Chris Beams + * @author Sam Brannen + * @author Phillip Webb + * @since 13.02.2004 + * @see RootBeanDefinition + * @see DefaultListableBeanFactory + * @see BeanDefinitionRegistry + */ +public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory + implements AutowireCapableBeanFactory { + + /** + * Whether this environment lives within a native image. + * Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594. + * @see ImageInfo.java + */ + private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null); + + + /** Strategy for creating bean instances. */ + private InstantiationStrategy instantiationStrategy; + + /** Resolver strategy for method parameter names. */ + @Nullable + private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + /** Whether to automatically try to resolve circular references between beans. */ + private boolean allowCircularReferences = true; + + /** + * Whether to resort to injecting a raw bean instance in case of circular reference, + * even if the injected bean eventually got wrapped. + */ + private boolean allowRawInjectionDespiteWrapping = false; + + /** + * Dependency types to ignore on dependency check and autowire, as Set of + * Class objects: for example, String. Default is none. + */ + private final Set> ignoredDependencyTypes = new HashSet<>(); + + /** + * Dependency interfaces to ignore on dependency check and autowire, as Set of + * Class objects. By default, only the BeanFactory interface is ignored. + */ + private final Set> ignoredDependencyInterfaces = new HashSet<>(); + + /** + * The name of the currently created bean, for implicit dependency registration + * on getBean etc invocations triggered from a user-specified Supplier callback. + */ + private final NamedThreadLocal currentlyCreatedBean = new NamedThreadLocal<>("Currently created bean"); + + /** Cache of unfinished FactoryBean instances: FactoryBean name to BeanWrapper. */ + private final ConcurrentMap factoryBeanInstanceCache = new ConcurrentHashMap<>(); + + /** Cache of candidate factory methods per factory class. */ + private final ConcurrentMap, Method[]> factoryMethodCandidateCache = new ConcurrentHashMap<>(); + + /** Cache of filtered PropertyDescriptors: bean Class to PropertyDescriptor array. */ + private final ConcurrentMap, PropertyDescriptor[]> filteredPropertyDescriptorsCache = + new ConcurrentHashMap<>(); + + + /** + * Create a new AbstractAutowireCapableBeanFactory. + */ + public AbstractAutowireCapableBeanFactory() { + super(); + ignoreDependencyInterface(BeanNameAware.class); + ignoreDependencyInterface(BeanFactoryAware.class); + ignoreDependencyInterface(BeanClassLoaderAware.class); + if (IN_NATIVE_IMAGE) { + this.instantiationStrategy = new SimpleInstantiationStrategy(); + } + else { + this.instantiationStrategy = new CglibSubclassingInstantiationStrategy(); + } + } + + /** + * Create a new AbstractAutowireCapableBeanFactory with the given parent. + * @param parentBeanFactory parent bean factory, or {@code null} if none + */ + public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) { + this(); + setParentBeanFactory(parentBeanFactory); + } + + + /** + * Set the instantiation strategy to use for creating bean instances. + * Default is CglibSubclassingInstantiationStrategy. + * @see CglibSubclassingInstantiationStrategy + */ + public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) { + this.instantiationStrategy = instantiationStrategy; + } + + /** + * Return the instantiation strategy to use for creating bean instances. + */ + protected InstantiationStrategy getInstantiationStrategy() { + return this.instantiationStrategy; + } + + /** + * Set the ParameterNameDiscoverer to use for resolving method parameter + * names if needed (e.g. for constructor names). + *

Default is a {@link DefaultParameterNameDiscoverer}. + */ + public void setParameterNameDiscoverer(@Nullable ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + /** + * Return the ParameterNameDiscoverer to use for resolving method parameter + * names if needed. + */ + @Nullable + protected ParameterNameDiscoverer getParameterNameDiscoverer() { + return this.parameterNameDiscoverer; + } + + /** + * Set whether to allow circular references between beans - and automatically + * try to resolve them. + *

Note that circular reference resolution means that one of the involved beans + * will receive a reference to another bean that is not fully initialized yet. + * This can lead to subtle and not-so-subtle side effects on initialization; + * it does work fine for many scenarios, though. + *

Default is "true". Turn this off to throw an exception when encountering + * a circular reference, disallowing them completely. + *

NOTE: It is generally recommended to not rely on circular references + * between your beans. Refactor your application logic to have the two beans + * involved delegate to a third bean that encapsulates their common logic. + */ + public void setAllowCircularReferences(boolean allowCircularReferences) { + this.allowCircularReferences = allowCircularReferences; + } + + /** + * Set whether to allow the raw injection of a bean instance into some other + * bean's property, despite the injected bean eventually getting wrapped + * (for example, through AOP auto-proxying). + *

This will only be used as a last resort in case of a circular reference + * that cannot be resolved otherwise: essentially, preferring a raw instance + * getting injected over a failure of the entire bean wiring process. + *

Default is "false", as of Spring 2.0. Turn this on to allow for non-wrapped + * raw beans injected into some of your references, which was Spring 1.2's + * (arguably unclean) default behavior. + *

NOTE: It is generally recommended to not rely on circular references + * between your beans, in particular with auto-proxying involved. + * @see #setAllowCircularReferences + */ + public void setAllowRawInjectionDespiteWrapping(boolean allowRawInjectionDespiteWrapping) { + this.allowRawInjectionDespiteWrapping = allowRawInjectionDespiteWrapping; + } + + /** + * Ignore the given dependency type for autowiring: + * for example, String. Default is none. + */ + public void ignoreDependencyType(Class type) { + this.ignoredDependencyTypes.add(type); + } + + /** + * Ignore the given dependency interface for autowiring. + *

This will typically be used by application contexts to register + * dependencies that are resolved in other ways, like BeanFactory through + * BeanFactoryAware or ApplicationContext through ApplicationContextAware. + *

By default, only the BeanFactoryAware interface is ignored. + * For further types to ignore, invoke this method for each type. + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.context.ApplicationContextAware + */ + public void ignoreDependencyInterface(Class ifc) { + this.ignoredDependencyInterfaces.add(ifc); + } + + @Override + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + super.copyConfigurationFrom(otherFactory); + if (otherFactory instanceof AbstractAutowireCapableBeanFactory) { + AbstractAutowireCapableBeanFactory otherAutowireFactory = + (AbstractAutowireCapableBeanFactory) otherFactory; + this.instantiationStrategy = otherAutowireFactory.instantiationStrategy; + this.allowCircularReferences = otherAutowireFactory.allowCircularReferences; + this.ignoredDependencyTypes.addAll(otherAutowireFactory.ignoredDependencyTypes); + this.ignoredDependencyInterfaces.addAll(otherAutowireFactory.ignoredDependencyInterfaces); + } + } + + + //------------------------------------------------------------------------- + // Typical methods for creating and populating external bean instances + //------------------------------------------------------------------------- + + @Override + @SuppressWarnings("unchecked") + public T createBean(Class beanClass) throws BeansException { + // Use prototype bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass); + bd.setScope(SCOPE_PROTOTYPE); + bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader()); + return (T) createBean(beanClass.getName(), bd, null); + } + + @Override + public void autowireBean(Object existingBean) { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(ClassUtils.getUserClass(existingBean)); + bd.setScope(SCOPE_PROTOTYPE); + bd.allowCaching = ClassUtils.isCacheSafe(bd.getBeanClass(), getBeanClassLoader()); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(bd.getBeanClass().getName(), bd, bw); + } + + @Override + public Object configureBean(Object existingBean, String beanName) throws BeansException { + markBeanAsCreated(beanName); + BeanDefinition mbd = getMergedBeanDefinition(beanName); + RootBeanDefinition bd = null; + if (mbd instanceof RootBeanDefinition) { + RootBeanDefinition rbd = (RootBeanDefinition) mbd; + bd = (rbd.isPrototype() ? rbd : rbd.cloneBeanDefinition()); + } + if (bd == null) { + bd = new RootBeanDefinition(mbd); + } + if (!bd.isPrototype()) { + bd.setScope(SCOPE_PROTOTYPE); + bd.allowCaching = ClassUtils.isCacheSafe(ClassUtils.getUserClass(existingBean), getBeanClassLoader()); + } + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(beanName, bd, bw); + return initializeBean(beanName, existingBean, bd); + } + + + //------------------------------------------------------------------------- + // Specialized methods for fine-grained control over the bean lifecycle + //------------------------------------------------------------------------- + + @Override + public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); + bd.setScope(SCOPE_PROTOTYPE); + return createBean(beanClass.getName(), bd, null); + } + + @Override + public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); + bd.setScope(SCOPE_PROTOTYPE); + if (bd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) { + return autowireConstructor(beanClass.getName(), bd, null, null).getWrappedInstance(); + } + else { + Object bean; + if (System.getSecurityManager() != null) { + bean = AccessController.doPrivileged( + (PrivilegedAction) () -> getInstantiationStrategy().instantiate(bd, null, this), + getAccessControlContext()); + } + else { + bean = getInstantiationStrategy().instantiate(bd, null, this); + } + populateBean(beanClass.getName(), bd, new BeanWrapperImpl(bean)); + return bean; + } + } + + @Override + public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) + throws BeansException { + + if (autowireMode == AUTOWIRE_CONSTRUCTOR) { + throw new IllegalArgumentException("AUTOWIRE_CONSTRUCTOR not supported for existing bean instance"); + } + // Use non-singleton bean definition, to avoid registering bean as dependent bean. + RootBeanDefinition bd = + new RootBeanDefinition(ClassUtils.getUserClass(existingBean), autowireMode, dependencyCheck); + bd.setScope(SCOPE_PROTOTYPE); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + populateBean(bd.getBeanClass().getName(), bd, bw); + } + + @Override + public void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException { + markBeanAsCreated(beanName); + BeanDefinition bd = getMergedBeanDefinition(beanName); + BeanWrapper bw = new BeanWrapperImpl(existingBean); + initBeanWrapper(bw); + applyPropertyValues(beanName, bd, bw, bd.getPropertyValues()); + } + + @Override + public Object initializeBean(Object existingBean, String beanName) { + return initializeBean(beanName, existingBean, null); + } + + @Override + public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (BeanPostProcessor processor : getBeanPostProcessors()) { + Object current = processor.postProcessBeforeInitialization(result, beanName); + if (current == null) { + return result; + } + result = current; + } + return result; + } + + @Override + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (BeanPostProcessor processor : getBeanPostProcessors()) { + Object current = processor.postProcessAfterInitialization(result, beanName); + if (current == null) { + return result; + } + result = current; + } + return result; + } + + @Override + public void destroyBean(Object existingBean) { + new DisposableBeanAdapter( + existingBean, getBeanPostProcessorCache().destructionAware, getAccessControlContext()).destroy(); + } + + + //------------------------------------------------------------------------- + // Delegate methods for resolving injection points + //------------------------------------------------------------------------- + + @Override + public Object resolveBeanByName(String name, DependencyDescriptor descriptor) { + InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); + try { + return getBean(name, descriptor.getDependencyType()); + } + finally { + ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); + } + } + + @Override + @Nullable + public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException { + return resolveDependency(descriptor, requestingBeanName, null, null); + } + + + //--------------------------------------------------------------------- + // Implementation of relevant AbstractBeanFactory template methods + //--------------------------------------------------------------------- + + /** + * Central method of this class: creates a bean instance, + * populates the bean instance, applies post-processors, etc. + * @see #doCreateBean + */ + @Override + protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + if (logger.isTraceEnabled()) { + logger.trace("Creating instance of bean '" + beanName + "'"); + } + RootBeanDefinition mbdToUse = mbd; + + // Make sure bean class is actually resolved at this point, and + // clone the bean definition in case of a dynamically resolved Class + // which cannot be stored in the shared merged bean definition. + Class resolvedClass = resolveBeanClass(mbd, beanName); + if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { + mbdToUse = new RootBeanDefinition(mbd); + mbdToUse.setBeanClass(resolvedClass); + } + + // Prepare method overrides. + try { + mbdToUse.prepareMethodOverrides(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), + beanName, "Validation of method overrides failed", ex); + } + + try { + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + Object bean = resolveBeforeInstantiation(beanName, mbdToUse); + if (bean != null) { + return bean; + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, + "BeanPostProcessor before instantiation of bean failed", ex); + } + + try { + Object beanInstance = doCreateBean(beanName, mbdToUse, args); + if (logger.isTraceEnabled()) { + logger.trace("Finished creating instance of bean '" + beanName + "'"); + } + return beanInstance; + } + catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { + // A previously detected exception with proper bean creation context already, + // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. + throw ex; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); + } + } + + /** + * Actually create the specified bean. Pre-creation processing has already happened + * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks. + *

Differentiates between default bean instantiation, use of a + * factory method, and autowiring a constructor. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param args explicit arguments to use for constructor or factory method invocation + * @return a new instance of the bean + * @throws BeanCreationException if the bean could not be created + * @see #instantiateBean + * @see #instantiateUsingFactoryMethod + * @see #autowireConstructor + */ + protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + // Instantiate the bean. + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // Allow post-processors to modify the merged bean definition. + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex); + } + mbd.postProcessed = true; + } + } + + // Eagerly cache singletons to be able to resolve circular references + // even when triggered by lifecycle interfaces like BeanFactoryAware. + boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && + isSingletonCurrentlyInCreation(beanName)); + if (earlySingletonExposure) { + if (logger.isTraceEnabled()) { + logger.trace("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); + } + + // Initialize the bean instance. + Object exposedObject = bean; + try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + catch (Throwable ex) { + if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { + throw (BeanCreationException) ex; + } + else { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); + } + } + + if (earlySingletonExposure) { + Object earlySingletonReference = getSingleton(beanName, false); + if (earlySingletonReference != null) { + if (exposedObject == bean) { + exposedObject = earlySingletonReference; + } + else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { + String[] dependentBeans = getDependentBeans(beanName); + Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); + for (String dependentBean : dependentBeans) { + if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { + actualDependentBeans.add(dependentBean); + } + } + if (!actualDependentBeans.isEmpty()) { + throw new BeanCurrentlyInCreationException(beanName, + "Bean with name '" + beanName + "' has been injected into other beans [" + + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + + "] in its raw version as part of a circular reference, but has eventually been " + + "wrapped. This means that said other beans do not use the final version of the " + + "bean. This is often the result of over-eager type matching - consider using " + + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); + } + } + } + } + + // Register bean as disposable. + try { + registerDisposableBeanIfNecessary(beanName, bean, mbd); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); + } + + return exposedObject; + } + + @Override + @Nullable + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + Class targetType = determineTargetType(beanName, mbd, typesToMatch); + // Apply SmartInstantiationAwareBeanPostProcessors to predict the + // eventual type after a before-instantiation shortcut. + if (targetType != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + boolean matchingOnlyFactoryBean = typesToMatch.length == 1 && typesToMatch[0] == FactoryBean.class; + for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { + Class predicted = bp.predictBeanType(targetType, beanName); + if (predicted != null && + (!matchingOnlyFactoryBean || FactoryBean.class.isAssignableFrom(predicted))) { + return predicted; + } + } + } + return targetType; + } + + /** + * Determine the target type for the given bean definition. + * @param beanName the name of the bean (for error handling purposes) + * @param mbd the merged bean definition for the bean + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned {@code Class} will never be exposed to application code) + * @return the type for the bean if determinable, or {@code null} otherwise + */ + @Nullable + 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 (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) { + mbd.resolvedTargetType = targetType; + } + } + return targetType; + } + + /** + * Determine the target type for the given bean definition which is based on + * a factory method. Only called if there is no singleton instance registered + * for the target bean already. + *

This implementation determines the type matching {@link #createBean}'s + * different creation strategies. As far as possible, we'll perform static + * type checking to avoid creation of the target bean. + * @param beanName the name of the bean (for error handling purposes) + * @param mbd the merged bean definition for the bean + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned {@code Class} will never be exposed to application code) + * @return the type for the bean if determinable, or {@code null} otherwise + * @see #createBean + */ + @Nullable + protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + ResolvableType cachedReturnType = mbd.factoryMethodReturnType; + if (cachedReturnType != null) { + return cachedReturnType.resolve(); + } + + Class commonType = null; + Method uniqueCandidate = mbd.factoryMethodToIntrospect; + + if (uniqueCandidate == null) { + Class factoryClass; + boolean isStatic = true; + + String factoryBeanName = mbd.getFactoryBeanName(); + if (factoryBeanName != null) { + if (factoryBeanName.equals(beanName)) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "factory-bean reference points back to the same bean definition"); + } + // Check declared factory method return type on factory class. + factoryClass = getType(factoryBeanName); + isStatic = false; + } + else { + // Check declared factory method return type on bean class. + factoryClass = resolveBeanClass(mbd, beanName, typesToMatch); + } + + if (factoryClass == null) { + return null; + } + factoryClass = ClassUtils.getUserClass(factoryClass); + + // If all factory methods have the same return type, return that type. + // Can't clearly figure out exact method due to type converting / autowiring! + int minNrOfArgs = + (mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0); + Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(factoryClass, + clazz -> ReflectionUtils.getUniqueDeclaredMethods(clazz, ReflectionUtils.USER_DECLARED_METHODS)); + + for (Method candidate : candidates) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) && + candidate.getParameterCount() >= minNrOfArgs) { + // Declared type variables to inspect? + if (candidate.getTypeParameters().length > 0) { + try { + // Fully resolve parameter names and argument values. + Class[] paramTypes = candidate.getParameterTypes(); + String[] paramNames = null; + ParameterNameDiscoverer pnd = getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(candidate); + } + ConstructorArgumentValues cav = mbd.getConstructorArgumentValues(); + Set usedValueHolders = new HashSet<>(paramTypes.length); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < args.length; i++) { + ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue( + i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders); + if (valueHolder == null) { + valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders); + } + if (valueHolder != null) { + args[i] = valueHolder.getValue(); + usedValueHolders.add(valueHolder); + } + } + Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( + candidate, args, getBeanClassLoader()); + uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ? + candidate : null); + commonType = ClassUtils.determineCommonAncestor(returnType, commonType); + if (commonType == null) { + // Ambiguous return types found: return null to indicate "not determinable". + return null; + } + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to resolve generic return type for factory method: " + ex); + } + } + } + else { + uniqueCandidate = (commonType == null ? candidate : null); + commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType); + if (commonType == null) { + // Ambiguous return types found: return null to indicate "not determinable". + return null; + } + } + } + } + + mbd.factoryMethodToIntrospect = uniqueCandidate; + if (commonType == null) { + return null; + } + } + + // Common return type found: all factory methods return same type. For a non-parameterized + // unique candidate, cache the full type declaration context of the target factory method. + cachedReturnType = (uniqueCandidate != null ? + ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType)); + mbd.factoryMethodReturnType = cachedReturnType; + return cachedReturnType.resolve(); + } + + /** + * This implementation attempts to query the FactoryBean's generic parameter metadata + * if present to determine the object type. If not present, i.e. the FactoryBean is + * declared as a raw type, checks the FactoryBean's {@code getObjectType} method + * on a plain instance of the FactoryBean, without bean properties applied yet. + * If this doesn't return a type yet, and {@code allowInit} is {@code true} a + * full creation of the FactoryBean is used as fallback (through delegation to the + * superclass's implementation). + *

The shortcut check for a FactoryBean is only applied in case of a singleton + * FactoryBean. If the FactoryBean instance itself is not kept as singleton, + * it will be fully created to check the type of its exposed object. + */ + @Override + protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) { + // Check if the bean definition itself has defined the type with an attribute + ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd); + if (result != ResolvableType.NONE) { + return result; + } + + ResolvableType beanType = + (mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : ResolvableType.NONE); + + // For instance supplied beans try the target type and bean class + if (mbd.getInstanceSupplier() != null) { + result = getFactoryBeanGeneric(mbd.targetType); + if (result.resolve() != null) { + return result; + } + result = getFactoryBeanGeneric(beanType); + if (result.resolve() != null) { + return result; + } + } + + // Consider factory methods + String factoryBeanName = mbd.getFactoryBeanName(); + String factoryMethodName = mbd.getFactoryMethodName(); + + // Scan the factory bean methods + if (factoryBeanName != null) { + if (factoryMethodName != null) { + // Try to obtain the FactoryBean's object type from its factory method + // declaration without instantiating the containing bean at all. + BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName); + Class factoryBeanClass; + if (factoryBeanDefinition instanceof AbstractBeanDefinition && + ((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) { + factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass(); + } + else { + RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition); + factoryBeanClass = determineTargetType(factoryBeanName, fbmbd); + } + if (factoryBeanClass != null) { + result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName); + if (result.resolve() != null) { + return result; + } + } + } + // If not resolvable above and the referenced factory bean doesn't exist yet, + // exit here - we don't want to force the creation of another bean just to + // obtain a FactoryBean's object type... + if (!isBeanEligibleForMetadataCaching(factoryBeanName)) { + return ResolvableType.NONE; + } + } + + // If we're allowed, we can create the factory bean and call getObjectType() early + if (allowInit) { + FactoryBean factoryBean = (mbd.isSingleton() ? + getSingletonFactoryBeanForTypeCheck(beanName, mbd) : + getNonSingletonFactoryBeanForTypeCheck(beanName, mbd)); + if (factoryBean != null) { + // Try to obtain the FactoryBean's object type from this early stage of the instance. + Class type = getTypeForFactoryBean(factoryBean); + if (type != null) { + return ResolvableType.forClass(type); + } + // No type found for shortcut FactoryBean instance: + // fall back to full creation of the FactoryBean instance. + return super.getTypeForFactoryBean(beanName, mbd, true); + } + } + + if (factoryBeanName == null && mbd.hasBeanClass() && factoryMethodName != null) { + // No early bean instantiation possible: determine FactoryBean's type from + // static factory method signature or from class inheritance hierarchy... + return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName); + } + result = getFactoryBeanGeneric(beanType); + if (result.resolve() != null) { + return result; + } + return ResolvableType.NONE; + } + + private ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) { + if (type == null) { + return ResolvableType.NONE; + } + return type.as(FactoryBean.class).getGeneric(); + } + + /** + * Introspect the factory method signatures on the given bean class, + * trying to find a common {@code FactoryBean} object type declared there. + * @param beanClass the bean class to find the factory method on + * @param factoryMethodName the name of the factory method + * @return the common {@code FactoryBean} object type, or {@code null} if none + */ + private ResolvableType getTypeForFactoryBeanFromMethod(Class beanClass, String factoryMethodName) { + // CGLIB subclass methods hide generic parameters; look at the original user class. + Class factoryBeanClass = ClassUtils.getUserClass(beanClass); + FactoryBeanMethodTypeFinder finder = new FactoryBeanMethodTypeFinder(factoryMethodName); + ReflectionUtils.doWithMethods(factoryBeanClass, finder, ReflectionUtils.USER_DECLARED_METHODS); + return finder.getResult(); + } + + /** + * This implementation attempts to query the FactoryBean's generic parameter metadata + * if present to determine the object type. If not present, i.e. the FactoryBean is + * declared as a raw type, checks the FactoryBean's {@code getObjectType} method + * on a plain instance of the FactoryBean, without bean properties applied yet. + * If this doesn't return a type yet, a full creation of the FactoryBean is + * used as fallback (through delegation to the superclass's implementation). + *

The shortcut check for a FactoryBean is only applied in case of a singleton + * FactoryBean. If the FactoryBean instance itself is not kept as singleton, + * it will be fully created to check the type of its exposed object. + */ + @Override + @Deprecated + @Nullable + protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { + return getTypeForFactoryBean(beanName, mbd, true).resolve(); + } + + /** + * Obtain a reference for early access to the specified bean, + * typically for the purpose of resolving a circular reference. + * @param beanName the name of the bean (for error handling purposes) + * @param mbd the merged bean definition for the bean + * @param bean the raw bean instance + * @return the object to expose as bean reference + */ + protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { + Object exposedObject = bean; + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { + exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); + } + } + return exposedObject; + } + + + //--------------------------------------------------------------------- + // Implementation methods + //--------------------------------------------------------------------- + + /** + * Obtain a "shortcut" singleton FactoryBean instance to use for a + * {@code getObjectType()} call, without full initialization of the FactoryBean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the FactoryBean instance, or {@code null} to indicate + * that we couldn't obtain a shortcut FactoryBean instance + */ + @Nullable + private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + synchronized (getSingletonMutex()) { + BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName); + if (bw != null) { + return (FactoryBean) bw.getWrappedInstance(); + } + Object beanInstance = getSingleton(beanName, false); + if (beanInstance instanceof FactoryBean) { + return (FactoryBean) beanInstance; + } + if (isSingletonCurrentlyInCreation(beanName) || + (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { + return null; + } + + Object instance; + try { + // Mark this bean as currently in creation, even if just partially. + beforeSingletonCreation(beanName); + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + instance = resolveBeforeInstantiation(beanName, mbd); + if (instance == null) { + bw = createBeanInstance(beanName, mbd, null); + instance = bw.getWrappedInstance(); + } + } + catch (UnsatisfiedDependencyException ex) { + // Don't swallow, probably misconfiguration... + throw ex; + } + catch (BeanCreationException ex) { + // Instantiation failure, maybe too early... + if (logger.isDebugEnabled()) { + logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex); + } + onSuppressedException(ex); + return null; + } + finally { + // Finished partial creation of this bean. + afterSingletonCreation(beanName); + } + + FactoryBean fb = getFactoryBean(beanName, instance); + if (bw != null) { + this.factoryBeanInstanceCache.put(beanName, bw); + } + return fb; + } + } + + /** + * Obtain a "shortcut" non-singleton FactoryBean instance to use for a + * {@code getObjectType()} call, without full initialization of the FactoryBean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the FactoryBean instance, or {@code null} to indicate + * that we couldn't obtain a shortcut FactoryBean instance + */ + @Nullable + private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + if (isPrototypeCurrentlyInCreation(beanName)) { + return null; + } + + Object instance; + try { + // Mark this bean as currently in creation, even if just partially. + beforePrototypeCreation(beanName); + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + instance = resolveBeforeInstantiation(beanName, mbd); + if (instance == null) { + BeanWrapper bw = createBeanInstance(beanName, mbd, null); + instance = bw.getWrappedInstance(); + } + } + catch (UnsatisfiedDependencyException ex) { + // Don't swallow, probably misconfiguration... + throw ex; + } + catch (BeanCreationException ex) { + // Instantiation failure, maybe too early... + if (logger.isDebugEnabled()) { + logger.debug("Bean creation exception on non-singleton FactoryBean type check: " + ex); + } + onSuppressedException(ex); + return null; + } + finally { + // Finished partial creation of this bean. + afterPrototypeCreation(beanName); + } + + return getFactoryBean(beanName, instance); + } + + /** + * Apply MergedBeanDefinitionPostProcessors to the specified bean definition, + * invoking their {@code postProcessMergedBeanDefinition} methods. + * @param mbd the merged bean definition for the bean + * @param beanType the actual type of the managed bean instance + * @param beanName the name of the bean + * @see MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition + */ + protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) { + for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { + processor.postProcessMergedBeanDefinition(mbd, beanType, beanName); + } + } + + /** + * Apply before-instantiation post-processors, resolving whether there is a + * before-instantiation shortcut for the specified bean. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return the shortcut-determined bean instance, or {@code null} if none + */ + @Nullable + protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { + Object bean = null; + if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { + // Make sure bean class is actually resolved at this point. + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + Class targetType = determineTargetType(beanName, mbd); + if (targetType != null) { + bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); + if (bean != null) { + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + } + } + } + mbd.beforeInstantiationResolved = (bean != null); + } + return bean; + } + + /** + * Apply InstantiationAwareBeanPostProcessors to the specified bean definition + * (by class and name), invoking their {@code postProcessBeforeInstantiation} methods. + *

Any returned object will be used as the bean instead of actually instantiating + * the target bean. A {@code null} return value from the post-processor will + * result in the target bean being instantiated. + * @param beanClass the class of the bean to be instantiated + * @param beanName the name of the bean + * @return the bean object to use instead of a default instance of the target bean, or {@code null} + * @see InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation + */ + @Nullable + protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { + for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { + Object result = bp.postProcessBeforeInstantiation(beanClass, beanName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Create a new instance for the specified bean, using an appropriate instantiation strategy: + * factory method, constructor autowiring, or simple instantiation. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param args explicit arguments to use for constructor or factory method invocation + * @return a BeanWrapper for the new instance + * @see #obtainFromSupplier + * @see #instantiateUsingFactoryMethod + * @see #autowireConstructor + * @see #instantiateBean + */ + protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { + // Make sure bean class is actually resolved at this point. + Class beanClass = resolveBeanClass(mbd, beanName); + + if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); + } + + Supplier instanceSupplier = mbd.getInstanceSupplier(); + if (instanceSupplier != null) { + return obtainFromSupplier(instanceSupplier, beanName); + } + + if (mbd.getFactoryMethodName() != null) { + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + // Shortcut when re-creating the same bean... + boolean resolved = false; + boolean autowireNecessary = false; + if (args == null) { + synchronized (mbd.constructorArgumentLock) { + if (mbd.resolvedConstructorOrFactoryMethod != null) { + resolved = true; + autowireNecessary = mbd.constructorArgumentsResolved; + } + } + } + if (resolved) { + if (autowireNecessary) { + return autowireConstructor(beanName, mbd, null, null); + } + else { + return instantiateBean(beanName, mbd); + } + } + + // Candidate constructors for autowiring? + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // Preferred constructors for default construction? + ctors = mbd.getPreferredConstructors(); + if (ctors != null) { + return autowireConstructor(beanName, mbd, ctors, null); + } + + // No special handling: simply use no-arg constructor. + return instantiateBean(beanName, mbd); + } + + /** + * Obtain a bean instance from the given supplier. + * @param instanceSupplier the configured supplier + * @param beanName the corresponding bean name + * @return a BeanWrapper for the new instance + * @since 5.0 + * @see #getObjectForBeanInstance + */ + protected BeanWrapper obtainFromSupplier(Supplier instanceSupplier, String beanName) { + Object instance; + + String outerBean = this.currentlyCreatedBean.get(); + this.currentlyCreatedBean.set(beanName); + try { + instance = instanceSupplier.get(); + } + finally { + if (outerBean != null) { + this.currentlyCreatedBean.set(outerBean); + } + else { + this.currentlyCreatedBean.remove(); + } + } + + if (instance == null) { + instance = new NullBean(); + } + BeanWrapper bw = new BeanWrapperImpl(instance); + initBeanWrapper(bw); + return bw; + } + + /** + * Overridden in order to implicitly register the currently created bean as + * dependent on further beans getting programmatically retrieved during a + * {@link Supplier} callback. + * @since 5.0 + * @see #obtainFromSupplier + */ + @Override + protected Object getObjectForBeanInstance( + Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { + + String currentlyCreatedBean = this.currentlyCreatedBean.get(); + if (currentlyCreatedBean != null) { + registerDependentBean(beanName, currentlyCreatedBean); + } + + return super.getObjectForBeanInstance(beanInstance, name, beanName, mbd); + } + + /** + * Determine candidate constructors to use for the given bean, checking all registered + * {@link SmartInstantiationAwareBeanPostProcessor SmartInstantiationAwareBeanPostProcessors}. + * @param beanClass the raw class of the bean + * @param beanName the name of the bean + * @return the candidate constructors, or {@code null} if none specified + * @throws org.springframework.beans.BeansException in case of errors + * @see org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors + */ + @Nullable + protected Constructor[] determineConstructorsFromBeanPostProcessors(@Nullable Class beanClass, String beanName) + throws BeansException { + + if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) { + for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { + Constructor[] ctors = bp.determineCandidateConstructors(beanClass, beanName); + if (ctors != null) { + return ctors; + } + } + } + return null; + } + + /** + * Instantiate the given bean using its default constructor. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @return a BeanWrapper for the new instance + */ + protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { + try { + Object beanInstance; + if (System.getSecurityManager() != null) { + beanInstance = AccessController.doPrivileged( + (PrivilegedAction) () -> getInstantiationStrategy().instantiate(mbd, beanName, this), + getAccessControlContext()); + } + else { + beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this); + } + BeanWrapper bw = new BeanWrapperImpl(beanInstance); + initBeanWrapper(bw); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } + } + + /** + * Instantiate the bean using a named factory method. The method may be static, if the + * mbd parameter specifies a class, rather than a factoryBean, or an instance variable + * on a factory object itself configured using Dependency Injection. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or {@code null} if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + * @see #getBean(String, Object[]) + */ + protected BeanWrapper instantiateUsingFactoryMethod( + String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) { + + return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs); + } + + /** + * "autowire constructor" (with constructor arguments by type) behavior. + * Also applied if explicit constructor argument values are specified, + * matching all remaining arguments with beans from the bean factory. + *

This corresponds to constructor injection: In this mode, a Spring + * bean factory is able to host components that expect constructor-based + * dependency resolution. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param ctors the chosen candidate constructors + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or {@code null} if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + */ + protected BeanWrapper autowireConstructor( + String beanName, RootBeanDefinition mbd, @Nullable Constructor[] ctors, @Nullable Object[] explicitArgs) { + + return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); + } + + /** + * Populate the bean instance in the given BeanWrapper with the property values + * from the bean definition. + * @param beanName the name of the bean + * @param mbd the bean definition for the bean + * @param bw the BeanWrapper with bean instance + */ + @SuppressWarnings("deprecation") // for postProcessPropertyValues + protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { + if (bw == null) { + if (mbd.hasPropertyValues()) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); + } + else { + // Skip property population phase for null instance. + return; + } + } + + // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the + // state of the bean before properties are set. This can be used, for example, + // to support styles of field injection. + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { + if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { + return; + } + } + } + + PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); + + int resolvedAutowireMode = mbd.getResolvedAutowireMode(); + if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { + MutablePropertyValues newPvs = new MutablePropertyValues(pvs); + // Add property values based on autowire by name if applicable. + if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { + autowireByName(beanName, mbd, bw, newPvs); + } + // Add property values based on autowire by type if applicable. + if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { + autowireByType(beanName, mbd, bw, newPvs); + } + pvs = newPvs; + } + + boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); + boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); + + PropertyDescriptor[] filteredPds = null; + if (hasInstAwareBpps) { + if (pvs == null) { + pvs = mbd.getPropertyValues(); + } + for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { + PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + if (filteredPds == null) { + filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); + } + pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + return; + } + } + pvs = pvsToUse; + } + } + if (needsDepCheck) { + if (filteredPds == null) { + filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); + } + checkDependencies(beanName, mbd, filteredPds, pvs); + } + + if (pvs != null) { + applyPropertyValues(beanName, mbd, bw, pvs); + } + } + + /** + * Fill in any missing property values with references to + * other beans in this factory if autowire is set to "byName". + * @param beanName the name of the bean we're wiring up. + * Useful for debugging messages; not used functionally. + * @param mbd bean definition to update through autowiring + * @param bw the BeanWrapper from which we can obtain information about the bean + * @param pvs the PropertyValues to register wired objects with + */ + protected void autowireByName( + String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { + + String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); + for (String propertyName : propertyNames) { + if (containsBean(propertyName)) { + Object bean = getBean(propertyName); + pvs.add(propertyName, bean); + registerDependentBean(propertyName, beanName); + if (logger.isTraceEnabled()) { + logger.trace("Added autowiring by name from bean name '" + beanName + + "' via property '" + propertyName + "' to bean named '" + propertyName + "'"); + } + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName + + "' by name: no matching bean found"); + } + } + } + } + + /** + * Abstract method defining "autowire by type" (bean properties by type) behavior. + *

This is like PicoContainer default, in which there must be exactly one bean + * of the property type in the bean factory. This makes bean factories simple to + * configure for small namespaces, but doesn't work as well as standard Spring + * behavior for bigger applications. + * @param beanName the name of the bean to autowire by type + * @param mbd the merged bean definition to update through autowiring + * @param bw the BeanWrapper from which we can obtain information about the bean + * @param pvs the PropertyValues to register wired objects with + */ + protected void autowireByType( + String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { + + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + + Set autowiredBeanNames = new LinkedHashSet<>(4); + String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); + for (String propertyName : propertyNames) { + try { + PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); + // Don't try autowiring by type for type Object: never makes sense, + // even if it technically is a unsatisfied, non-simple property. + if (Object.class != pd.getPropertyType()) { + MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); + // Do not allow eager init for type matching in case of a prioritized post-processor. + boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered); + DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); + Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); + if (autowiredArgument != null) { + pvs.add(propertyName, autowiredArgument); + } + for (String autowiredBeanName : autowiredBeanNames) { + registerDependentBean(autowiredBeanName, beanName); + if (logger.isTraceEnabled()) { + logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" + + propertyName + "' to bean named '" + autowiredBeanName + "'"); + } + } + autowiredBeanNames.clear(); + } + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex); + } + } + } + + + /** + * Return an array of non-simple bean properties that are unsatisfied. + * These are probably unsatisfied references to other beans in the + * factory. Does not include simple properties like primitives or Strings. + * @param mbd the merged bean definition the bean was created with + * @param bw the BeanWrapper the bean was created with + * @return an array of bean property names + * @see org.springframework.beans.BeanUtils#isSimpleProperty + */ + protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) { + Set result = new TreeSet<>(); + PropertyValues pvs = mbd.getPropertyValues(); + PropertyDescriptor[] pds = bw.getPropertyDescriptors(); + for (PropertyDescriptor pd : pds) { + if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) && + !BeanUtils.isSimpleProperty(pd.getPropertyType())) { + result.add(pd.getName()); + } + } + return StringUtils.toStringArray(result); + } + + /** + * Extract a filtered set of PropertyDescriptors from the given BeanWrapper, + * excluding ignored dependency types or properties defined on ignored dependency interfaces. + * @param bw the BeanWrapper the bean was created with + * @param cache whether to cache filtered PropertyDescriptors for the given bean Class + * @return the filtered PropertyDescriptors + * @see #isExcludedFromDependencyCheck + * @see #filterPropertyDescriptorsForDependencyCheck(org.springframework.beans.BeanWrapper) + */ + protected PropertyDescriptor[] filterPropertyDescriptorsForDependencyCheck(BeanWrapper bw, boolean cache) { + PropertyDescriptor[] filtered = this.filteredPropertyDescriptorsCache.get(bw.getWrappedClass()); + if (filtered == null) { + filtered = filterPropertyDescriptorsForDependencyCheck(bw); + if (cache) { + PropertyDescriptor[] existing = + this.filteredPropertyDescriptorsCache.putIfAbsent(bw.getWrappedClass(), filtered); + if (existing != null) { + filtered = existing; + } + } + } + return filtered; + } + + /** + * Extract a filtered set of PropertyDescriptors from the given BeanWrapper, + * excluding ignored dependency types or properties defined on ignored dependency interfaces. + * @param bw the BeanWrapper the bean was created with + * @return the filtered PropertyDescriptors + * @see #isExcludedFromDependencyCheck + */ + protected PropertyDescriptor[] filterPropertyDescriptorsForDependencyCheck(BeanWrapper bw) { + List pds = new ArrayList<>(Arrays.asList(bw.getPropertyDescriptors())); + pds.removeIf(this::isExcludedFromDependencyCheck); + return pds.toArray(new PropertyDescriptor[0]); + } + + /** + * Determine whether the given bean property is excluded from dependency checks. + *

This implementation excludes properties defined by CGLIB and + * properties whose type matches an ignored dependency type or which + * are defined by an ignored dependency interface. + * @param pd the PropertyDescriptor of the bean property + * @return whether the bean property is excluded + * @see #ignoreDependencyType(Class) + * @see #ignoreDependencyInterface(Class) + */ + protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + return (AutowireUtils.isExcludedFromDependencyCheck(pd) || + this.ignoredDependencyTypes.contains(pd.getPropertyType()) || + AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces)); + } + + /** + * Perform a dependency check that all properties exposed have been set, + * if desired. Dependency checks can be objects (collaborating beans), + * simple (primitives and String), or all (both). + * @param beanName the name of the bean + * @param mbd the merged bean definition the bean was created with + * @param pds the relevant property descriptors for the target bean + * @param pvs the property values to be applied to the bean + * @see #isExcludedFromDependencyCheck(java.beans.PropertyDescriptor) + */ + protected void checkDependencies( + String beanName, AbstractBeanDefinition mbd, PropertyDescriptor[] pds, @Nullable PropertyValues pvs) + throws UnsatisfiedDependencyException { + + int dependencyCheck = mbd.getDependencyCheck(); + for (PropertyDescriptor pd : pds) { + if (pd.getWriteMethod() != null && (pvs == null || !pvs.contains(pd.getName()))) { + boolean isSimple = BeanUtils.isSimpleProperty(pd.getPropertyType()); + boolean unsatisfied = (dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_ALL) || + (isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_SIMPLE) || + (!isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS); + if (unsatisfied) { + throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, pd.getName(), + "Set this property value or disable dependency checking for this bean."); + } + } + } + } + + /** + * Apply the given property values, resolving any runtime references + * to other beans in this bean factory. Must use deep copy, so we + * don't permanently modify this property. + * @param beanName the bean name passed for better exception information + * @param mbd the merged bean definition + * @param bw the BeanWrapper wrapping the target object + * @param pvs the new property values + */ + protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { + if (pvs.isEmpty()) { + return; + } + + if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) { + ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); + } + + MutablePropertyValues mpvs = null; + List original; + + if (pvs instanceof MutablePropertyValues) { + mpvs = (MutablePropertyValues) pvs; + if (mpvs.isConverted()) { + // Shortcut: use the pre-converted values as-is. + try { + bw.setPropertyValues(mpvs); + return; + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + original = mpvs.getPropertyValueList(); + } + else { + original = Arrays.asList(pvs.getPropertyValues()); + } + + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); + + // Create a deep copy, resolving any references for values. + List deepCopy = new ArrayList<>(original.size()); + boolean resolveNecessary = false; + for (PropertyValue pv : original) { + if (pv.isConverted()) { + deepCopy.add(pv); + } + else { + String propertyName = pv.getName(); + Object originalValue = pv.getValue(); + if (originalValue == AutowiredPropertyMarker.INSTANCE) { + Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod(); + if (writeMethod == null) { + throw new IllegalArgumentException("Autowire marker for property without write method: " + pv); + } + originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true); + } + Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); + Object convertedValue = resolvedValue; + boolean convertible = bw.isWritableProperty(propertyName) && + !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); + if (convertible) { + convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); + } + // Possibly store converted value in merged bean definition, + // in order to avoid re-conversion for every created bean instance. + if (resolvedValue == originalValue) { + if (convertible) { + pv.setConvertedValue(convertedValue); + } + deepCopy.add(pv); + } + else if (convertible && originalValue instanceof TypedStringValue && + !((TypedStringValue) originalValue).isDynamic() && + !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { + pv.setConvertedValue(convertedValue); + deepCopy.add(pv); + } + else { + resolveNecessary = true; + deepCopy.add(new PropertyValue(pv, convertedValue)); + } + } + } + if (mpvs != null && !resolveNecessary) { + mpvs.setConverted(); + } + + // Set our (possibly massaged) deep copy. + try { + bw.setPropertyValues(new MutablePropertyValues(deepCopy)); + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + + /** + * Convert the given value for the specified target property. + */ + @Nullable + private Object convertForProperty( + @Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { + + if (converter instanceof BeanWrapperImpl) { + return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); + } + else { + PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); + MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); + return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam); + } + } + + + /** + * Initialize the given bean instance, applying factory callbacks + * as well as init methods and bean post processors. + *

Called from {@link #createBean} for traditionally defined beans, + * and from {@link #initializeBean} for existing bean instances. + * @param beanName the bean name in the factory (for debugging purposes) + * @param bean the new bean instance we may need to initialize + * @param mbd the bean definition that the bean was created with + * (can also be {@code null}, if given an existing bean instance) + * @return the initialized bean instance (potentially wrapped) + * @see BeanNameAware + * @see BeanClassLoaderAware + * @see BeanFactoryAware + * @see #applyBeanPostProcessorsBeforeInitialization + * @see #invokeInitMethods + * @see #applyBeanPostProcessorsAfterInitialization + */ + protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; + } + + private void invokeAwareMethods(String beanName, Object bean) { + if (bean instanceof Aware) { + if (bean instanceof BeanNameAware) { + ((BeanNameAware) bean).setBeanName(beanName); + } + if (bean instanceof BeanClassLoaderAware) { + ClassLoader bcl = getBeanClassLoader(); + if (bcl != null) { + ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl); + } + } + if (bean instanceof BeanFactoryAware) { + ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this); + } + } + } + + /** + * Give a bean a chance to react now all its properties are set, + * and a chance to know about its owning bean factory (this object). + * This means checking whether the bean implements InitializingBean or defines + * a custom init method, and invoking the necessary callback(s) if it does. + * @param beanName the bean name in the factory (for debugging purposes) + * @param bean the new bean instance we may need to initialize + * @param mbd the merged bean definition that the bean was created with + * (can also be {@code null}, if given an existing bean instance) + * @throws Throwable if thrown by init methods or by the invocation process + * @see #invokeCustomInitMethod + */ + protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) + throws Throwable { + + boolean isInitializingBean = (bean instanceof InitializingBean); + if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { + if (logger.isTraceEnabled()) { + logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); + } + if (System.getSecurityManager() != null) { + try { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + ((InitializingBean) bean).afterPropertiesSet(); + return null; + }, getAccessControlContext()); + } + catch (PrivilegedActionException pae) { + throw pae.getException(); + } + } + else { + ((InitializingBean) bean).afterPropertiesSet(); + } + } + + if (mbd != null && bean.getClass() != NullBean.class) { + String initMethodName = mbd.getInitMethodName(); + if (StringUtils.hasLength(initMethodName) && + !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && + !mbd.isExternallyManagedInitMethod(initMethodName)) { + invokeCustomInitMethod(beanName, bean, mbd); + } + } + } + + /** + * Invoke the specified custom init method on the given bean. + * Called by invokeInitMethods. + *

Can be overridden in subclasses for custom resolution of init + * methods with arguments. + * @see #invokeInitMethods + */ + protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd) + throws Throwable { + + String initMethodName = mbd.getInitMethodName(); + Assert.state(initMethodName != null, "No init method set"); + Method initMethod = (mbd.isNonPublicAccessAllowed() ? + BeanUtils.findMethod(bean.getClass(), initMethodName) : + ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName)); + + if (initMethod == null) { + if (mbd.isEnforceInitMethod()) { + throw new BeanDefinitionValidationException("Could not find an init method named '" + + initMethodName + "' on bean with name '" + beanName + "'"); + } + else { + if (logger.isTraceEnabled()) { + logger.trace("No default init method named '" + initMethodName + + "' found on bean with name '" + beanName + "'"); + } + // Ignore non-existent default lifecycle methods. + return; + } + } + + if (logger.isTraceEnabled()) { + logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); + } + Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); + + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + ReflectionUtils.makeAccessible(methodToInvoke); + return null; + }); + try { + AccessController.doPrivileged((PrivilegedExceptionAction) + () -> methodToInvoke.invoke(bean), getAccessControlContext()); + } + catch (PrivilegedActionException pae) { + InvocationTargetException ex = (InvocationTargetException) pae.getException(); + throw ex.getTargetException(); + } + } + else { + try { + ReflectionUtils.makeAccessible(methodToInvoke); + methodToInvoke.invoke(bean); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + + + /** + * Applies the {@code postProcessAfterInitialization} callback of all + * registered BeanPostProcessors, giving them a chance to post-process the + * object obtained from FactoryBeans (for example, to auto-proxy them). + * @see #applyBeanPostProcessorsAfterInitialization + */ + @Override + protected Object postProcessObjectFromFactoryBean(Object object, String beanName) { + return applyBeanPostProcessorsAfterInitialization(object, beanName); + } + + /** + * Overridden to clear FactoryBean instance cache as well. + */ + @Override + protected void removeSingleton(String beanName) { + synchronized (getSingletonMutex()) { + super.removeSingleton(beanName); + this.factoryBeanInstanceCache.remove(beanName); + } + } + + /** + * Overridden to clear FactoryBean instance cache as well. + */ + @Override + protected void clearSingletonCache() { + synchronized (getSingletonMutex()) { + super.clearSingletonCache(); + this.factoryBeanInstanceCache.clear(); + } + } + + /** + * Expose the logger to collaborating delegates. + * @since 5.0.7 + */ + Log getLogger() { + return logger; + } + + + /** + * Special DependencyDescriptor variant for Spring's good old autowire="byType" mode. + * Always optional; never considering the parameter name for choosing a primary candidate. + */ + @SuppressWarnings("serial") + private static class AutowireByTypeDependencyDescriptor extends DependencyDescriptor { + + public AutowireByTypeDependencyDescriptor(MethodParameter methodParameter, boolean eager) { + super(methodParameter, false, eager); + } + + @Override + public String getDependencyName() { + return null; + } + } + + + /** + * {@link MethodCallback} used to find {@link FactoryBean} type information. + */ + private static class FactoryBeanMethodTypeFinder implements MethodCallback { + + private final String factoryMethodName; + + private ResolvableType result = ResolvableType.NONE; + + FactoryBeanMethodTypeFinder(String factoryMethodName) { + this.factoryMethodName = factoryMethodName; + } + + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + if (isFactoryBeanMethod(method)) { + ResolvableType returnType = ResolvableType.forMethodReturnType(method); + ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric(); + if (this.result == ResolvableType.NONE) { + this.result = candidate; + } + else { + Class resolvedResult = this.result.resolve(); + Class commonAncestor = ClassUtils.determineCommonAncestor(candidate.resolve(), resolvedResult); + if (!ObjectUtils.nullSafeEquals(resolvedResult, commonAncestor)) { + this.result = ResolvableType.forClass(commonAncestor); + } + } + } + } + + private boolean isFactoryBeanMethod(Method method) { + return (method.getName().equals(this.factoryMethodName) && + FactoryBean.class.isAssignableFrom(method.getReturnType())); + } + + ResolvableType getResult() { + Class resolved = this.result.resolve(); + boolean foundResult = resolved != null && resolved != Object.class; + return (foundResult ? this.result : ResolvableType.NONE); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java new file mode 100644 index 0000000..a9a556c --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2019 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.beans.factory.support; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.env.Environment; +import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Abstract base class for bean definition readers which implement + * the {@link BeanDefinitionReader} interface. + * + *

Provides common properties like the bean factory to work on + * and the class loader to use for loading bean classes. + * + * @author Juergen Hoeller + * @author Chris Beams + * @since 11.12.2003 + * @see BeanDefinitionReaderUtils + */ +public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + private final BeanDefinitionRegistry registry; + + @Nullable + private ResourceLoader resourceLoader; + + @Nullable + private ClassLoader beanClassLoader; + + private Environment environment; + + private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE; + + + /** + * Create a new AbstractBeanDefinitionReader for the given bean factory. + *

If the passed-in bean factory does not only implement the BeanDefinitionRegistry + * interface but also the ResourceLoader interface, it will be used as default + * ResourceLoader as well. This will usually be the case for + * {@link org.springframework.context.ApplicationContext} implementations. + *

If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a + * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}. + *

If the passed-in bean factory also implements {@link EnvironmentCapable} its + * environment will be used by this reader. Otherwise, the reader will initialize and + * use a {@link StandardEnvironment}. All ApplicationContext implementations are + * EnvironmentCapable, while normal BeanFactory implementations are not. + * @param registry the BeanFactory to load bean definitions into, + * in the form of a BeanDefinitionRegistry + * @see #setResourceLoader + * @see #setEnvironment + */ + protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + this.registry = registry; + + // Determine ResourceLoader to use. + if (this.registry instanceof ResourceLoader) { + this.resourceLoader = (ResourceLoader) this.registry; + } + else { + this.resourceLoader = new PathMatchingResourcePatternResolver(); + } + + // Inherit Environment if possible + if (this.registry instanceof EnvironmentCapable) { + this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); + } + else { + this.environment = new StandardEnvironment(); + } + } + + + public final BeanDefinitionRegistry getBeanFactory() { + return this.registry; + } + + @Override + public final BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + /** + * Set the ResourceLoader to use for resource locations. + * If specifying a ResourcePatternResolver, the bean definition reader + * will be capable of resolving resource patterns to Resource arrays. + *

Default is PathMatchingResourcePatternResolver, also capable of + * resource pattern resolving through the ResourcePatternResolver interface. + *

Setting this to {@code null} suggests that absolute resource loading + * is not available for this bean definition reader. + * @see org.springframework.core.io.support.ResourcePatternResolver + * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver + */ + public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Override + @Nullable + public ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + + /** + * Set the ClassLoader to use for bean classes. + *

Default is {@code null}, which suggests to not load bean classes + * eagerly but rather to just register bean definitions with class names, + * with the corresponding Classes to be resolved later (or never). + * @see Thread#getContextClassLoader() + */ + public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + @Override + @Nullable + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + /** + * Set the Environment to use when reading bean definitions. Most often used + * for evaluating profile information to determine which bean definitions + * should be read and which should be omitted. + */ + public void setEnvironment(Environment environment) { + Assert.notNull(environment, "Environment must not be null"); + this.environment = environment; + } + + @Override + public Environment getEnvironment() { + return this.environment; + } + + /** + * Set the BeanNameGenerator to use for anonymous beans + * (without explicit bean name specified). + *

Default is a {@link DefaultBeanNameGenerator}. + */ + public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) { + this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : DefaultBeanNameGenerator.INSTANCE); + } + + @Override + public BeanNameGenerator getBeanNameGenerator() { + return this.beanNameGenerator; + } + + + @Override + public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { + Assert.notNull(resources, "Resource array must not be null"); + int count = 0; + for (Resource resource : resources) { + count += loadBeanDefinitions(resource); + } + return count; + } + + @Override + public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { + return loadBeanDefinitions(location, null); + } + + /** + * Load bean definitions from the specified resource location. + *

The location can also be a location pattern, provided that the + * ResourceLoader of this bean definition reader is a ResourcePatternResolver. + * @param location the resource location, to be loaded with the ResourceLoader + * (or ResourcePatternResolver) of this bean definition reader + * @param actualResources a Set to be filled with the actual Resource objects + * that have been resolved during the loading process. May be {@code null} + * to indicate that the caller is not interested in those Resource objects. + * @return the number of bean definitions found + * @throws BeanDefinitionStoreException in case of loading or parsing errors + * @see #getResourceLoader() + * @see #loadBeanDefinitions(org.springframework.core.io.Resource) + * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) + */ + public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException { + ResourceLoader resourceLoader = getResourceLoader(); + if (resourceLoader == null) { + throw new BeanDefinitionStoreException( + "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); + } + + if (resourceLoader instanceof ResourcePatternResolver) { + // Resource pattern matching available. + try { + Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); + int count = loadBeanDefinitions(resources); + if (actualResources != null) { + Collections.addAll(actualResources, resources); + } + if (logger.isTraceEnabled()) { + logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); + } + return count; + } + catch (IOException ex) { + throw new BeanDefinitionStoreException( + "Could not resolve bean definition resource pattern [" + location + "]", ex); + } + } + else { + // Can only load single resources by absolute URL. + Resource resource = resourceLoader.getResource(location); + int count = loadBeanDefinitions(resource); + if (actualResources != null) { + actualResources.add(resource); + } + if (logger.isTraceEnabled()) { + logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); + } + return count; + } + } + + @Override + public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { + Assert.notNull(locations, "Location array must not be null"); + int count = 0; + for (String location : locations) { + count += loadBeanDefinitions(location); + } + return count; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java new file mode 100644 index 0000000..443b7ee --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -0,0 +1,2114 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.beans.PropertyEditor; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.beans.PropertyEditorRegistrySupport; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanIsAbstractException; +import org.springframework.beans.factory.BeanIsNotAFactoryException; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.config.Scope; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +import org.springframework.core.AttributeAccessor; +import org.springframework.core.DecoratingClassLoader; +import org.springframework.core.NamedThreadLocal; +import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.log.LogMessage; +import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.core.metrics.StartupStep; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +/** + * Abstract base class for {@link org.springframework.beans.factory.BeanFactory} + * implementations, providing the full capabilities of the + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory} SPI. + * Does not assume a listable bean factory: can therefore also be used + * as base class for bean factory implementations which obtain bean definitions + * from some backend resource (where bean definition access is an expensive operation). + * + *

This class provides a singleton cache (through its base class + * {@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry}, + * singleton/prototype determination, {@link org.springframework.beans.factory.FactoryBean} + * handling, aliases, bean definition merging for child bean definitions, + * and bean destruction ({@link org.springframework.beans.factory.DisposableBean} + * interface, custom destroy methods). Furthermore, it can manage a bean factory + * hierarchy (delegating to the parent in case of an unknown bean), through implementing + * the {@link org.springframework.beans.factory.HierarchicalBeanFactory} interface. + * + *

The main template methods to be implemented by subclasses are + * {@link #getBeanDefinition} and {@link #createBean}, retrieving a bean definition + * for a given bean name and creating a bean instance for a given bean definition, + * respectively. Default implementations of those operations can be found in + * {@link DefaultListableBeanFactory} and {@link AbstractAutowireCapableBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Costin Leau + * @author Chris Beams + * @author Phillip Webb + * @since 15 April 2001 + * @see #getBeanDefinition + * @see #createBean + * @see AbstractAutowireCapableBeanFactory#createBean + * @see DefaultListableBeanFactory#getBeanDefinition + */ +public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { + + /** Parent bean factory, for bean inheritance support. */ + @Nullable + private BeanFactory parentBeanFactory; + + /** ClassLoader to resolve bean class names with, if necessary. */ + @Nullable + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + /** ClassLoader to temporarily resolve bean class names with, if necessary. */ + @Nullable + private ClassLoader tempClassLoader; + + /** Whether to cache bean metadata or rather reobtain it for every access. */ + private boolean cacheBeanMetadata = true; + + /** Resolution strategy for expressions in bean definition values. */ + @Nullable + private BeanExpressionResolver beanExpressionResolver; + + /** Spring ConversionService to use instead of PropertyEditors. */ + @Nullable + private ConversionService conversionService; + + /** Custom PropertyEditorRegistrars to apply to the beans of this factory. */ + private final Set propertyEditorRegistrars = new LinkedHashSet<>(4); + + /** Custom PropertyEditors to apply to the beans of this factory. */ + private final Map, Class> customEditors = new HashMap<>(4); + + /** A custom TypeConverter to use, overriding the default PropertyEditor mechanism. */ + @Nullable + private TypeConverter typeConverter; + + /** String resolvers to apply e.g. to annotation attribute values. */ + private final List embeddedValueResolvers = new CopyOnWriteArrayList<>(); + + /** BeanPostProcessors to apply. */ + private final List beanPostProcessors = new BeanPostProcessorCacheAwareList(); + + /** Cache of pre-filtered post-processors. */ + @Nullable + private volatile BeanPostProcessorCache beanPostProcessorCache; + + /** Map from scope identifier String to corresponding Scope. */ + private final Map scopes = new LinkedHashMap<>(8); + + /** Security context used when running with a SecurityManager. */ + @Nullable + private SecurityContextProvider securityContextProvider; + + /** Map from bean name to merged RootBeanDefinition. */ + private final Map mergedBeanDefinitions = new ConcurrentHashMap<>(256); + + /** Names of beans that have already been created at least once. */ + private final Set alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); + + /** Names of beans that are currently in creation. */ + private final ThreadLocal prototypesCurrentlyInCreation = + new NamedThreadLocal<>("Prototype beans currently in creation"); + + /** Application startup metrics. **/ + private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT; + + /** + * Create a new AbstractBeanFactory. + */ + public AbstractBeanFactory() { + } + + /** + * Create a new AbstractBeanFactory with the given parent. + * @param parentBeanFactory parent bean factory, or {@code null} if none + * @see #getBean + */ + public AbstractBeanFactory(@Nullable BeanFactory parentBeanFactory) { + this.parentBeanFactory = parentBeanFactory; + } + + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + @Override + public Object getBean(String name) throws BeansException { + return doGetBean(name, null, null, false); + } + + @Override + public T getBean(String name, Class requiredType) throws BeansException { + return doGetBean(name, requiredType, null, false); + } + + @Override + public Object getBean(String name, Object... args) throws BeansException { + return doGetBean(name, null, args, false); + } + + /** + * Return an instance, which may be shared or independent, of the specified bean. + * @param name the name of the bean to retrieve + * @param requiredType the required type of the bean to retrieve + * @param args arguments to use when creating a bean instance using explicit arguments + * (only applied when creating a new instance as opposed to retrieving an existing one) + * @return an instance of the bean + * @throws BeansException if the bean could not be created + */ + public T getBean(String name, @Nullable Class requiredType, @Nullable Object... args) + throws BeansException { + + return doGetBean(name, requiredType, args, false); + } + + /** + * Return an instance, which may be shared or independent, of the specified bean. + * @param name the name of the bean to retrieve + * @param requiredType the required type of the bean to retrieve + * @param args arguments to use when creating a bean instance using explicit arguments + * (only applied when creating a new instance as opposed to retrieving an existing one) + * @param typeCheckOnly whether the instance is obtained for a type check, + * not for actual use + * @return an instance of the bean + * @throws BeansException if the bean could not be created + */ + @SuppressWarnings("unchecked") + protected T doGetBean( + String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) + throws BeansException { + + String beanName = transformedBeanName(name); + Object bean; + + // Eagerly check singleton cache for manually registered singletons. + Object sharedInstance = getSingleton(beanName); + if (sharedInstance != null && args == null) { + if (logger.isTraceEnabled()) { + if (isSingletonCurrentlyInCreation(beanName)) { + logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + + "' that is not fully initialized yet - a consequence of a circular reference"); + } + else { + logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); + } + } + bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + } + + else { + // Fail if we're already creating this bean instance: + // We're assumably within a circular reference. + if (isPrototypeCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + + // Check if bean definition exists in this factory. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // Not found -> check parent. + String nameToLookup = originalBeanName(name); + if (parentBeanFactory instanceof AbstractBeanFactory) { + return ((AbstractBeanFactory) parentBeanFactory).doGetBean( + nameToLookup, requiredType, args, typeCheckOnly); + } + else if (args != null) { + // Delegation to parent with explicit args. + return (T) parentBeanFactory.getBean(nameToLookup, args); + } + else if (requiredType != null) { + // No args -> delegate to standard getBean method. + return parentBeanFactory.getBean(nameToLookup, requiredType); + } + else { + return (T) parentBeanFactory.getBean(nameToLookup); + } + } + + if (!typeCheckOnly) { + markBeanAsCreated(beanName); + } + + StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate") + .tag("beanName", name); + try { + if (requiredType != null) { + beanCreation.tag("beanType", requiredType::toString); + } + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + // Guarantee initialization of beans that the current bean depends on. + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + for (String dep : dependsOn) { + if (isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + registerDependentBean(dep, beanName); + try { + getBean(dep); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "'" + beanName + "' depends on missing bean '" + dep + "'", ex); + } + } + } + + // Create bean instance. + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, () -> { + try { + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + + else if (mbd.isPrototype()) { + // It's a prototype -> create a new instance. + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); + } + + else { + String scopeName = mbd.getScope(); + if (!StringUtils.hasLength(scopeName)) { + throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); + } + Scope scope = this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); + } + try { + Object scopedInstance = scope.get(beanName, () -> { + beforePrototypeCreation(beanName); + try { + return createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + }); + bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + } + catch (IllegalStateException ex) { + throw new ScopeNotActiveException(beanName, scopeName, ex); + } + } + } + catch (BeansException ex) { + beanCreation.tag("exception", ex.getClass().toString()); + beanCreation.tag("message", String.valueOf(ex.getMessage())); + cleanupAfterBeanCreationFailure(beanName); + throw ex; + } + finally { + beanCreation.end(); + } + } + + // Check if required type matches the type of the actual bean instance. + if (requiredType != null && !requiredType.isInstance(bean)) { + try { + T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); + if (convertedBean == null) { + throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); + } + return convertedBean; + } + catch (TypeMismatchException ex) { + if (logger.isTraceEnabled()) { + logger.trace("Failed to convert bean '" + name + "' to required type '" + + ClassUtils.getQualifiedName(requiredType) + "'", ex); + } + throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); + } + } + return (T) bean; + } + + @Override + public boolean containsBean(String name) { + String beanName = transformedBeanName(name); + if (containsSingleton(beanName) || containsBeanDefinition(beanName)) { + return (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(name)); + } + // Not found -> check parent. + BeanFactory parentBeanFactory = getParentBeanFactory(); + return (parentBeanFactory != null && parentBeanFactory.containsBean(originalBeanName(name))); + } + + @Override + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + if (beanInstance instanceof FactoryBean) { + return (BeanFactoryUtils.isFactoryDereference(name) || ((FactoryBean) beanInstance).isSingleton()); + } + else { + return !BeanFactoryUtils.isFactoryDereference(name); + } + } + + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isSingleton(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + + // In case of FactoryBean, return singleton status of created object if not a dereference. + if (mbd.isSingleton()) { + if (isFactoryBean(beanName, mbd)) { + if (BeanFactoryUtils.isFactoryDereference(name)) { + return true; + } + FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + return factoryBean.isSingleton(); + } + else { + return !BeanFactoryUtils.isFactoryDereference(name); + } + } + else { + return false; + } + } + + @Override + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isPrototype(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + if (mbd.isPrototype()) { + // In case of FactoryBean, return singleton status of created object if not a dereference. + return (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(beanName, mbd)); + } + + // Singleton or scoped - not a prototype. + // However, FactoryBean may still produce a prototype object... + if (BeanFactoryUtils.isFactoryDereference(name)) { + return false; + } + if (isFactoryBean(beanName, mbd)) { + FactoryBean fb = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged( + (PrivilegedAction) () -> + ((fb instanceof SmartFactoryBean && ((SmartFactoryBean) fb).isPrototype()) || + !fb.isSingleton()), + getAccessControlContext()); + } + else { + return ((fb instanceof SmartFactoryBean && ((SmartFactoryBean) fb).isPrototype()) || + !fb.isSingleton()); + } + } + else { + return false; + } + } + + @Override + public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { + return isTypeMatch(name, typeToMatch, true); + } + + /** + * Internal extended variant of {@link #isTypeMatch(String, ResolvableType)} + * to check whether the bean with the given name matches the specified type. Allow + * additional constraints to be applied to ensure that beans are not created early. + * @param name the name of the bean to query + * @param typeToMatch the type to match against (as a + * {@code ResolvableType}) + * @return {@code true} if the bean type matches, {@code false} if it + * doesn't match or cannot be determined yet + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 5.2 + * @see #getBean + * @see #getType + */ + protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + String beanName = transformedBeanName(name); + boolean isFactoryDereference = BeanFactoryUtils.isFactoryDereference(name); + + // Check manually registered singletons. + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null && beanInstance.getClass() != NullBean.class) { + if (beanInstance instanceof FactoryBean) { + if (!isFactoryDereference) { + Class type = getTypeForFactoryBean((FactoryBean) beanInstance); + return (type != null && typeToMatch.isAssignableFrom(type)); + } + else { + return typeToMatch.isInstance(beanInstance); + } + } + else if (!isFactoryDereference) { + if (typeToMatch.isInstance(beanInstance)) { + // Direct match for exposed instance? + return true; + } + else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { + // Generics potentially only match on the target class, not on the proxy... + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class targetType = mbd.getTargetType(); + if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { + // Check raw class match as well, making sure it's exposed on the proxy. + Class classToMatch = typeToMatch.resolve(); + if (classToMatch != null && !classToMatch.isInstance(beanInstance)) { + return false; + } + if (typeToMatch.isAssignableFrom(targetType)) { + return true; + } + } + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; + } + return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType)); + } + } + return false; + } + else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { + // null instance registered + return false; + } + + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch); + } + + // Retrieve corresponding bean definition. + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + + // Setup the types that we want to match against + Class classToMatch = typeToMatch.resolve(); + if (classToMatch == null) { + classToMatch = FactoryBean.class; + } + Class[] typesToMatch = (FactoryBean.class == classToMatch ? + new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch}); + + + // Attempt to predict the bean type + Class predictedType = null; + + // We're looking for a regular reference but we're a factory bean that has + // a decorated bean definition. The target bean should be the same type + // as FactoryBean would ultimately return. + if (!isFactoryDereference && dbd != null && isFactoryBean(beanName, mbd)) { + // We should only attempt if the user explicitly set lazy-init to true + // and we know the merged bean definition is for a factory bean. + if (!mbd.isLazyInit() || allowFactoryBeanInit) { + RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); + Class targetType = predictBeanType(dbd.getBeanName(), tbd, typesToMatch); + if (targetType != null && !FactoryBean.class.isAssignableFrom(targetType)) { + predictedType = targetType; + } + } + } + + // If we couldn't use the target type, try regular prediction. + if (predictedType == null) { + predictedType = predictBeanType(beanName, mbd, typesToMatch); + if (predictedType == null) { + return false; + } + } + + // Attempt to get the actual ResolvableType for the bean. + ResolvableType beanType = null; + + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + if (FactoryBean.class.isAssignableFrom(predictedType)) { + if (beanInstance == null && !isFactoryDereference) { + beanType = getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit); + predictedType = beanType.resolve(); + if (predictedType == null) { + return false; + } + } + } + else if (isFactoryDereference) { + // Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean + // type but we nevertheless are being asked to dereference a FactoryBean... + // Let's check the original bean class and proceed with it if it is a FactoryBean. + predictedType = predictBeanType(beanName, mbd, FactoryBean.class); + if (predictedType == null || !FactoryBean.class.isAssignableFrom(predictedType)) { + return false; + } + } + + // We don't have an exact type but if bean definition target type or the factory + // method return type matches the predicted type then we can use that. + if (beanType == null) { + ResolvableType definedType = mbd.targetType; + if (definedType == null) { + definedType = mbd.factoryMethodReturnType; + } + if (definedType != null && definedType.resolve() == predictedType) { + beanType = definedType; + } + } + + // If we have a bean type use it so that generics are considered + if (beanType != null) { + return typeToMatch.isAssignableFrom(beanType); + } + + // If we don't have a bean type, fallback to the predicted type + return typeToMatch.isAssignableFrom(predictedType); + } + + @Override + public boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { + return isTypeMatch(name, ResolvableType.forRawClass(typeToMatch)); + } + + @Override + @Nullable + public Class getType(String name) throws NoSuchBeanDefinitionException { + return getType(name, true); + } + + @Override + @Nullable + public Class getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + + // Check manually registered singletons. + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null && beanInstance.getClass() != NullBean.class) { + if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + return getTypeForFactoryBean((FactoryBean) beanInstance); + } + else { + return beanInstance.getClass(); + } + } + + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.getType(originalBeanName(name)); + } + + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + + // Check decorated bean definition, if any: We assume it'll be easier + // to determine the decorated bean's type than the proxy's type. + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { + RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); + Class targetClass = predictBeanType(dbd.getBeanName(), tbd); + if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { + return targetClass; + } + } + + Class beanClass = predictBeanType(beanName, mbd); + + // Check bean class whether we're dealing with a FactoryBean. + if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not at the factory class. + return getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit).resolve(); + } + else { + return beanClass; + } + } + else { + return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null); + } + } + + @Override + public String[] getAliases(String name) { + String beanName = transformedBeanName(name); + List aliases = new ArrayList<>(); + boolean factoryPrefix = name.startsWith(FACTORY_BEAN_PREFIX); + String fullBeanName = beanName; + if (factoryPrefix) { + fullBeanName = FACTORY_BEAN_PREFIX + beanName; + } + if (!fullBeanName.equals(name)) { + aliases.add(fullBeanName); + } + String[] retrievedAliases = super.getAliases(beanName); + String prefix = factoryPrefix ? FACTORY_BEAN_PREFIX : ""; + for (String retrievedAlias : retrievedAliases) { + String alias = prefix + retrievedAlias; + if (!alias.equals(name)) { + aliases.add(alias); + } + } + if (!containsSingleton(beanName) && !containsBeanDefinition(beanName)) { + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null) { + aliases.addAll(Arrays.asList(parentBeanFactory.getAliases(fullBeanName))); + } + } + return StringUtils.toStringArray(aliases); + } + + + //--------------------------------------------------------------------- + // Implementation of HierarchicalBeanFactory interface + //--------------------------------------------------------------------- + + @Override + @Nullable + public BeanFactory getParentBeanFactory() { + return this.parentBeanFactory; + } + + @Override + public boolean containsLocalBean(String name) { + String beanName = transformedBeanName(name); + return ((containsSingleton(beanName) || containsBeanDefinition(beanName)) && + (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(beanName))); + } + + + //--------------------------------------------------------------------- + // Implementation of ConfigurableBeanFactory interface + //--------------------------------------------------------------------- + + @Override + public void setParentBeanFactory(@Nullable BeanFactory parentBeanFactory) { + if (this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) { + throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory); + } + if (this == parentBeanFactory) { + throw new IllegalStateException("Cannot set parent bean factory to self"); + } + this.parentBeanFactory = parentBeanFactory; + } + + @Override + public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) { + this.beanClassLoader = (beanClassLoader != null ? beanClassLoader : ClassUtils.getDefaultClassLoader()); + } + + @Override + @Nullable + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + @Override + public void setTempClassLoader(@Nullable ClassLoader tempClassLoader) { + this.tempClassLoader = tempClassLoader; + } + + @Override + @Nullable + public ClassLoader getTempClassLoader() { + return this.tempClassLoader; + } + + @Override + public void setCacheBeanMetadata(boolean cacheBeanMetadata) { + this.cacheBeanMetadata = cacheBeanMetadata; + } + + @Override + public boolean isCacheBeanMetadata() { + return this.cacheBeanMetadata; + } + + @Override + public void setBeanExpressionResolver(@Nullable BeanExpressionResolver resolver) { + this.beanExpressionResolver = resolver; + } + + @Override + @Nullable + public BeanExpressionResolver getBeanExpressionResolver() { + return this.beanExpressionResolver; + } + + @Override + public void setConversionService(@Nullable ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + @Nullable + public ConversionService getConversionService() { + return this.conversionService; + } + + @Override + public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) { + Assert.notNull(registrar, "PropertyEditorRegistrar must not be null"); + this.propertyEditorRegistrars.add(registrar); + } + + /** + * Return the set of PropertyEditorRegistrars. + */ + public Set getPropertyEditorRegistrars() { + return this.propertyEditorRegistrars; + } + + @Override + public void registerCustomEditor(Class requiredType, Class propertyEditorClass) { + Assert.notNull(requiredType, "Required type must not be null"); + Assert.notNull(propertyEditorClass, "PropertyEditor class must not be null"); + this.customEditors.put(requiredType, propertyEditorClass); + } + + @Override + public void copyRegisteredEditorsTo(PropertyEditorRegistry registry) { + registerCustomEditors(registry); + } + + /** + * Return the map of custom editors, with Classes as keys and PropertyEditor classes as values. + */ + public Map, Class> getCustomEditors() { + return this.customEditors; + } + + @Override + public void setTypeConverter(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + /** + * Return the custom TypeConverter to use, if any. + * @return the custom TypeConverter, or {@code null} if none specified + */ + @Nullable + protected TypeConverter getCustomTypeConverter() { + return this.typeConverter; + } + + @Override + public TypeConverter getTypeConverter() { + TypeConverter customConverter = getCustomTypeConverter(); + if (customConverter != null) { + return customConverter; + } + else { + // Build default TypeConverter, registering custom editors. + SimpleTypeConverter typeConverter = new SimpleTypeConverter(); + typeConverter.setConversionService(getConversionService()); + registerCustomEditors(typeConverter); + return typeConverter; + } + } + + @Override + public void addEmbeddedValueResolver(StringValueResolver valueResolver) { + Assert.notNull(valueResolver, "StringValueResolver must not be null"); + this.embeddedValueResolvers.add(valueResolver); + } + + @Override + public boolean hasEmbeddedValueResolver() { + return !this.embeddedValueResolvers.isEmpty(); + } + + @Override + @Nullable + public String resolveEmbeddedValue(@Nullable String value) { + if (value == null) { + return null; + } + String result = value; + for (StringValueResolver resolver : this.embeddedValueResolvers) { + result = resolver.resolveStringValue(result); + if (result == null) { + return null; + } + } + return result; + } + + @Override + public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) { + Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null"); + // Remove from old position, if any + this.beanPostProcessors.remove(beanPostProcessor); + // Add to end of list + this.beanPostProcessors.add(beanPostProcessor); + } + + /** + * Add new BeanPostProcessors that will get applied to beans created + * by this factory. To be invoked during factory configuration. + * @since 5.3 + * @see #addBeanPostProcessor + */ + public void addBeanPostProcessors(Collection beanPostProcessors) { + this.beanPostProcessors.removeAll(beanPostProcessors); + this.beanPostProcessors.addAll(beanPostProcessors); + } + + @Override + public int getBeanPostProcessorCount() { + return this.beanPostProcessors.size(); + } + + /** + * Return the list of BeanPostProcessors that will get applied + * to beans created with this factory. + */ + public List getBeanPostProcessors() { + return this.beanPostProcessors; + } + + /** + * Return the internal cache of pre-filtered post-processors, + * freshly (re-)building it if necessary. + * @since 5.3 + */ + BeanPostProcessorCache getBeanPostProcessorCache() { + BeanPostProcessorCache bpCache = this.beanPostProcessorCache; + if (bpCache == null) { + bpCache = new BeanPostProcessorCache(); + for (BeanPostProcessor bp : this.beanPostProcessors) { + if (bp instanceof InstantiationAwareBeanPostProcessor) { + bpCache.instantiationAware.add((InstantiationAwareBeanPostProcessor) bp); + if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { + bpCache.smartInstantiationAware.add((SmartInstantiationAwareBeanPostProcessor) bp); + } + } + if (bp instanceof DestructionAwareBeanPostProcessor) { + bpCache.destructionAware.add((DestructionAwareBeanPostProcessor) bp); + } + if (bp instanceof MergedBeanDefinitionPostProcessor) { + bpCache.mergedDefinition.add((MergedBeanDefinitionPostProcessor) bp); + } + } + this.beanPostProcessorCache = bpCache; + } + return bpCache; + } + + /** + * Return whether this factory holds a InstantiationAwareBeanPostProcessor + * that will get applied to singleton beans on creation. + * @see #addBeanPostProcessor + * @see org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor + */ + protected boolean hasInstantiationAwareBeanPostProcessors() { + return !getBeanPostProcessorCache().instantiationAware.isEmpty(); + } + + /** + * Return whether this factory holds a DestructionAwareBeanPostProcessor + * that will get applied to singleton beans on shutdown. + * @see #addBeanPostProcessor + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + */ + protected boolean hasDestructionAwareBeanPostProcessors() { + return !getBeanPostProcessorCache().destructionAware.isEmpty(); + } + + @Override + public void registerScope(String scopeName, Scope scope) { + Assert.notNull(scopeName, "Scope identifier must not be null"); + Assert.notNull(scope, "Scope must not be null"); + if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) { + throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'"); + } + Scope previous = this.scopes.put(scopeName, scope); + if (previous != null && previous != scope) { + if (logger.isDebugEnabled()) { + logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]"); + } + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]"); + } + } + } + + @Override + public String[] getRegisteredScopeNames() { + return StringUtils.toStringArray(this.scopes.keySet()); + } + + @Override + @Nullable + public Scope getRegisteredScope(String scopeName) { + Assert.notNull(scopeName, "Scope identifier must not be null"); + return this.scopes.get(scopeName); + } + + /** + * Set the security context provider for this bean factory. If a security manager + * is set, interaction with the user code will be executed using the privileged + * of the provided security context. + */ + public void setSecurityContextProvider(SecurityContextProvider securityProvider) { + this.securityContextProvider = securityProvider; + } + + @Override + public void setApplicationStartup(ApplicationStartup applicationStartup) { + Assert.notNull(applicationStartup, "applicationStartup should not be null"); + this.applicationStartup = applicationStartup; + } + + @Override + public ApplicationStartup getApplicationStartup() { + return this.applicationStartup; + } + + /** + * Delegate the creation of the access control context to the + * {@link #setSecurityContextProvider SecurityContextProvider}. + */ + @Override + public AccessControlContext getAccessControlContext() { + return (this.securityContextProvider != null ? + this.securityContextProvider.getAccessControlContext() : + AccessController.getContext()); + } + + @Override + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + Assert.notNull(otherFactory, "BeanFactory must not be null"); + setBeanClassLoader(otherFactory.getBeanClassLoader()); + setCacheBeanMetadata(otherFactory.isCacheBeanMetadata()); + setBeanExpressionResolver(otherFactory.getBeanExpressionResolver()); + setConversionService(otherFactory.getConversionService()); + if (otherFactory instanceof AbstractBeanFactory) { + AbstractBeanFactory otherAbstractFactory = (AbstractBeanFactory) otherFactory; + this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars); + this.customEditors.putAll(otherAbstractFactory.customEditors); + this.typeConverter = otherAbstractFactory.typeConverter; + this.beanPostProcessors.addAll(otherAbstractFactory.beanPostProcessors); + this.scopes.putAll(otherAbstractFactory.scopes); + this.securityContextProvider = otherAbstractFactory.securityContextProvider; + } + else { + setTypeConverter(otherFactory.getTypeConverter()); + String[] otherScopeNames = otherFactory.getRegisteredScopeNames(); + for (String scopeName : otherScopeNames) { + this.scopes.put(scopeName, otherFactory.getRegisteredScope(scopeName)); + } + } + } + + /** + * Return a 'merged' BeanDefinition for the given bean name, + * merging a child bean definition with its parent if necessary. + *

This {@code getMergedBeanDefinition} considers bean definition + * in ancestors as well. + * @param name the name of the bean to retrieve the merged definition for + * (may be an alias) + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + @Override + public BeanDefinition getMergedBeanDefinition(String name) throws BeansException { + String beanName = transformedBeanName(name); + // Efficiently check whether bean definition exists in this factory. + if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(beanName); + } + // Resolve merged bean definition locally. + return getMergedLocalBeanDefinition(beanName); + } + + @Override + public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException { + String beanName = transformedBeanName(name); + Object beanInstance = getSingleton(beanName, false); + if (beanInstance != null) { + return (beanInstance instanceof FactoryBean); + } + // No singleton instance found -> check bean definition. + if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { + // No bean definition found in this factory -> delegate to parent. + return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name); + } + return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName)); + } + + @Override + public boolean isActuallyInCreation(String beanName) { + return (isSingletonCurrentlyInCreation(beanName) || isPrototypeCurrentlyInCreation(beanName)); + } + + /** + * Return whether the specified prototype bean is currently in creation + * (within the current thread). + * @param beanName the name of the bean + */ + protected boolean isPrototypeCurrentlyInCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + return (curVal != null && + (curVal.equals(beanName) || (curVal instanceof Set && ((Set) curVal).contains(beanName)))); + } + + /** + * Callback before prototype creation. + *

The default implementation register the prototype as currently in creation. + * @param beanName the name of the prototype about to be created + * @see #isPrototypeCurrentlyInCreation + */ + @SuppressWarnings("unchecked") + protected void beforePrototypeCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + if (curVal == null) { + this.prototypesCurrentlyInCreation.set(beanName); + } + else if (curVal instanceof String) { + Set beanNameSet = new HashSet<>(2); + beanNameSet.add((String) curVal); + beanNameSet.add(beanName); + this.prototypesCurrentlyInCreation.set(beanNameSet); + } + else { + Set beanNameSet = (Set) curVal; + beanNameSet.add(beanName); + } + } + + /** + * Callback after prototype creation. + *

The default implementation marks the prototype as not in creation anymore. + * @param beanName the name of the prototype that has been created + * @see #isPrototypeCurrentlyInCreation + */ + @SuppressWarnings("unchecked") + protected void afterPrototypeCreation(String beanName) { + Object curVal = this.prototypesCurrentlyInCreation.get(); + if (curVal instanceof String) { + this.prototypesCurrentlyInCreation.remove(); + } + else if (curVal instanceof Set) { + Set beanNameSet = (Set) curVal; + beanNameSet.remove(beanName); + if (beanNameSet.isEmpty()) { + this.prototypesCurrentlyInCreation.remove(); + } + } + } + + @Override + public void destroyBean(String beanName, Object beanInstance) { + destroyBean(beanName, beanInstance, getMergedLocalBeanDefinition(beanName)); + } + + /** + * Destroy the given bean instance (usually a prototype instance + * obtained from this factory) according to the given bean definition. + * @param beanName the name of the bean definition + * @param bean the bean instance to destroy + * @param mbd the merged bean definition + */ + protected void destroyBean(String beanName, Object bean, RootBeanDefinition mbd) { + new DisposableBeanAdapter( + bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, getAccessControlContext()).destroy(); + } + + @Override + public void destroyScopedBean(String beanName) { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + if (mbd.isSingleton() || mbd.isPrototype()) { + throw new IllegalArgumentException( + "Bean name '" + beanName + "' does not correspond to an object in a mutable scope"); + } + String scopeName = mbd.getScope(); + Scope scope = this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'"); + } + Object bean = scope.remove(beanName); + if (bean != null) { + destroyBean(beanName, bean, mbd); + } + } + + + //--------------------------------------------------------------------- + // Implementation methods + //--------------------------------------------------------------------- + + /** + * Return the bean name, stripping out the factory dereference prefix if necessary, + * and resolving aliases to canonical names. + * @param name the user-specified name + * @return the transformed bean name + */ + protected String transformedBeanName(String name) { + return canonicalName(BeanFactoryUtils.transformedBeanName(name)); + } + + /** + * Determine the original bean name, resolving locally defined aliases to canonical names. + * @param name the user-specified name + * @return the original bean name + */ + protected String originalBeanName(String name) { + String beanName = transformedBeanName(name); + if (name.startsWith(FACTORY_BEAN_PREFIX)) { + beanName = FACTORY_BEAN_PREFIX + beanName; + } + return beanName; + } + + /** + * Initialize the given BeanWrapper with the custom editors registered + * with this factory. To be called for BeanWrappers that will create + * and populate bean instances. + *

The default implementation delegates to {@link #registerCustomEditors}. + * Can be overridden in subclasses. + * @param bw the BeanWrapper to initialize + */ + protected void initBeanWrapper(BeanWrapper bw) { + bw.setConversionService(getConversionService()); + registerCustomEditors(bw); + } + + /** + * Initialize the given PropertyEditorRegistry with the custom editors + * that have been registered with this BeanFactory. + *

To be called for BeanWrappers that will create and populate bean + * instances, and for SimpleTypeConverter used for constructor argument + * and factory method type conversion. + * @param registry the PropertyEditorRegistry to initialize + */ + protected void registerCustomEditors(PropertyEditorRegistry registry) { + if (registry instanceof PropertyEditorRegistrySupport) { + ((PropertyEditorRegistrySupport) registry).useConfigValueEditors(); + } + if (!this.propertyEditorRegistrars.isEmpty()) { + for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) { + try { + registrar.registerCustomEditors(registry); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + String bceBeanName = bce.getBeanName(); + if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + + "] failed because it tried to obtain currently created bean '" + + ex.getBeanName() + "': " + ex.getMessage()); + } + onSuppressedException(ex); + continue; + } + } + throw ex; + } + } + } + if (!this.customEditors.isEmpty()) { + this.customEditors.forEach((requiredType, editorClass) -> + registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); + } + } + + + /** + * Return a merged RootBeanDefinition, traversing the parent bean definition + * if the specified bean corresponds to a child bean definition. + * @param beanName the name of the bean to retrieve the merged definition for + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { + // Quick check on the concurrent map first, with minimal locking. + RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName); + if (mbd != null && !mbd.stale) { + return mbd; + } + return getMergedBeanDefinition(beanName, getBeanDefinition(beanName)); + } + + /** + * Return a RootBeanDefinition for the given top-level bean, by merging with + * the parent if the given bean's definition is a child bean definition. + * @param beanName the name of the bean definition + * @param bd the original bean definition (Root/ChildBeanDefinition) + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) + throws BeanDefinitionStoreException { + + return getMergedBeanDefinition(beanName, bd, null); + } + + /** + * Return a RootBeanDefinition for the given bean, by merging with the + * parent if the given bean's definition is a child bean definition. + * @param beanName the name of the bean definition + * @param bd the original bean definition (Root/ChildBeanDefinition) + * @param containingBd the containing bean definition in case of inner bean, + * or {@code null} in case of a top-level bean + * @return a (potentially merged) RootBeanDefinition for the given bean + * @throws BeanDefinitionStoreException in case of an invalid bean definition + */ + protected RootBeanDefinition getMergedBeanDefinition( + String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) + throws BeanDefinitionStoreException { + + synchronized (this.mergedBeanDefinitions) { + RootBeanDefinition mbd = null; + RootBeanDefinition previous = null; + + // Check with full lock now in order to enforce the same merged instance. + if (containingBd == null) { + mbd = this.mergedBeanDefinitions.get(beanName); + } + + if (mbd == null || mbd.stale) { + previous = mbd; + if (bd.getParentName() == null) { + // Use copy of given root bean definition. + if (bd instanceof RootBeanDefinition) { + mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); + } + else { + mbd = new RootBeanDefinition(bd); + } + } + else { + // Child bean definition: needs to be merged with parent. + BeanDefinition pbd; + try { + String parentBeanName = transformedBeanName(bd.getParentName()); + if (!beanName.equals(parentBeanName)) { + pbd = getMergedBeanDefinition(parentBeanName); + } + else { + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof ConfigurableBeanFactory) { + pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName); + } + else { + throw new NoSuchBeanDefinitionException(parentBeanName, + "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + + "': cannot be resolved without a ConfigurableBeanFactory parent"); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, + "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex); + } + // Deep copy with overridden values. + mbd = new RootBeanDefinition(pbd); + mbd.overrideFrom(bd); + } + + // Set default singleton scope, if not configured before. + if (!StringUtils.hasLength(mbd.getScope())) { + mbd.setScope(SCOPE_SINGLETON); + } + + // A bean contained in a non-singleton bean cannot be a singleton itself. + // Let's correct this on the fly here, since this might be the result of + // parent-child merging for the outer bean, in which case the original inner bean + // definition will not have inherited the merged outer bean's singleton status. + if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { + mbd.setScope(containingBd.getScope()); + } + + // Cache the merged bean definition for the time being + // (it might still get re-merged later on in order to pick up metadata changes) + if (containingBd == null && isCacheBeanMetadata()) { + this.mergedBeanDefinitions.put(beanName, mbd); + } + } + if (previous != null) { + copyRelevantMergedBeanDefinitionCaches(previous, mbd); + } + return mbd; + } + } + + private void copyRelevantMergedBeanDefinitionCaches(RootBeanDefinition previous, RootBeanDefinition mbd) { + if (ObjectUtils.nullSafeEquals(mbd.getBeanClassName(), previous.getBeanClassName()) && + ObjectUtils.nullSafeEquals(mbd.getFactoryBeanName(), previous.getFactoryBeanName()) && + ObjectUtils.nullSafeEquals(mbd.getFactoryMethodName(), previous.getFactoryMethodName())) { + ResolvableType targetType = mbd.targetType; + ResolvableType previousTargetType = previous.targetType; + if (targetType == null || targetType.equals(previousTargetType)) { + mbd.targetType = previousTargetType; + mbd.isFactoryBean = previous.isFactoryBean; + mbd.resolvedTargetType = previous.resolvedTargetType; + mbd.factoryMethodReturnType = previous.factoryMethodReturnType; + mbd.factoryMethodToIntrospect = previous.factoryMethodToIntrospect; + } + } + } + + /** + * Check the given merged bean definition, + * potentially throwing validation exceptions. + * @param mbd the merged bean definition to check + * @param beanName the name of the bean + * @param args the arguments for bean creation, if any + * @throws BeanDefinitionStoreException in case of validation failure + */ + protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) + throws BeanDefinitionStoreException { + + if (mbd.isAbstract()) { + throw new BeanIsAbstractException(beanName); + } + } + + /** + * Remove the merged bean definition for the specified bean, + * recreating it on next access. + * @param beanName the bean name to clear the merged definition for + */ + protected void clearMergedBeanDefinition(String beanName) { + RootBeanDefinition bd = this.mergedBeanDefinitions.get(beanName); + if (bd != null) { + bd.stale = true; + } + } + + /** + * Clear the merged bean definition cache, removing entries for beans + * which are not considered eligible for full metadata caching yet. + *

Typically triggered after changes to the original bean definitions, + * e.g. after applying a {@code BeanFactoryPostProcessor}. Note that metadata + * for beans which have already been created at this point will be kept around. + * @since 4.2 + */ + public void clearMetadataCache() { + this.mergedBeanDefinitions.forEach((beanName, bd) -> { + if (!isBeanEligibleForMetadataCaching(beanName)) { + bd.stale = true; + } + }); + } + + /** + * Resolve the bean class for the specified bean definition, + * resolving a bean class name into a Class reference (if necessary) + * and storing the resolved Class in the bean definition for further use. + * @param mbd the merged bean definition to determine the class for + * @param beanName the name of the bean (for error handling purposes) + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned {@code Class} will never be exposed to application code) + * @return the resolved bean class (or {@code null} if none) + * @throws CannotLoadBeanClassException if we failed to load the class + */ + @Nullable + protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName, Class... typesToMatch) + throws CannotLoadBeanClassException { + + try { + if (mbd.hasBeanClass()) { + return mbd.getBeanClass(); + } + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedExceptionAction>) + () -> doResolveBeanClass(mbd, typesToMatch), getAccessControlContext()); + } + else { + return doResolveBeanClass(mbd, typesToMatch); + } + } + catch (PrivilegedActionException pae) { + ClassNotFoundException ex = (ClassNotFoundException) pae.getException(); + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (ClassNotFoundException ex) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (LinkageError err) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err); + } + } + + @Nullable + private Class doResolveBeanClass(RootBeanDefinition mbd, Class... typesToMatch) + throws ClassNotFoundException { + + ClassLoader beanClassLoader = getBeanClassLoader(); + ClassLoader dynamicLoader = beanClassLoader; + boolean freshResolve = false; + + if (!ObjectUtils.isEmpty(typesToMatch)) { + // When just doing type checks (i.e. not creating an actual instance yet), + // use the specified temporary class loader (e.g. in a weaving scenario). + ClassLoader tempClassLoader = getTempClassLoader(); + if (tempClassLoader != null) { + dynamicLoader = tempClassLoader; + freshResolve = true; + if (tempClassLoader instanceof DecoratingClassLoader) { + DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; + for (Class typeToMatch : typesToMatch) { + dcl.excludeClass(typeToMatch.getName()); + } + } + } + } + + String className = mbd.getBeanClassName(); + if (className != null) { + Object evaluated = evaluateBeanDefinitionString(className, mbd); + if (!className.equals(evaluated)) { + // A dynamically resolved expression, supported as of 4.2... + if (evaluated instanceof Class) { + return (Class) evaluated; + } + else if (evaluated instanceof String) { + className = (String) evaluated; + freshResolve = true; + } + else { + throw new IllegalStateException("Invalid class name expression result: " + evaluated); + } + } + if (freshResolve) { + // When resolving against a temporary class loader, exit early in order + // to avoid storing the resolved Class in the bean definition. + if (dynamicLoader != null) { + try { + return dynamicLoader.loadClass(className); + } + catch (ClassNotFoundException ex) { + if (logger.isTraceEnabled()) { + logger.trace("Could not load class [" + className + "] from " + dynamicLoader + ": " + ex); + } + } + } + return ClassUtils.forName(className, dynamicLoader); + } + } + + // Resolve regularly, caching the result in the BeanDefinition... + return mbd.resolveBeanClass(beanClassLoader); + } + + /** + * Evaluate the given String as contained in a bean definition, + * potentially resolving it as an expression. + * @param value the value to check + * @param beanDefinition the bean definition that the value comes from + * @return the resolved value + * @see #setBeanExpressionResolver + */ + @Nullable + protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) { + if (this.beanExpressionResolver == null) { + return value; + } + + Scope scope = null; + if (beanDefinition != null) { + String scopeName = beanDefinition.getScope(); + if (scopeName != null) { + scope = getRegisteredScope(scopeName); + } + } + return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope)); + } + + + /** + * Predict the eventual bean type (of the processed bean instance) for the + * specified bean. Called by {@link #getType} and {@link #isTypeMatch}. + * Does not need to handle FactoryBeans specifically, since it is only + * supposed to operate on the raw bean type. + *

This implementation is simplistic in that it is not able to + * handle factory methods and InstantiationAwareBeanPostProcessors. + * It only predicts the bean type correctly for a standard bean. + * To be overridden in subclasses, applying more sophisticated type detection. + * @param beanName the name of the bean + * @param mbd the merged bean definition to determine the type for + * @param typesToMatch the types to match in case of internal type matching purposes + * (also signals that the returned {@code Class} will never be exposed to application code) + * @return the type of the bean, or {@code null} if not predictable + */ + @Nullable + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + Class targetType = mbd.getTargetType(); + if (targetType != null) { + return targetType; + } + if (mbd.getFactoryMethodName() != null) { + return null; + } + return resolveBeanClass(mbd, beanName, typesToMatch); + } + + /** + * Check whether the given bean is defined as a {@link FactoryBean}. + * @param beanName the name of the bean + * @param mbd the corresponding bean definition + */ + protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { + Boolean result = mbd.isFactoryBean; + if (result == null) { + Class beanType = predictBeanType(beanName, mbd, FactoryBean.class); + result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType)); + mbd.isFactoryBean = result; + } + return result; + } + + /** + * Determine the bean type for the given FactoryBean definition, as far as possible. + * Only called if there is no singleton instance registered for the target bean + * already. The implementation is allowed to instantiate the target factory bean if + * {@code allowInit} is {@code true} and the type cannot be determined another way; + * otherwise it is restricted to introspecting signatures and related metadata. + *

If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition + * and {@code allowInit} is {@code true}, the default implementation will create + * the FactoryBean via {@code getBean} to call its {@code getObjectType} method. + * Subclasses are encouraged to optimize this, typically by inspecting the generic + * signature of the factory bean class or the factory method that creates it. + * If subclasses do instantiate the FactoryBean, they should consider trying the + * {@code getObjectType} method without fully populating the bean. If this fails, + * a full FactoryBean creation as performed by this implementation should be used + * as fallback. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param allowInit if initialization of the FactoryBean is permitted if the type + * cannot be determined another way + * @return the type for the bean if determinable, otherwise {@code ResolvableType.NONE} + * @since 5.2 + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + * @see #getBean(String) + */ + protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) { + ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd); + if (result != ResolvableType.NONE) { + return result; + } + + if (allowInit && mbd.isSingleton()) { + try { + FactoryBean factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true); + Class objectType = getTypeForFactoryBean(factoryBean); + return (objectType != null ? ResolvableType.forClass(objectType) : ResolvableType.NONE); + } + catch (BeanCreationException ex) { + if (ex.contains(BeanCurrentlyInCreationException.class)) { + logger.trace(LogMessage.format("Bean currently in creation on FactoryBean type check: %s", ex)); + } + else if (mbd.isLazyInit()) { + logger.trace(LogMessage.format("Bean creation exception on lazy FactoryBean type check: %s", ex)); + } + else { + logger.debug(LogMessage.format("Bean creation exception on eager FactoryBean type check: %s", ex)); + } + onSuppressedException(ex); + } + } + return ResolvableType.NONE; + } + + /** + * Determine the bean type for a FactoryBean by inspecting its attributes for a + * {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} value. + * @param attributes the attributes to inspect + * @return a {@link ResolvableType} extracted from the attributes or + * {@code ResolvableType.NONE} + * @since 5.2 + */ + ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) { + Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE); + if (attribute instanceof ResolvableType) { + return (ResolvableType) attribute; + } + if (attribute instanceof Class) { + return ResolvableType.forClass((Class) attribute); + } + return ResolvableType.NONE; + } + + /** + * Determine the bean type for the given FactoryBean definition, as far as possible. + * Only called if there is no singleton instance registered for the target bean already. + *

The default implementation creates the FactoryBean via {@code getBean} + * to call its {@code getObjectType} method. Subclasses are encouraged to optimize + * this, typically by just instantiating the FactoryBean but not populating it yet, + * trying whether its {@code getObjectType} method already returns a type. + * If no type found, a full FactoryBean creation as performed by this implementation + * should be used as fallback. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @return the type for the bean if determinable, or {@code null} otherwise + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + * @see #getBean(String) + * @deprecated since 5.2 in favor of {@link #getTypeForFactoryBean(String, RootBeanDefinition, boolean)} + */ + @Nullable + @Deprecated + protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { + return getTypeForFactoryBean(beanName, mbd, true).resolve(); + } + + /** + * Mark the specified bean as already created (or about to be created). + *

This allows the bean factory to optimize its caching for repeated + * creation of the specified bean. + * @param beanName the name of the bean + */ + protected void markBeanAsCreated(String beanName) { + if (!this.alreadyCreated.contains(beanName)) { + synchronized (this.mergedBeanDefinitions) { + if (!this.alreadyCreated.contains(beanName)) { + // Let the bean definition get re-merged now that we're actually creating + // the bean... just in case some of its metadata changed in the meantime. + clearMergedBeanDefinition(beanName); + this.alreadyCreated.add(beanName); + } + } + } + } + + /** + * Perform appropriate cleanup of cached metadata after bean creation failed. + * @param beanName the name of the bean + */ + protected void cleanupAfterBeanCreationFailure(String beanName) { + synchronized (this.mergedBeanDefinitions) { + this.alreadyCreated.remove(beanName); + } + } + + /** + * Determine whether the specified bean is eligible for having + * its bean definition metadata cached. + * @param beanName the name of the bean + * @return {@code true} if the bean's metadata may be cached + * at this point already + */ + protected boolean isBeanEligibleForMetadataCaching(String beanName) { + return this.alreadyCreated.contains(beanName); + } + + /** + * Remove the singleton instance (if any) for the given bean name, + * but only if it hasn't been used for other purposes than type checking. + * @param beanName the name of the bean + * @return {@code true} if actually removed, {@code false} otherwise + */ + protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) { + if (!this.alreadyCreated.contains(beanName)) { + removeSingleton(beanName); + return true; + } + else { + return false; + } + } + + /** + * Check whether this factory's bean creation phase already started, + * i.e. whether any bean has been marked as created in the meantime. + * @since 4.2.2 + * @see #markBeanAsCreated + */ + protected boolean hasBeanCreationStarted() { + return !this.alreadyCreated.isEmpty(); + } + + /** + * Get the object for the given bean instance, either the bean + * instance itself or its created object in case of a FactoryBean. + * @param beanInstance the shared bean instance + * @param name the name that may include factory dereference prefix + * @param beanName the canonical bean name + * @param mbd the merged bean definition + * @return the object to expose for the bean + */ + protected Object getObjectForBeanInstance( + Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { + + // Don't let calling code try to dereference the factory if the bean isn't a factory. + if (BeanFactoryUtils.isFactoryDereference(name)) { + if (beanInstance instanceof NullBean) { + return beanInstance; + } + if (!(beanInstance instanceof FactoryBean)) { + throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); + } + if (mbd != null) { + mbd.isFactoryBean = true; + } + return beanInstance; + } + + // Now we have the bean instance, which may be a normal bean or a FactoryBean. + // If it's a FactoryBean, we use it to create a bean instance, unless the + // caller actually wants a reference to the factory. + if (!(beanInstance instanceof FactoryBean)) { + return beanInstance; + } + + Object object = null; + if (mbd != null) { + mbd.isFactoryBean = true; + } + else { + object = getCachedObjectForFactoryBean(beanName); + } + if (object == null) { + // Return bean instance from factory. + FactoryBean factory = (FactoryBean) beanInstance; + // Caches object obtained from FactoryBean if it is a singleton. + if (mbd == null && containsBeanDefinition(beanName)) { + mbd = getMergedLocalBeanDefinition(beanName); + } + boolean synthetic = (mbd != null && mbd.isSynthetic()); + object = getObjectFromFactoryBean(factory, beanName, !synthetic); + } + return object; + } + + /** + * Determine whether the given bean name is already in use within this factory, + * i.e. whether there is a local bean or alias registered under this name or + * an inner bean created with this name. + * @param beanName the name to check + */ + public boolean isBeanNameInUse(String beanName) { + return isAlias(beanName) || containsLocalBean(beanName) || hasDependentBean(beanName); + } + + /** + * Determine whether the given bean requires destruction on shutdown. + *

The default implementation checks the DisposableBean interface as well as + * a specified destroy method and registered DestructionAwareBeanPostProcessors. + * @param bean the bean instance to check + * @param mbd the corresponding bean definition + * @see org.springframework.beans.factory.DisposableBean + * @see AbstractBeanDefinition#getDestroyMethodName() + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + */ + protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { + return (bean.getClass() != NullBean.class && (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || + (hasDestructionAwareBeanPostProcessors() && DisposableBeanAdapter.hasApplicableProcessors( + bean, getBeanPostProcessorCache().destructionAware)))); + } + + /** + * Add the given bean to the list of disposable beans in this factory, + * registering its DisposableBean interface and/or the given destroy method + * to be called on factory shutdown (if applicable). Only applies to singletons. + * @param beanName the name of the bean + * @param bean the bean instance + * @param mbd the bean definition for the bean + * @see RootBeanDefinition#isSingleton + * @see RootBeanDefinition#getDependsOn + * @see #registerDisposableBean + * @see #registerDependentBean + */ + protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) { + AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null); + if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { + if (mbd.isSingleton()) { + // Register a DisposableBean implementation that performs all destruction + // work for the given bean: DestructionAwareBeanPostProcessors, + // DisposableBean interface, custom destroy method. + registerDisposableBean(beanName, new DisposableBeanAdapter( + bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc)); + } + else { + // A bean with a custom scope... + Scope scope = this.scopes.get(mbd.getScope()); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'"); + } + scope.registerDestructionCallback(beanName, new DisposableBeanAdapter( + bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc)); + } + } + } + + + //--------------------------------------------------------------------- + // Abstract methods to be implemented by subclasses + //--------------------------------------------------------------------- + + /** + * Check if this bean factory contains a bean definition with the given name. + * Does not consider any hierarchy this factory may participate in. + * Invoked by {@code containsBean} when no cached singleton instance is found. + *

Depending on the nature of the concrete bean factory implementation, + * this operation might be expensive (for example, because of directory lookups + * in external registries). However, for listable bean factories, this usually + * just amounts to a local hash lookup: The operation is therefore part of the + * public interface there. The same implementation can serve for both this + * template method and the public interface method in that case. + * @param beanName the name of the bean to look for + * @return if this bean factory contains a bean definition with the given name + * @see #containsBean + * @see org.springframework.beans.factory.ListableBeanFactory#containsBeanDefinition + */ + protected abstract boolean containsBeanDefinition(String beanName); + + /** + * Return the bean definition for the given bean name. + * Subclasses should normally implement caching, as this method is invoked + * by this class every time bean definition metadata is needed. + *

Depending on the nature of the concrete bean factory implementation, + * this operation might be expensive (for example, because of directory lookups + * in external registries). However, for listable bean factories, this usually + * just amounts to a local hash lookup: The operation is therefore part of the + * public interface there. The same implementation can serve for both this + * template method and the public interface method in that case. + * @param beanName the name of the bean to find a definition for + * @return the BeanDefinition for this prototype name (never {@code null}) + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * if the bean definition cannot be resolved + * @throws BeansException in case of errors + * @see RootBeanDefinition + * @see ChildBeanDefinition + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#getBeanDefinition + */ + protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException; + + /** + * Create a bean instance for the given merged bean definition (and arguments). + * The bean definition will already have been merged with the parent definition + * in case of a child definition. + *

All bean retrieval methods delegate to this method for actual bean creation. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param args explicit arguments to use for constructor or factory method invocation + * @return a new instance of the bean + * @throws BeanCreationException if the bean could not be created + */ + protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException; + + + /** + * CopyOnWriteArrayList which resets the beanPostProcessorCache field on modification. + * + * @since 5.3 + */ + private class BeanPostProcessorCacheAwareList extends CopyOnWriteArrayList { + + @Override + public BeanPostProcessor set(int index, BeanPostProcessor element) { + BeanPostProcessor result = super.set(index, element); + beanPostProcessorCache = null; + return result; + } + + @Override + public boolean add(BeanPostProcessor o) { + boolean success = super.add(o); + beanPostProcessorCache = null; + return success; + } + + @Override + public void add(int index, BeanPostProcessor element) { + super.add(index, element); + beanPostProcessorCache = null; + } + + @Override + public BeanPostProcessor remove(int index) { + BeanPostProcessor result = super.remove(index); + beanPostProcessorCache = null; + return result; + } + + @Override + public boolean remove(Object o) { + boolean success = super.remove(o); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public boolean removeAll(Collection c) { + boolean success = super.removeAll(c); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public boolean retainAll(Collection c) { + boolean success = super.retainAll(c); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public boolean addAll(Collection c) { + boolean success = super.addAll(c); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public boolean addAll(int index, Collection c) { + boolean success = super.addAll(index, c); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public boolean removeIf(Predicate filter) { + boolean success = super.removeIf(filter); + if (success) { + beanPostProcessorCache = null; + } + return success; + } + + @Override + public void replaceAll(UnaryOperator operator) { + super.replaceAll(operator); + beanPostProcessorCache = null; + } + } + + + /** + * Internal cache of pre-filtered post-processors. + * + * @since 5.3 + */ + static class BeanPostProcessorCache { + + final List instantiationAware = new ArrayList<>(); + + final List smartInstantiationAware = new ArrayList<>(); + + final List destructionAware = new ArrayList<>(); + + final List mergedDefinition = new ArrayList<>(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java new file mode 100644 index 0000000..405e35c --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import org.springframework.beans.BeanMetadataAttributeAccessor; +import org.springframework.util.Assert; + +/** + * Qualifier for resolving autowire candidates. A bean definition that + * includes one or more such qualifiers enables fine-grained matching + * against annotations on a field or parameter to be autowired. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + * @see org.springframework.beans.factory.annotation.Qualifier + */ +@SuppressWarnings("serial") +public class AutowireCandidateQualifier extends BeanMetadataAttributeAccessor { + + /** + * The name of the key used to store the value. + */ + public static final String VALUE_KEY = "value"; + + private final String typeName; + + + /** + * Construct a qualifier to match against an annotation of the + * given type. + * @param type the annotation type + */ + public AutowireCandidateQualifier(Class type) { + this(type.getName()); + } + + /** + * Construct a qualifier to match against an annotation of the + * given type name. + *

The type name may match the fully-qualified class name of + * the annotation or the short class name (without the package). + * @param typeName the name of the annotation type + */ + public AutowireCandidateQualifier(String typeName) { + Assert.notNull(typeName, "Type name must not be null"); + this.typeName = typeName; + } + + /** + * Construct a qualifier to match against an annotation of the + * given type whose {@code value} attribute also matches + * the specified value. + * @param type the annotation type + * @param value the annotation value to match + */ + public AutowireCandidateQualifier(Class type, Object value) { + this(type.getName(), value); + } + + /** + * Construct a qualifier to match against an annotation of the + * given type name whose {@code value} attribute also matches + * the specified value. + *

The type name may match the fully-qualified class name of + * the annotation or the short class name (without the package). + * @param typeName the name of the annotation type + * @param value the annotation value to match + */ + public AutowireCandidateQualifier(String typeName, Object value) { + Assert.notNull(typeName, "Type name must not be null"); + this.typeName = typeName; + setAttribute(VALUE_KEY, value); + } + + + /** + * Retrieve the type name. This value will be the same as the + * type name provided to the constructor or the fully-qualified + * class name if a Class instance was provided to the constructor. + */ + public String getTypeName() { + return this.typeName; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java new file mode 100644 index 0000000..0a1f0a7 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.lang.Nullable; + +/** + * Strategy interface for determining whether a specific bean definition + * qualifies as an autowire candidate for a specific dependency. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + */ +public interface AutowireCandidateResolver { + + /** + * Determine whether the given bean definition qualifies as an + * autowire candidate for the given dependency. + *

The default implementation checks + * {@link org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()}. + * @param bdHolder the bean definition including bean name and aliases + * @param descriptor the descriptor for the target method parameter or field + * @return whether the bean definition qualifies as autowire candidate + * @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate() + */ + default boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + return bdHolder.getBeanDefinition().isAutowireCandidate(); + } + + /** + * Determine whether the given descriptor is effectively required. + *

The default implementation checks {@link DependencyDescriptor#isRequired()}. + * @param descriptor the descriptor for the target method parameter or field + * @return whether the descriptor is marked as required or possibly indicating + * non-required status some other way (e.g. through a parameter annotation) + * @since 5.0 + * @see DependencyDescriptor#isRequired() + */ + default boolean isRequired(DependencyDescriptor descriptor) { + return descriptor.isRequired(); + } + + /** + * Determine whether the given descriptor declares a qualifier beyond the type + * (typically - but not necessarily - a specific kind of annotation). + *

The default implementation returns {@code false}. + * @param descriptor the descriptor for the target method parameter or field + * @return whether the descriptor declares a qualifier, narrowing the candidate + * status beyond the type match + * @since 5.1 + * @see org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#hasQualifier + */ + default boolean hasQualifier(DependencyDescriptor descriptor) { + return false; + } + + /** + * Determine whether a default value is suggested for the given dependency. + *

The default implementation simply returns {@code null}. + * @param descriptor the descriptor for the target method parameter or field + * @return the value suggested (typically an expression String), + * or {@code null} if none found + * @since 3.0 + */ + @Nullable + default Object getSuggestedValue(DependencyDescriptor descriptor) { + return null; + } + + /** + * Build a proxy for lazy resolution of the actual dependency target, + * if demanded by the injection point. + *

The default implementation simply returns {@code null}. + * @param descriptor the descriptor for the target method parameter or field + * @param beanName the name of the bean that contains the injection point + * @return the lazy resolution proxy for the actual dependency target, + * or {@code null} if straight resolution is to be performed + * @since 4.0 + */ + @Nullable + default Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { + return null; + } + + /** + * Return a clone of this resolver instance if necessary, retaining its local + * configuration and allowing for the cloned instance to get associated with + * a new bean factory, or this original instance if there is no such state. + *

The default implementation creates a separate instance via the default + * class constructor, assuming no specific configuration state to copy. + * Subclasses may override this with custom configuration state handling + * or with standard {@link Cloneable} support (as implemented by Spring's + * own configurable {@code AutowireCandidateResolver} variants), or simply + * return {@code this} (as in {@link SimpleAutowireCandidateResolver}). + * @since 5.2.7 + * @see GenericTypeAwareAutowireCandidateResolver#cloneIfNecessary() + * @see DefaultListableBeanFactory#copyConfigurationFrom + */ + default AutowireCandidateResolver cloneIfNecessary() { + return BeanUtils.instantiateClass(getClass()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java new file mode 100644 index 0000000..d807cc4 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -0,0 +1,300 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Utility class that contains various methods useful for the implementation of + * autowire-capable bean factories. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Sam Brannen + * @since 1.1.2 + * @see AbstractAutowireCapableBeanFactory + */ +abstract class AutowireUtils { + + public static final Comparator EXECUTABLE_COMPARATOR = (e1, e2) -> { + int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers())); + return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount()); + }; + + + /** + * Sort the given constructors, preferring public constructors and "greedy" ones with + * a maximum number of arguments. The result will contain public constructors first, + * with decreasing number of arguments, then non-public constructors, again with + * decreasing number of arguments. + * @param constructors the constructor array to sort + */ + public static void sortConstructors(Constructor[] constructors) { + Arrays.sort(constructors, EXECUTABLE_COMPARATOR); + } + + /** + * Sort the given factory methods, preferring public methods and "greedy" ones + * with a maximum of arguments. The result will contain public methods first, + * with decreasing number of arguments, then non-public methods, again with + * decreasing number of arguments. + * @param factoryMethods the factory method array to sort + */ + public static void sortFactoryMethods(Method[] factoryMethods) { + Arrays.sort(factoryMethods, EXECUTABLE_COMPARATOR); + } + + /** + * Determine whether the given bean property is excluded from dependency checks. + *

This implementation excludes properties defined by CGLIB. + * @param pd the PropertyDescriptor of the bean property + * @return whether the bean property is excluded + */ + public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { + Method wm = pd.getWriteMethod(); + if (wm == null) { + return false; + } + if (!wm.getDeclaringClass().getName().contains("$$")) { + // Not a CGLIB method so it's OK. + return false; + } + // It was declared by CGLIB, but we might still want to autowire it + // if it was actually declared by the superclass. + Class superclass = wm.getDeclaringClass().getSuperclass(); + return !ClassUtils.hasMethod(superclass, wm); + } + + /** + * Return whether the setter method of the given bean property is defined + * in any of the given interfaces. + * @param pd the PropertyDescriptor of the bean property + * @param interfaces the Set of interfaces (Class objects) + * @return whether the setter method is defined by an interface + */ + public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { + Method setter = pd.getWriteMethod(); + if (setter != null) { + Class targetClass = setter.getDeclaringClass(); + for (Class ifc : interfaces) { + if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter)) { + return true; + } + } + } + return false; + } + + /** + * Resolve the given autowiring value against the given required type, + * e.g. an {@link ObjectFactory} value to its actual object result. + * @param autowiringValue the value to resolve + * @param requiredType the type to assign the result to + * @return the resolved value + */ + public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { + if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { + ObjectFactory factory = (ObjectFactory) autowiringValue; + if (autowiringValue instanceof Serializable && requiredType.isInterface()) { + autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), + new Class[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); + } + else { + return factory.getObject(); + } + } + return autowiringValue; + } + + /** + * Determine the target type for the generic return type of the given + * generic factory method, where formal type variables are declared + * on the given method itself. + *

For example, given a factory method with the following signature, if + * {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected + * method for {@code createProxy()} and an {@code Object[]} array containing + * {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will + * infer that the target return type is {@code MyService}. + *

{@code public static  T createProxy(Class clazz)}
+ *

Possible Return Values

+ *
    + *
  • the target return type, if it can be inferred
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if + * the given {@code method} does not declare any {@linkplain + * Method#getTypeParameters() formal type variables}
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if the + * target return type cannot be inferred (e.g., due to type erasure)
  • + *
  • {@code null}, if the length of the given arguments array is shorter + * than the length of the {@linkplain + * Method#getGenericParameterTypes() formal argument list} for the given + * method
  • + *
+ * @param method the method to introspect (never {@code null}) + * @param args the arguments that will be supplied to the method when it is + * invoked (never {@code null}) + * @param classLoader the ClassLoader to resolve class names against, + * if necessary (never {@code null}) + * @return the resolved target return type or the standard method return type + * @since 3.2.5 + */ + public static Class resolveReturnTypeForFactoryMethod( + Method method, Object[] args, @Nullable ClassLoader classLoader) { + + Assert.notNull(method, "Method must not be null"); + Assert.notNull(args, "Argument array must not be null"); + + TypeVariable[] declaredTypeVariables = method.getTypeParameters(); + Type genericReturnType = method.getGenericReturnType(); + Type[] methodParameterTypes = method.getGenericParameterTypes(); + Assert.isTrue(args.length == methodParameterTypes.length, "Argument array does not match parameter count"); + + // Ensure that the type variable (e.g., T) is declared directly on the method + // itself (e.g., via ), not on the enclosing class or interface. + boolean locallyDeclaredTypeVariableMatchesReturnType = false; + for (TypeVariable currentTypeVariable : declaredTypeVariables) { + if (currentTypeVariable.equals(genericReturnType)) { + locallyDeclaredTypeVariableMatchesReturnType = true; + break; + } + } + + if (locallyDeclaredTypeVariableMatchesReturnType) { + for (int i = 0; i < methodParameterTypes.length; i++) { + Type methodParameterType = methodParameterTypes[i]; + Object arg = args[i]; + if (methodParameterType.equals(genericReturnType)) { + if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + if (typedValue.hasTargetType()) { + return typedValue.getTargetType(); + } + try { + Class resolvedType = typedValue.resolveTargetType(classLoader); + if (resolvedType != null) { + return resolvedType; + } + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Failed to resolve value type [" + + typedValue.getTargetTypeName() + "] for factory method argument", ex); + } + } + else if (arg != null && !(arg instanceof BeanMetadataElement)) { + // Only consider argument type if it is a simple value... + return arg.getClass(); + } + return method.getReturnType(); + } + else if (methodParameterType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) methodParameterType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + for (Type typeArg : actualTypeArguments) { + if (typeArg.equals(genericReturnType)) { + if (arg instanceof Class) { + return (Class) arg; + } + else { + String className = null; + if (arg instanceof String) { + className = (String) arg; + } + else if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + String targetTypeName = typedValue.getTargetTypeName(); + if (targetTypeName == null || Class.class.getName().equals(targetTypeName)) { + className = typedValue.getValue(); + } + } + if (className != null) { + try { + return ClassUtils.forName(className, classLoader); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not resolve class name [" + arg + + "] for factory method argument", ex); + } + } + // Consider adding logic to determine the class of the typeArg, if possible. + // For now, just fall back... + return method.getReturnType(); + } + } + } + } + } + } + + // Fall back... + return method.getReturnType(); + } + + + /** + * Reflective {@link InvocationHandler} for lazy access to the current target object. + */ + @SuppressWarnings("serial") + private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { + + private final ObjectFactory objectFactory; + + ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "equals": + // Only consider equal when proxies are identical. + return (proxy == args[0]); + case "hashCode": + // Use hashCode of proxy. + return System.identityHashCode(proxy); + case "toString": + return this.objectFactory.toString(); + } + try { + return method.invoke(this.objectFactory.getObject(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java new file mode 100644 index 0000000..dab55e9 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java @@ -0,0 +1,345 @@ +/* + * Copyright 2002-2019 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.beans.factory.support; + +import java.util.function.Supplier; + +import org.springframework.beans.factory.config.AutowiredPropertyMarker; +import org.springframework.beans.factory.config.BeanDefinitionCustomizer; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * Programmatic means of constructing + * {@link org.springframework.beans.factory.config.BeanDefinition BeanDefinitions} + * using the builder pattern. Intended primarily for use when implementing Spring 2.0 + * {@link org.springframework.beans.factory.xml.NamespaceHandler NamespaceHandlers}. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public final class BeanDefinitionBuilder { + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}. + */ + public static BeanDefinitionBuilder genericBeanDefinition() { + return new BeanDefinitionBuilder(new GenericBeanDefinition()); + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + */ + public static BeanDefinitionBuilder genericBeanDefinition(String beanClassName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); + builder.beanDefinition.setBeanClassName(beanClassName); + return builder; + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}. + * @param beanClass the {@code Class} of the bean that the definition is being created for + */ + public static BeanDefinitionBuilder genericBeanDefinition(Class beanClass) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); + builder.beanDefinition.setBeanClass(beanClass); + return builder; + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}. + * @param beanClass the {@code Class} of the bean that the definition is being created for + * @param instanceSupplier a callback for creating an instance of the bean + * @since 5.0 + */ + public static BeanDefinitionBuilder genericBeanDefinition(Class beanClass, Supplier instanceSupplier) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); + builder.beanDefinition.setBeanClass(beanClass); + builder.beanDefinition.setInstanceSupplier(instanceSupplier); + return builder; + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + */ + public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName) { + return rootBeanDefinition(beanClassName, null); + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanClassName the class name for the bean that the definition is being created for + * @param factoryMethodName the name of the method to use to construct the bean instance + */ + public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName, @Nullable String factoryMethodName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new RootBeanDefinition()); + builder.beanDefinition.setBeanClassName(beanClassName); + builder.beanDefinition.setFactoryMethodName(factoryMethodName); + return builder; + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanClass the {@code Class} of the bean that the definition is being created for + */ + public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass) { + return rootBeanDefinition(beanClass, null); + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanClass the {@code Class} of the bean that the definition is being created for + * @param factoryMethodName the name of the method to use to construct the bean instance + */ + public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass, @Nullable String factoryMethodName) { + BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new RootBeanDefinition()); + builder.beanDefinition.setBeanClass(beanClass); + builder.beanDefinition.setFactoryMethodName(factoryMethodName); + return builder; + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link ChildBeanDefinition}. + * @param parentName the name of the parent bean + */ + public static BeanDefinitionBuilder childBeanDefinition(String parentName) { + return new BeanDefinitionBuilder(new ChildBeanDefinition(parentName)); + } + + + /** + * The {@code BeanDefinition} instance we are creating. + */ + private final AbstractBeanDefinition beanDefinition; + + /** + * Our current position with respect to constructor args. + */ + private int constructorArgIndex; + + + /** + * Enforce the use of factory methods. + */ + private BeanDefinitionBuilder(AbstractBeanDefinition beanDefinition) { + this.beanDefinition = beanDefinition; + } + + /** + * Return the current BeanDefinition object in its raw (unvalidated) form. + * @see #getBeanDefinition() + */ + public AbstractBeanDefinition getRawBeanDefinition() { + return this.beanDefinition; + } + + /** + * Validate and return the created BeanDefinition object. + */ + public AbstractBeanDefinition getBeanDefinition() { + this.beanDefinition.validate(); + return this.beanDefinition; + } + + + /** + * Set the name of the parent definition of this bean definition. + */ + public BeanDefinitionBuilder setParentName(String parentName) { + this.beanDefinition.setParentName(parentName); + return this; + } + + /** + * Set the name of a static factory method to use for this definition, + * to be called on this bean's class. + */ + public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) { + this.beanDefinition.setFactoryMethodName(factoryMethod); + return this; + } + + /** + * Set the name of a non-static factory method to use for this definition, + * including the bean name of the factory instance to call the method on. + * @param factoryMethod the name of the factory method + * @param factoryBean the name of the bean to call the specified factory method on + * @since 4.3.6 + */ + public BeanDefinitionBuilder setFactoryMethodOnBean(String factoryMethod, String factoryBean) { + this.beanDefinition.setFactoryMethodName(factoryMethod); + this.beanDefinition.setFactoryBeanName(factoryBean); + return this; + } + + /** + * Add an indexed constructor arg value. The current index is tracked internally + * and all additions are at the present point. + */ + public BeanDefinitionBuilder addConstructorArgValue(@Nullable Object value) { + this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue( + this.constructorArgIndex++, value); + return this; + } + + /** + * Add a reference to a named bean as a constructor arg. + * @see #addConstructorArgValue(Object) + */ + public BeanDefinitionBuilder addConstructorArgReference(String beanName) { + this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue( + this.constructorArgIndex++, new RuntimeBeanReference(beanName)); + return this; + } + + /** + * Add the supplied property value under the given property name. + */ + public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) { + this.beanDefinition.getPropertyValues().add(name, value); + return this; + } + + /** + * Add a reference to the specified bean name under the property specified. + * @param name the name of the property to add the reference to + * @param beanName the name of the bean being referenced + */ + public BeanDefinitionBuilder addPropertyReference(String name, String beanName) { + this.beanDefinition.getPropertyValues().add(name, new RuntimeBeanReference(beanName)); + return this; + } + + /** + * Add an autowired marker for the specified property on the specified bean. + * @param name the name of the property to mark as autowired + * @since 5.2 + * @see AutowiredPropertyMarker + */ + public BeanDefinitionBuilder addAutowiredProperty(String name) { + this.beanDefinition.getPropertyValues().add(name, AutowiredPropertyMarker.INSTANCE); + return this; + } + + /** + * Set the init method for this definition. + */ + public BeanDefinitionBuilder setInitMethodName(@Nullable String methodName) { + this.beanDefinition.setInitMethodName(methodName); + return this; + } + + /** + * Set the destroy method for this definition. + */ + public BeanDefinitionBuilder setDestroyMethodName(@Nullable String methodName) { + this.beanDefinition.setDestroyMethodName(methodName); + return this; + } + + + /** + * Set the scope of this definition. + * @see org.springframework.beans.factory.config.BeanDefinition#SCOPE_SINGLETON + * @see org.springframework.beans.factory.config.BeanDefinition#SCOPE_PROTOTYPE + */ + public BeanDefinitionBuilder setScope(@Nullable String scope) { + this.beanDefinition.setScope(scope); + return this; + } + + /** + * Set whether or not this definition is abstract. + */ + public BeanDefinitionBuilder setAbstract(boolean flag) { + this.beanDefinition.setAbstract(flag); + return this; + } + + /** + * Set whether beans for this definition should be lazily initialized or not. + */ + public BeanDefinitionBuilder setLazyInit(boolean lazy) { + this.beanDefinition.setLazyInit(lazy); + return this; + } + + /** + * Set the autowire mode for this definition. + */ + public BeanDefinitionBuilder setAutowireMode(int autowireMode) { + this.beanDefinition.setAutowireMode(autowireMode); + return this; + } + + /** + * Set the dependency check mode for this definition. + */ + public BeanDefinitionBuilder setDependencyCheck(int dependencyCheck) { + this.beanDefinition.setDependencyCheck(dependencyCheck); + return this; + } + + /** + * Append the specified bean name to the list of beans that this definition + * depends on. + */ + public BeanDefinitionBuilder addDependsOn(String beanName) { + if (this.beanDefinition.getDependsOn() == null) { + this.beanDefinition.setDependsOn(beanName); + } + else { + String[] added = ObjectUtils.addObjectToArray(this.beanDefinition.getDependsOn(), beanName); + this.beanDefinition.setDependsOn(added); + } + return this; + } + + /** + * Set whether this bean is a primary autowire candidate. + * @since 5.1.11 + */ + public BeanDefinitionBuilder setPrimary(boolean primary) { + this.beanDefinition.setPrimary(primary); + return this; + } + + /** + * Set the role of this definition. + */ + public BeanDefinitionBuilder setRole(int role) { + this.beanDefinition.setRole(role); + return this; + } + + /** + * Apply the given customizers to the underlying bean definition. + * @since 5.0 + */ + public BeanDefinitionBuilder applyCustomizers(BeanDefinitionCustomizer... customizers) { + for (BeanDefinitionCustomizer customizer : customizers) { + customizer.customize(this.beanDefinition); + } + return this; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java new file mode 100644 index 0000000..5da53bf --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * A simple holder for {@code BeanDefinition} property defaults. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + * @see AbstractBeanDefinition#applyDefaults + */ +public class BeanDefinitionDefaults { + + @Nullable + private Boolean lazyInit; + + private int autowireMode = AbstractBeanDefinition.AUTOWIRE_NO; + + private int dependencyCheck = AbstractBeanDefinition.DEPENDENCY_CHECK_NONE; + + @Nullable + private String initMethodName; + + @Nullable + private String destroyMethodName; + + + /** + * Set whether beans should be lazily initialized by default. + *

If {@code false}, the bean will get instantiated on startup by bean + * factories that perform eager initialization of singletons. + * @see AbstractBeanDefinition#setLazyInit + */ + public void setLazyInit(boolean lazyInit) { + this.lazyInit = lazyInit; + } + + /** + * Return whether beans should be lazily initialized by default, i.e. not + * eagerly instantiated on startup. Only applicable to singleton beans. + * @return whether to apply lazy-init semantics ({@code false} by default) + */ + public boolean isLazyInit() { + return (this.lazyInit != null && this.lazyInit.booleanValue()); + } + + /** + * Return whether beans should be lazily initialized by default, i.e. not + * eagerly instantiated on startup. Only applicable to singleton beans. + * @return the lazy-init flag if explicitly set, or {@code null} otherwise + * @since 5.2 + */ + @Nullable + public Boolean getLazyInit() { + return this.lazyInit; + } + + /** + * Set the autowire mode. This determines whether any automagical detection + * and setting of bean references will happen. Default is AUTOWIRE_NO + * which means there won't be convention-based autowiring by name or type + * (however, there may still be explicit annotation-driven autowiring). + * @param autowireMode the autowire mode to set. + * Must be one of the constants defined in {@link AbstractBeanDefinition}. + * @see AbstractBeanDefinition#setAutowireMode + */ + public void setAutowireMode(int autowireMode) { + this.autowireMode = autowireMode; + } + + /** + * Return the default autowire mode. + */ + public int getAutowireMode() { + return this.autowireMode; + } + + /** + * Set the dependency check code. + * @param dependencyCheck the code to set. + * Must be one of the constants defined in {@link AbstractBeanDefinition}. + * @see AbstractBeanDefinition#setDependencyCheck + */ + public void setDependencyCheck(int dependencyCheck) { + this.dependencyCheck = dependencyCheck; + } + + /** + * Return the default dependency check code. + */ + public int getDependencyCheck() { + return this.dependencyCheck; + } + + /** + * Set the name of the default initializer method. + *

Note that this method is not enforced on all affected bean definitions + * but rather taken as an optional callback, to be invoked if actually present. + * @see AbstractBeanDefinition#setInitMethodName + * @see AbstractBeanDefinition#setEnforceInitMethod + */ + public void setInitMethodName(@Nullable String initMethodName) { + this.initMethodName = (StringUtils.hasText(initMethodName) ? initMethodName : null); + } + + /** + * Return the name of the default initializer method. + */ + @Nullable + public String getInitMethodName() { + return this.initMethodName; + } + + /** + * Set the name of the default destroy method. + *

Note that this method is not enforced on all affected bean definitions + * but rather taken as an optional callback, to be invoked if actually present. + * @see AbstractBeanDefinition#setDestroyMethodName + * @see AbstractBeanDefinition#setEnforceDestroyMethod + */ + public void setDestroyMethodName(@Nullable String destroyMethodName) { + this.destroyMethodName = (StringUtils.hasText(destroyMethodName) ? destroyMethodName : null); + } + + /** + * Return the name of the default destroy method. + */ + @Nullable + public String getDestroyMethodName() { + return this.destroyMethodName; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java new file mode 100644 index 0000000..b026877 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java @@ -0,0 +1,193 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Utility methods that are useful for bean definition reader implementations. + * Mainly intended for internal use. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1 + * @see PropertiesBeanDefinitionReader + * @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader + */ +public abstract class BeanDefinitionReaderUtils { + + /** + * Separator for generated bean names. If a class name or parent name is not + * unique, "#1", "#2" etc will be appended, until the name becomes unique. + */ + public static final String GENERATED_BEAN_NAME_SEPARATOR = BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR; + + + /** + * Create a new GenericBeanDefinition for the given parent name and class name, + * eagerly loading the bean class if a ClassLoader has been specified. + * @param parentName the name of the parent bean, if any + * @param className the name of the bean class, if any + * @param classLoader the ClassLoader to use for loading bean classes + * (can be {@code null} to just register bean classes by name) + * @return the bean definition + * @throws ClassNotFoundException if the bean class could not be loaded + */ + public static AbstractBeanDefinition createBeanDefinition( + @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { + + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setParentName(parentName); + if (className != null) { + if (classLoader != null) { + bd.setBeanClass(ClassUtils.forName(className, classLoader)); + } + else { + bd.setBeanClassName(className); + } + } + return bd; + } + + /** + * Generate a bean name for the given top-level bean definition, + * unique within the given bean factory. + * @param beanDefinition the bean definition to generate a bean name for + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition + * @see #generateBeanName(BeanDefinition, BeanDefinitionRegistry, boolean) + */ + public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + return generateBeanName(beanDefinition, registry, false); + } + + /** + * Generate a bean name for the given bean definition, unique within the + * given bean factory. + * @param definition the bean definition to generate a bean name for + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @param isInnerBean whether the given bean definition will be registered + * as inner bean or as top-level bean (allowing for special name generation + * for inner beans versus top-level beans) + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition + */ + public static String generateBeanName( + BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) + throws BeanDefinitionStoreException { + + String generatedBeanName = definition.getBeanClassName(); + if (generatedBeanName == null) { + if (definition.getParentName() != null) { + generatedBeanName = definition.getParentName() + "$child"; + } + else if (definition.getFactoryBeanName() != null) { + generatedBeanName = definition.getFactoryBeanName() + "$created"; + } + } + if (!StringUtils.hasText(generatedBeanName)) { + throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); + } + + if (isInnerBean) { + // Inner bean: generate identity hashcode suffix. + return generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); + } + + // Top-level bean: use plain class name with unique suffix if necessary. + return uniqueBeanName(generatedBeanName, registry); + } + + /** + * Turn the given bean name into a unique bean name for the given bean factory, + * appending a unique counter as suffix if necessary. + * @param beanName the original bean name + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @return the unique bean name to use + * @since 5.1 + */ + public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) { + String id = beanName; + int counter = -1; + + // Increase counter until the id is unique. + String prefix = beanName + GENERATED_BEAN_NAME_SEPARATOR; + while (counter == -1 || registry.containsBeanDefinition(id)) { + counter++; + id = prefix + counter; + } + return id; + } + + /** + * Register the given bean definition with the given bean factory. + * @param definitionHolder the bean definition including name and aliases + * @param registry the bean factory to register with + * @throws BeanDefinitionStoreException if registration failed + */ + public static void registerBeanDefinition( + BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + // Register bean definition under primary name. + String beanName = definitionHolder.getBeanName(); + registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); + + // Register aliases for bean name, if any. + String[] aliases = definitionHolder.getAliases(); + if (aliases != null) { + for (String alias : aliases) { + registry.registerAlias(beanName, alias); + } + } + } + + /** + * Register the given bean definition with a generated name, + * unique within the given bean factory. + * @param definition the bean definition to generate a bean name for + * @param registry the bean factory to register with + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition or the definition cannot be registered + */ + public static String registerWithGeneratedName( + AbstractBeanDefinition definition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + String generatedName = generateBeanName(definition, registry, false); + registry.registerBeanDefinition(generatedName, definition); + return generatedName; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java new file mode 100644 index 0000000..a1f47e5 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.AliasRegistry; + +/** + * Interface for registries that hold bean definitions, for example RootBeanDefinition + * and ChildBeanDefinition instances. Typically implemented by BeanFactories that + * internally work with the AbstractBeanDefinition hierarchy. + * + *

This is the only interface in Spring's bean factory packages that encapsulates + * registration of bean definitions. The standard BeanFactory interfaces + * only cover access to a fully configured factory instance. + * + *

Spring's bean definition readers expect to work on an implementation of this + * interface. Known implementors within the Spring core are DefaultListableBeanFactory + * and GenericApplicationContext. + * + * @author Juergen Hoeller + * @since 26.11.2003 + * @see org.springframework.beans.factory.config.BeanDefinition + * @see AbstractBeanDefinition + * @see RootBeanDefinition + * @see ChildBeanDefinition + * @see DefaultListableBeanFactory + * @see org.springframework.context.support.GenericApplicationContext + * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + * @see PropertiesBeanDefinitionReader + */ +public interface BeanDefinitionRegistry extends AliasRegistry { + + /** + * Register a new bean definition with this registry. + * Must support RootBeanDefinition and ChildBeanDefinition. + * @param beanName the name of the bean instance to register + * @param beanDefinition definition of the bean instance to register + * @throws BeanDefinitionStoreException if the BeanDefinition is invalid + * @throws BeanDefinitionOverrideException if there is already a BeanDefinition + * for the specified bean name and we are not allowed to override it + * @see GenericBeanDefinition + * @see RootBeanDefinition + * @see ChildBeanDefinition + */ + void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException; + + /** + * Remove the BeanDefinition for the given name. + * @param beanName the name of the bean instance to register + * @throws NoSuchBeanDefinitionException if there is no such bean definition + */ + void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Return the BeanDefinition for the given bean name. + * @param beanName name of the bean to find a definition for + * @return the BeanDefinition for the given name (never {@code null}) + * @throws NoSuchBeanDefinitionException if there is no such bean definition + */ + BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; + + /** + * Check if this registry contains a bean definition with the given name. + * @param beanName the name of the bean to look for + * @return if this registry contains a bean definition with the given name + */ + boolean containsBeanDefinition(String beanName); + + /** + * Return the names of all beans defined in this registry. + * @return the names of all beans defined in this registry, + * or an empty array if none defined + */ + String[] getBeanDefinitionNames(); + + /** + * Return the number of beans defined in the registry. + * @return the number of beans defined in the registry + */ + int getBeanDefinitionCount(); + + /** + * Determine whether the given bean name is already in use within this registry, + * i.e. whether there is a local bean or alias registered under this name. + * @param beanName the name to check + * @return whether the given bean name is already in use + */ + boolean isBeanNameInUse(String beanName); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistryPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistryPostProcessor.java new file mode 100644 index 0000000..b94f1ab --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistryPostProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2010 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.beans.factory.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; + +/** + * Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for + * the registration of further bean definitions before regular + * BeanFactoryPostProcessor detection kicks in. In particular, + * BeanDefinitionRegistryPostProcessor may register further bean definitions + * which in turn define BeanFactoryPostProcessor instances. + * + * @author Juergen Hoeller + * @since 3.0.1 + * @see org.springframework.context.annotation.ConfigurationClassPostProcessor + */ +public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { + + /** + * Modify the application context's internal bean definition registry after its + * standard initialization. All regular bean definitions will have been loaded, + * but no beans will have been instantiated yet. This allows for adding further + * bean definitions before the next post-processing phase kicks in. + * @param registry the bean definition registry used by the application context + * @throws org.springframework.beans.BeansException in case of errors + */ + void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java new file mode 100644 index 0000000..3a243bf --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionResource.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.io.AbstractResource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Descriptive {@link org.springframework.core.io.Resource} wrapper for + * a {@link org.springframework.beans.factory.config.BeanDefinition}. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.springframework.core.io.DescriptiveResource + */ +class BeanDefinitionResource extends AbstractResource { + + private final BeanDefinition beanDefinition; + + + /** + * Create a new BeanDefinitionResource. + * @param beanDefinition the BeanDefinition object to wrap + */ + public BeanDefinitionResource(BeanDefinition beanDefinition) { + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + this.beanDefinition = beanDefinition; + } + + /** + * Return the wrapped BeanDefinition object. + */ + public final BeanDefinition getBeanDefinition() { + return this.beanDefinition; + } + + + @Override + public boolean exists() { + return false; + } + + @Override + public boolean isReadable() { + return false; + } + + @Override + public InputStream getInputStream() throws IOException { + throw new FileNotFoundException( + "Resource cannot be opened because it points to " + getDescription()); + } + + @Override + public String getDescription() { + return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription(); + } + + + /** + * This implementation compares the underlying BeanDefinition. + */ + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof BeanDefinitionResource && + ((BeanDefinitionResource) other).beanDefinition.equals(this.beanDefinition))); + } + + /** + * This implementation returns the hash code of the underlying BeanDefinition. + */ + @Override + public int hashCode() { + return this.beanDefinition.hashCode(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java new file mode 100644 index 0000000..88e0458 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValidationException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2012 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.beans.factory.support; + +import org.springframework.beans.FatalBeanException; + +/** + * Exception thrown when the validation of a bean definition failed. + * + * @author Juergen Hoeller + * @since 21.11.2003 + * @see AbstractBeanDefinition#validate() + */ +@SuppressWarnings("serial") +public class BeanDefinitionValidationException extends FatalBeanException { + + /** + * Create a new BeanDefinitionValidationException with the specified message. + * @param msg the detail message + */ + public BeanDefinitionValidationException(String msg) { + super(msg); + } + + /** + * Create a new BeanDefinitionValidationException with the specified message + * and root cause. + * @param msg the detail message + * @param cause the root cause + */ + public BeanDefinitionValidationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java new file mode 100644 index 0000000..d7d3c9b --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2007 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.beans.factory.support; + +import org.springframework.beans.factory.config.BeanDefinition; + +/** + * Strategy interface for generating bean names for bean definitions. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +public interface BeanNameGenerator { + + /** + * Generate a bean name for the given bean definition. + * @param definition the bean definition to generate a name for + * @param registry the bean definition registry that the given definition + * is supposed to be registered with + * @return the generated bean name + */ + String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry); + +} 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 new file mode 100644 index 0000000..698a04d --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -0,0 +1,277 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy; +import org.springframework.cglib.core.SpringNamingPolicy; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.CallbackFilter; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.NoOp; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Default object instantiation strategy for use in BeanFactories. + * + *

Uses CGLIB to generate subclasses dynamically if methods need to be + * overridden by the container to implement Method Injection. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 1.1 + */ +public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { + + /** + * Index in the CGLIB callback array for passthrough behavior, + * in which case the subclass won't override the original class. + */ + private static final int PASSTHROUGH = 0; + + /** + * Index in the CGLIB callback array for a method that should + * be overridden to provide method lookup. + */ + private static final int LOOKUP_OVERRIDE = 1; + + /** + * Index in the CGLIB callback array for a method that should + * be overridden using generic method replacer functionality. + */ + private static final int METHOD_REPLACER = 2; + + + @Override + protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { + return instantiateWithMethodInjection(bd, beanName, owner, null); + } + + @Override + 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); + } + + + /** + * An inner class created for historical reasons to avoid external CGLIB dependency + * in Spring versions earlier than 3.2. + */ + private static class CglibSubclassCreator { + + private static final Class[] CALLBACK_TYPES = new Class[] + {NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class}; + + private final RootBeanDefinition beanDefinition; + + private final BeanFactory owner; + + CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { + this.beanDefinition = beanDefinition; + this.owner = owner; + } + + /** + * Create a new instance of a dynamically generated subclass implementing the + * required lookups. + * @param ctor constructor to use. If this is {@code null}, use the + * no-arg constructor (no parameterization, or Setter Injection) + * @param args arguments to use for the constructor. + * Ignored if the {@code ctor} parameter is {@code null}. + * @return new instance of the dynamically generated subclass + */ + public Object instantiate(@Nullable Constructor ctor, Object... args) { + Class subclass = createEnhancedSubclass(this.beanDefinition); + Object instance; + if (ctor == null) { + instance = BeanUtils.instantiateClass(subclass); + } + else { + try { + Constructor enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes()); + instance = enhancedSubclassConstructor.newInstance(args); + } + catch (Exception ex) { + throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), + "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex); + } + } + // SPR-10785: set callbacks directly on the instance instead of in the + // enhanced class (via the Enhancer) in order to avoid memory leaks. + Factory factory = (Factory) instance; + factory.setCallbacks(new Callback[] {NoOp.INSTANCE, + new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), + new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); + return instance; + } + + /** + * Create an enhanced subclass of the bean class for the provided bean + * definition, using CGLIB. + */ + private Class createEnhancedSubclass(RootBeanDefinition beanDefinition) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(beanDefinition.getBeanClass()); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + if (this.owner instanceof ConfigurableBeanFactory) { + ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader(); + enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); + } + enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition)); + enhancer.setCallbackTypes(CALLBACK_TYPES); + return enhancer.createClass(); + } + } + + + /** + * Class providing hashCode and equals methods required by CGLIB to + * ensure that CGLIB doesn't generate a distinct class per bean. + * Identity is based on class and bean definition. + */ + private static class CglibIdentitySupport { + + private final RootBeanDefinition beanDefinition; + + public CglibIdentitySupport(RootBeanDefinition beanDefinition) { + this.beanDefinition = beanDefinition; + } + + public RootBeanDefinition getBeanDefinition() { + return this.beanDefinition; + } + + @Override + public boolean equals(@Nullable Object other) { + return (other != null && getClass() == other.getClass() && + this.beanDefinition.equals(((CglibIdentitySupport) other).beanDefinition)); + } + + @Override + public int hashCode() { + return this.beanDefinition.hashCode(); + } + } + + + /** + * CGLIB callback for filtering method interception behavior. + */ + private static class MethodOverrideCallbackFilter extends CglibIdentitySupport implements CallbackFilter { + + private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class); + + public MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) { + super(beanDefinition); + } + + @Override + public int accept(Method method) { + MethodOverride methodOverride = getBeanDefinition().getMethodOverrides().getOverride(method); + if (logger.isTraceEnabled()) { + logger.trace("MethodOverride for " + method + ": " + methodOverride); + } + if (methodOverride == null) { + return PASSTHROUGH; + } + else if (methodOverride instanceof LookupOverride) { + return LOOKUP_OVERRIDE; + } + else if (methodOverride instanceof ReplaceOverride) { + return METHOD_REPLACER; + } + throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " + + methodOverride.getClass().getName()); + } + } + + + /** + * CGLIB MethodInterceptor to override methods, replacing them with an + * implementation that returns a bean looked up in the container. + */ + private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { + + private final BeanFactory owner; + + public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { + super(beanDefinition); + this.owner = owner; + } + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { + // Cast is safe, as CallbackFilter filters are used selectively. + LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); + Assert.state(lo != null, "LookupOverride not found"); + Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all + if (StringUtils.hasText(lo.getBeanName())) { + Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : + this.owner.getBean(lo.getBeanName())); + // Detect package-protected NullBean instance through equals(null) check + return (bean.equals(null) ? null : bean); + } + else { + return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : + this.owner.getBean(method.getReturnType())); + } + } + } + + + /** + * CGLIB MethodInterceptor to override methods, replacing them with a call + * to a generic MethodReplacer. + */ + private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { + + private final BeanFactory owner; + + public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { + super(beanDefinition); + this.owner = owner; + } + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { + ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method); + Assert.state(ro != null, "ReplaceOverride not found"); + // TODO could cache if a singleton for minor performance optimization + MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class); + return mr.reimplement(obj, method, args); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java new file mode 100644 index 0000000..7647f11 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -0,0 +1,1009 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.beans.ConstructorProperties; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.InjectionPoint; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.CollectionFactory; +import org.springframework.core.MethodParameter; +import org.springframework.core.NamedThreadLocal; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.MethodInvoker; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Delegate for resolving constructors and factory methods. + * + *

Performs constructor resolution through argument matching. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Mark Fisher + * @author Costin Leau + * @author Sebastien Deleuze + * @author Sam Brannen + * @since 2.0 + * @see #autowireConstructor + * @see #instantiateUsingFactoryMethod + * @see AbstractAutowireCapableBeanFactory + */ +class ConstructorResolver { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + /** + * Marker for autowired arguments in a cached argument array, to be replaced + * by a {@linkplain #resolveAutowiredArgument resolved autowired argument}. + */ + private static final Object autowiredArgumentMarker = new Object(); + + private static final NamedThreadLocal currentInjectionPoint = + new NamedThreadLocal<>("Current injection point"); + + + private final AbstractAutowireCapableBeanFactory beanFactory; + + private final Log logger; + + + /** + * Create a new ConstructorResolver for the given factory and instantiation strategy. + * @param beanFactory the BeanFactory to work with + */ + public ConstructorResolver(AbstractAutowireCapableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + this.logger = beanFactory.getLogger(); + } + + + /** + * "autowire constructor" (with constructor arguments by type) behavior. + * Also applied if explicit constructor argument values are specified, + * matching all remaining arguments with beans from the bean factory. + *

This corresponds to constructor injection: In this mode, a Spring + * bean factory is able to host components that expect constructor-based + * dependency resolution. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param chosenCtors chosen candidate constructors (or {@code null} if none) + * @param explicitArgs argument values passed in programmatically via the getBean method, + * or {@code null} if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + */ + public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, + @Nullable Constructor[] chosenCtors, @Nullable Object[] explicitArgs) { + + BeanWrapperImpl bw = new BeanWrapperImpl(); + this.beanFactory.initBeanWrapper(bw); + + Constructor constructorToUse = null; + ArgumentsHolder argsHolderToUse = null; + Object[] argsToUse = null; + + if (explicitArgs != null) { + argsToUse = explicitArgs; + } + else { + Object[] argsToResolve = null; + synchronized (mbd.constructorArgumentLock) { + constructorToUse = (Constructor) mbd.resolvedConstructorOrFactoryMethod; + if (constructorToUse != null && mbd.constructorArgumentsResolved) { + // Found a cached constructor... + argsToUse = mbd.resolvedConstructorArguments; + if (argsToUse == null) { + argsToResolve = mbd.preparedConstructorArguments; + } + } + } + if (argsToResolve != null) { + argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); + } + } + + if (constructorToUse == null || argsToUse == null) { + // Take specified constructors, if any. + Constructor[] candidates = chosenCtors; + if (candidates == null) { + Class beanClass = mbd.getBeanClass(); + try { + candidates = (mbd.isNonPublicAccessAllowed() ? + beanClass.getDeclaredConstructors() : beanClass.getConstructors()); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Resolution of declared constructors on bean Class [" + beanClass.getName() + + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); + } + } + + if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) { + Constructor uniqueCandidate = candidates[0]; + if (uniqueCandidate.getParameterCount() == 0) { + synchronized (mbd.constructorArgumentLock) { + mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate; + mbd.constructorArgumentsResolved = true; + mbd.resolvedConstructorArguments = EMPTY_ARGS; + } + bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS)); + return bw; + } + } + + // Need to resolve the constructor. + boolean autowiring = (chosenCtors != null || + mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); + ConstructorArgumentValues resolvedValues = null; + + int minNrOfArgs; + if (explicitArgs != null) { + minNrOfArgs = explicitArgs.length; + } + else { + ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); + resolvedValues = new ConstructorArgumentValues(); + minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); + } + + AutowireUtils.sortConstructors(candidates); + int minTypeDiffWeight = Integer.MAX_VALUE; + Set> ambiguousConstructors = null; + Deque causes = null; + + for (Constructor candidate : candidates) { + int parameterCount = candidate.getParameterCount(); + + if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) { + // Already found greedy constructor that can be satisfied -> + // do not look any further, there are only less greedy constructors left. + break; + } + if (parameterCount < minNrOfArgs) { + continue; + } + + ArgumentsHolder argsHolder; + Class[] paramTypes = candidate.getParameterTypes(); + if (resolvedValues != null) { + try { + String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); + if (paramNames == null) { + ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(candidate); + } + } + argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, + getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1); + } + catch (UnsatisfiedDependencyException ex) { + if (logger.isTraceEnabled()) { + logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex); + } + // Swallow and try next constructor. + if (causes == null) { + causes = new ArrayDeque<>(1); + } + causes.add(ex); + continue; + } + } + else { + // Explicit arguments given -> arguments length must match exactly. + if (parameterCount != explicitArgs.length) { + continue; + } + argsHolder = new ArgumentsHolder(explicitArgs); + } + + int typeDiffWeight = (mbd.isLenientConstructorResolution() ? + argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); + // Choose this constructor if it represents the closest match. + if (typeDiffWeight < minTypeDiffWeight) { + constructorToUse = candidate; + argsHolderToUse = argsHolder; + argsToUse = argsHolder.arguments; + minTypeDiffWeight = typeDiffWeight; + ambiguousConstructors = null; + } + else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { + if (ambiguousConstructors == null) { + ambiguousConstructors = new LinkedHashSet<>(); + ambiguousConstructors.add(constructorToUse); + } + ambiguousConstructors.add(candidate); + } + } + + if (constructorToUse == null) { + if (causes != null) { + UnsatisfiedDependencyException ex = causes.removeLast(); + for (Exception cause : causes) { + this.beanFactory.onSuppressedException(cause); + } + throw ex; + } + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Could not resolve matching constructor " + + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)"); + } + else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Ambiguous constructor matches found in bean '" + beanName + "' " + + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + + ambiguousConstructors); + } + + if (explicitArgs == null && argsHolderToUse != null) { + argsHolderToUse.storeCache(mbd, constructorToUse); + } + } + + Assert.state(argsToUse != null, "Unresolved constructor arguments"); + bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse)); + return bw; + } + + private Object instantiate( + String beanName, RootBeanDefinition mbd, Constructor constructorToUse, Object[] argsToUse) { + + try { + InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy(); + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedAction) () -> + strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse), + this.beanFactory.getAccessControlContext()); + } + else { + return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse); + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean instantiation via constructor failed", ex); + } + } + + /** + * Resolve the factory method in the specified bean definition, if possible. + * {@link RootBeanDefinition#getResolvedFactoryMethod()} can be checked for the result. + * @param mbd the bean definition to check + */ + public void resolveFactoryMethodIfPossible(RootBeanDefinition mbd) { + Class factoryClass; + boolean isStatic; + if (mbd.getFactoryBeanName() != null) { + factoryClass = this.beanFactory.getType(mbd.getFactoryBeanName()); + isStatic = false; + } + else { + factoryClass = mbd.getBeanClass(); + isStatic = true; + } + Assert.state(factoryClass != null, "Unresolvable factory class"); + factoryClass = ClassUtils.getUserClass(factoryClass); + + Method[] candidates = getCandidateMethods(factoryClass, mbd); + Method uniqueCandidate = null; + for (Method candidate : candidates) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) { + if (uniqueCandidate == null) { + uniqueCandidate = candidate; + } + else if (isParamMismatch(uniqueCandidate, candidate)) { + uniqueCandidate = null; + break; + } + } + } + mbd.factoryMethodToIntrospect = uniqueCandidate; + } + + private boolean isParamMismatch(Method uniqueCandidate, Method candidate) { + int uniqueCandidateParameterCount = uniqueCandidate.getParameterCount(); + int candidateParameterCount = candidate.getParameterCount(); + return (uniqueCandidateParameterCount != candidateParameterCount || + !Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParameterTypes())); + } + + /** + * Retrieve all candidate methods for the given class, considering + * the {@link RootBeanDefinition#isNonPublicAccessAllowed()} flag. + * Called as the starting point for factory method determination. + */ + private Method[] getCandidateMethods(Class factoryClass, RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedAction) () -> + (mbd.isNonPublicAccessAllowed() ? + ReflectionUtils.getAllDeclaredMethods(factoryClass) : factoryClass.getMethods())); + } + else { + return (mbd.isNonPublicAccessAllowed() ? + ReflectionUtils.getAllDeclaredMethods(factoryClass) : factoryClass.getMethods()); + } + } + + /** + * Instantiate the bean using a named factory method. The method may be static, if the + * bean definition parameter specifies a class, rather than a "factory-bean", or + * an instance variable on a factory object itself configured using Dependency Injection. + *

Implementation requires iterating over the static or instance methods with the + * name specified in the RootBeanDefinition (the method may be overloaded) and trying + * to match with the parameters. We don't have the types attached to constructor args, + * so trial and error is the only way to go here. The explicitArgs array may contain + * argument values passed in programmatically via the corresponding getBean method. + * @param beanName the name of the bean + * @param mbd the merged bean definition for the bean + * @param explicitArgs argument values passed in programmatically via the getBean + * method, or {@code null} if none (-> use constructor argument values from bean definition) + * @return a BeanWrapper for the new instance + */ + public BeanWrapper instantiateUsingFactoryMethod( + String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) { + + BeanWrapperImpl bw = new BeanWrapperImpl(); + this.beanFactory.initBeanWrapper(bw); + + Object factoryBean; + Class factoryClass; + boolean isStatic; + + String factoryBeanName = mbd.getFactoryBeanName(); + if (factoryBeanName != null) { + if (factoryBeanName.equals(beanName)) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "factory-bean reference points back to the same bean definition"); + } + factoryBean = this.beanFactory.getBean(factoryBeanName); + if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) { + throw new ImplicitlyAppearedSingletonException(); + } + this.beanFactory.registerDependentBean(factoryBeanName, beanName); + factoryClass = factoryBean.getClass(); + isStatic = false; + } + else { + // It's a static factory method on the bean class. + if (!mbd.hasBeanClass()) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "bean definition declares neither a bean class nor a factory-bean reference"); + } + factoryBean = null; + factoryClass = mbd.getBeanClass(); + isStatic = true; + } + + Method factoryMethodToUse = null; + ArgumentsHolder argsHolderToUse = null; + Object[] argsToUse = null; + + if (explicitArgs != null) { + argsToUse = explicitArgs; + } + else { + Object[] argsToResolve = null; + synchronized (mbd.constructorArgumentLock) { + factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod; + if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) { + // Found a cached factory method... + argsToUse = mbd.resolvedConstructorArguments; + if (argsToUse == null) { + argsToResolve = mbd.preparedConstructorArguments; + } + } + } + if (argsToResolve != null) { + argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve); + } + } + + if (factoryMethodToUse == null || argsToUse == null) { + // Need to determine the factory method... + // Try all methods with this name to see if they match the given arguments. + factoryClass = ClassUtils.getUserClass(factoryClass); + + List candidates = null; + if (mbd.isFactoryMethodUnique) { + if (factoryMethodToUse == null) { + factoryMethodToUse = mbd.getResolvedFactoryMethod(); + } + if (factoryMethodToUse != null) { + candidates = Collections.singletonList(factoryMethodToUse); + } + } + if (candidates == null) { + candidates = new ArrayList<>(); + Method[] rawCandidates = getCandidateMethods(factoryClass, mbd); + for (Method candidate : rawCandidates) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) { + candidates.add(candidate); + } + } + } + + if (candidates.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) { + Method uniqueCandidate = candidates.get(0); + if (uniqueCandidate.getParameterCount() == 0) { + mbd.factoryMethodToIntrospect = uniqueCandidate; + synchronized (mbd.constructorArgumentLock) { + mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate; + mbd.constructorArgumentsResolved = true; + mbd.resolvedConstructorArguments = EMPTY_ARGS; + } + bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS)); + return bw; + } + } + + if (candidates.size() > 1) { // explicitly skip immutable singletonList + candidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR); + } + + ConstructorArgumentValues resolvedValues = null; + boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); + int minTypeDiffWeight = Integer.MAX_VALUE; + Set ambiguousFactoryMethods = null; + + int minNrOfArgs; + if (explicitArgs != null) { + minNrOfArgs = explicitArgs.length; + } + else { + // We don't have arguments passed in programmatically, so we need to resolve the + // arguments specified in the constructor arguments held in the bean definition. + if (mbd.hasConstructorArgumentValues()) { + ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); + resolvedValues = new ConstructorArgumentValues(); + minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); + } + else { + minNrOfArgs = 0; + } + } + + Deque causes = null; + + for (Method candidate : candidates) { + int parameterCount = candidate.getParameterCount(); + + if (parameterCount >= minNrOfArgs) { + ArgumentsHolder argsHolder; + + Class[] paramTypes = candidate.getParameterTypes(); + if (explicitArgs != null) { + // Explicit arguments given -> arguments length must match exactly. + if (paramTypes.length != explicitArgs.length) { + continue; + } + argsHolder = new ArgumentsHolder(explicitArgs); + } + else { + // Resolved constructor arguments: type conversion and/or autowiring necessary. + try { + String[] paramNames = null; + ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(candidate); + } + argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, + paramTypes, paramNames, candidate, autowiring, candidates.size() == 1); + } + catch (UnsatisfiedDependencyException ex) { + if (logger.isTraceEnabled()) { + logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex); + } + // Swallow and try next overloaded factory method. + if (causes == null) { + causes = new ArrayDeque<>(1); + } + causes.add(ex); + continue; + } + } + + int typeDiffWeight = (mbd.isLenientConstructorResolution() ? + argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); + // Choose this factory method if it represents the closest match. + if (typeDiffWeight < minTypeDiffWeight) { + factoryMethodToUse = candidate; + argsHolderToUse = argsHolder; + argsToUse = argsHolder.arguments; + minTypeDiffWeight = typeDiffWeight; + ambiguousFactoryMethods = null; + } + // Find out about ambiguity: In case of the same type difference weight + // for methods with the same number of parameters, collect such candidates + // and eventually raise an ambiguity exception. + // However, only perform that check in non-lenient constructor resolution mode, + // and explicitly ignore overridden methods (with the same parameter signature). + else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && + !mbd.isLenientConstructorResolution() && + paramTypes.length == factoryMethodToUse.getParameterCount() && + !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { + if (ambiguousFactoryMethods == null) { + ambiguousFactoryMethods = new LinkedHashSet<>(); + ambiguousFactoryMethods.add(factoryMethodToUse); + } + ambiguousFactoryMethods.add(candidate); + } + } + } + + if (factoryMethodToUse == null || argsToUse == null) { + if (causes != null) { + UnsatisfiedDependencyException ex = causes.removeLast(); + for (Exception cause : causes) { + this.beanFactory.onSuppressedException(cause); + } + throw ex; + } + List argTypes = new ArrayList<>(minNrOfArgs); + if (explicitArgs != null) { + for (Object arg : explicitArgs) { + argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null"); + } + } + else if (resolvedValues != null) { + Set valueHolders = new LinkedHashSet<>(resolvedValues.getArgumentCount()); + valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values()); + valueHolders.addAll(resolvedValues.getGenericArgumentValues()); + for (ValueHolder value : valueHolders) { + String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) : + (value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null")); + argTypes.add(argType); + } + } + String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "No matching factory method found: " + + (mbd.getFactoryBeanName() != null ? + "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + + "factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " + + "Check that a method with the specified name " + + (minNrOfArgs > 0 ? "and arguments " : "") + + "exists and that it is " + + (isStatic ? "static" : "non-static") + "."); + } + else if (void.class == factoryMethodToUse.getReturnType()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Invalid factory method '" + mbd.getFactoryMethodName() + + "': needs to have a non-void return type!"); + } + else if (ambiguousFactoryMethods != null) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Ambiguous factory method matches found in bean '" + beanName + "' " + + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + + ambiguousFactoryMethods); + } + + if (explicitArgs == null && argsHolderToUse != null) { + mbd.factoryMethodToIntrospect = factoryMethodToUse; + argsHolderToUse.storeCache(mbd, factoryMethodToUse); + } + } + + bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse)); + return bw; + } + + private Object instantiate(String beanName, RootBeanDefinition mbd, + @Nullable Object factoryBean, Method factoryMethod, Object[] args) { + + try { + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedAction) () -> + this.beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args), + this.beanFactory.getAccessControlContext()); + } + else { + return this.beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args); + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean instantiation via factory method failed", ex); + } + } + + /** + * Resolve the constructor arguments for this bean into the resolvedValues object. + * This may involve looking up other beans. + *

This method is also used for handling invocations of static factory methods. + */ + private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, + ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { + + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + + int minNrOfArgs = cargs.getArgumentCount(); + + for (Map.Entry entry : cargs.getIndexedArgumentValues().entrySet()) { + int index = entry.getKey(); + if (index < 0) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Invalid constructor argument index: " + index); + } + if (index + 1 > minNrOfArgs) { + minNrOfArgs = index + 1; + } + ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue(); + if (valueHolder.isConverted()) { + resolvedValues.addIndexedArgumentValue(index, valueHolder); + } + else { + Object resolvedValue = + valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); + ConstructorArgumentValues.ValueHolder resolvedValueHolder = + new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); + resolvedValueHolder.setSource(valueHolder); + resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); + } + } + + for (ConstructorArgumentValues.ValueHolder valueHolder : cargs.getGenericArgumentValues()) { + if (valueHolder.isConverted()) { + resolvedValues.addGenericArgumentValue(valueHolder); + } + else { + Object resolvedValue = + valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); + ConstructorArgumentValues.ValueHolder resolvedValueHolder = new ConstructorArgumentValues.ValueHolder( + resolvedValue, valueHolder.getType(), valueHolder.getName()); + resolvedValueHolder.setSource(valueHolder); + resolvedValues.addGenericArgumentValue(resolvedValueHolder); + } + } + + return minNrOfArgs; + } + + /** + * Create an array of arguments to invoke a constructor or factory method, + * given the resolved constructor argument values. + */ + private ArgumentsHolder createArgumentArray( + String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues, + BeanWrapper bw, Class[] paramTypes, @Nullable String[] paramNames, Executable executable, + boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException { + + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); + + ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); + Set usedValueHolders = new HashSet<>(paramTypes.length); + Set autowiredBeanNames = new LinkedHashSet<>(4); + + for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { + Class paramType = paramTypes[paramIndex]; + String paramName = (paramNames != null ? paramNames[paramIndex] : ""); + // Try to find matching constructor argument value, either indexed or generic. + ConstructorArgumentValues.ValueHolder valueHolder = null; + if (resolvedValues != null) { + valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders); + // If we couldn't find a direct match and are not supposed to autowire, + // let's try the next generic, untyped argument value as fallback: + // it could match after type conversion (for example, String -> int). + if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) { + valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders); + } + } + if (valueHolder != null) { + // We found a potential match - let's give it a try. + // Do not consider the same value definition multiple times! + usedValueHolders.add(valueHolder); + Object originalValue = valueHolder.getValue(); + Object convertedValue; + if (valueHolder.isConverted()) { + convertedValue = valueHolder.getConvertedValue(); + args.preparedArguments[paramIndex] = convertedValue; + } + else { + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); + try { + convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); + } + catch (TypeMismatchException ex) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Could not convert argument value of type [" + + ObjectUtils.nullSafeClassName(valueHolder.getValue()) + + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); + } + Object sourceHolder = valueHolder.getSource(); + if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) { + Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue(); + args.resolveNecessary = true; + args.preparedArguments[paramIndex] = sourceValue; + } + } + args.arguments[paramIndex] = convertedValue; + args.rawArguments[paramIndex] = originalValue; + } + else { + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); + // No explicit match found: we're either supposed to autowire or + // have to fail creating an argument array for the given constructor. + if (!autowiring) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Ambiguous argument values for parameter of type [" + paramType.getName() + + "] - did you specify the correct bean references as arguments?"); + } + try { + Object autowiredArgument = resolveAutowiredArgument( + methodParam, beanName, autowiredBeanNames, converter, fallback); + args.rawArguments[paramIndex] = autowiredArgument; + args.arguments[paramIndex] = autowiredArgument; + args.preparedArguments[paramIndex] = autowiredArgumentMarker; + args.resolveNecessary = true; + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex); + } + } + } + + for (String autowiredBeanName : autowiredBeanNames) { + this.beanFactory.registerDependentBean(autowiredBeanName, beanName); + if (logger.isDebugEnabled()) { + logger.debug("Autowiring by type from bean name '" + beanName + + "' via " + (executable instanceof Constructor ? "constructor" : "factory method") + + " to bean named '" + autowiredBeanName + "'"); + } + } + + return args; + } + + /** + * Resolve the prepared arguments stored in the given bean definition. + */ + private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, + Executable executable, Object[] argsToResolve) { + + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + Class[] paramTypes = executable.getParameterTypes(); + + Object[] resolvedArgs = new Object[argsToResolve.length]; + for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { + Object argValue = argsToResolve[argIndex]; + MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); + if (argValue == autowiredArgumentMarker) { + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true); + } + else if (argValue instanceof BeanMetadataElement) { + argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); + } + else if (argValue instanceof String) { + argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd); + } + Class paramType = paramTypes[argIndex]; + try { + resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam); + } + catch (TypeMismatchException ex) { + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) + + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); + } + } + return resolvedArgs; + } + + protected Constructor getUserDeclaredConstructor(Constructor constructor) { + Class declaringClass = constructor.getDeclaringClass(); + Class userClass = ClassUtils.getUserClass(declaringClass); + if (userClass != declaringClass) { + try { + return userClass.getDeclaredConstructor(constructor.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + // No equivalent constructor on user class (superclass)... + // Let's proceed with the given constructor as we usually would. + } + } + return constructor; + } + + /** + * Template method for resolving the specified argument which is supposed to be autowired. + */ + @Nullable + protected Object resolveAutowiredArgument(MethodParameter param, String beanName, + @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { + + Class paramType = param.getParameterType(); + if (InjectionPoint.class.isAssignableFrom(paramType)) { + InjectionPoint injectionPoint = currentInjectionPoint.get(); + if (injectionPoint == null) { + throw new IllegalStateException("No current InjectionPoint available for " + param); + } + return injectionPoint; + } + try { + return this.beanFactory.resolveDependency( + new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); + } + catch (NoUniqueBeanDefinitionException ex) { + throw ex; + } + catch (NoSuchBeanDefinitionException ex) { + if (fallback) { + // Single constructor or factory method -> let's return an empty array/collection + // for e.g. a vararg or a non-null List/Set/Map parameter. + if (paramType.isArray()) { + return Array.newInstance(paramType.getComponentType(), 0); + } + else if (CollectionFactory.isApproximableCollectionType(paramType)) { + return CollectionFactory.createCollection(paramType, 0); + } + else if (CollectionFactory.isApproximableMapType(paramType)) { + return CollectionFactory.createMap(paramType, 0); + } + } + throw ex; + } + } + + static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) { + InjectionPoint old = currentInjectionPoint.get(); + if (injectionPoint != null) { + currentInjectionPoint.set(injectionPoint); + } + else { + currentInjectionPoint.remove(); + } + return old; + } + + + /** + * Private inner class for holding argument combinations. + */ + private static class ArgumentsHolder { + + public final Object[] rawArguments; + + public final Object[] arguments; + + public final Object[] preparedArguments; + + public boolean resolveNecessary = false; + + public ArgumentsHolder(int size) { + this.rawArguments = new Object[size]; + this.arguments = new Object[size]; + this.preparedArguments = new Object[size]; + } + + public ArgumentsHolder(Object[] args) { + this.rawArguments = args; + this.arguments = args; + this.preparedArguments = args; + } + + public int getTypeDifferenceWeight(Class[] paramTypes) { + // If valid arguments found, determine type difference weight. + // Try type difference weight on both the converted arguments and + // the raw arguments. If the raw weight is better, use it. + // Decrease raw weight by 1024 to prefer it over equal converted weight. + int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments); + int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024; + return Math.min(rawTypeDiffWeight, typeDiffWeight); + } + + public int getAssignabilityWeight(Class[] paramTypes) { + for (int i = 0; i < paramTypes.length; i++) { + if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { + return Integer.MAX_VALUE; + } + } + for (int i = 0; i < paramTypes.length; i++) { + if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) { + return Integer.MAX_VALUE - 512; + } + } + return Integer.MAX_VALUE - 1024; + } + + public void storeCache(RootBeanDefinition mbd, Executable constructorOrFactoryMethod) { + synchronized (mbd.constructorArgumentLock) { + mbd.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod; + mbd.constructorArgumentsResolved = true; + if (this.resolveNecessary) { + mbd.preparedConstructorArguments = this.preparedArguments; + } + else { + mbd.resolvedConstructorArguments = this.arguments; + } + } + } + } + + + /** + * Delegate for checking Java 6's {@link ConstructorProperties} annotation. + */ + private static class ConstructorPropertiesChecker { + + @Nullable + public static String[] evaluate(Constructor candidate, int paramCount) { + ConstructorProperties cp = candidate.getAnnotation(ConstructorProperties.class); + if (cp != null) { + String[] names = cp.value(); + if (names.length != paramCount) { + throw new IllegalStateException("Constructor annotated with @ConstructorProperties but not " + + "corresponding to actual number of parameters (" + paramCount + "): " + candidate); + } + return names; + } + else { + return null; + } + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java new file mode 100644 index 0000000..9632f5a --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultBeanNameGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2019 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.beans.factory.support; + +import org.springframework.beans.factory.config.BeanDefinition; + +/** + * Default implementation of the {@link BeanNameGenerator} interface, delegating to + * {@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +public class DefaultBeanNameGenerator implements BeanNameGenerator { + + /** + * A convenient constant for a default {@code DefaultBeanNameGenerator} instance, + * as used for {@link AbstractBeanDefinitionReader} setup. + * @since 5.2 + */ + public static final DefaultBeanNameGenerator INSTANCE = new DefaultBeanNameGenerator(); + + + @Override + public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { + return BeanDefinitionReaderUtils.generateBeanName(definition, registry); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java new file mode 100644 index 0000000..dc79d3c --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -0,0 +1,2163 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.inject.Provider; + +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InjectionPoint; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.NamedBeanHolder; +import org.springframework.core.OrderComparator; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.core.log.LogMessage; +import org.springframework.core.metrics.StartupStep; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.CompositeIterator; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Spring's default implementation of the {@link ConfigurableListableBeanFactory} + * and {@link BeanDefinitionRegistry} interfaces: a full-fledged bean factory + * based on bean definition metadata, extensible through post-processors. + * + *

Typical usage is registering all bean definitions first (possibly read + * from a bean definition file), before accessing beans. Bean lookup by name + * is therefore an inexpensive operation in a local bean definition table, + * operating on pre-resolved bean definition metadata objects. + * + *

Note that readers for specific bean definition formats are typically + * implemented separately rather than as bean factory subclasses: see for example + * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. + * + *

For an alternative implementation of the + * {@link org.springframework.beans.factory.ListableBeanFactory} interface, + * have a look at {@link StaticListableBeanFactory}, which manages existing + * bean instances rather than creating new ones based on bean definitions. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @author Costin Leau + * @author Chris Beams + * @author Phillip Webb + * @author Stephane Nicoll + * @since 16 April 2001 + * @see #registerBeanDefinition + * @see #addBeanPostProcessor + * @see #getBean + * @see #resolveDependency + */ +@SuppressWarnings("serial") +public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory + implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { + + @Nullable + private static Class javaxInjectProviderClass; + + static { + try { + javaxInjectProviderClass = + ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // JSR-330 API not available - Provider interface simply not supported then. + javaxInjectProviderClass = null; + } + } + + + /** Map from serialized id to factory instance. */ + private static final Map> serializableFactories = + new ConcurrentHashMap<>(8); + + /** Optional id for this factory, for serialization purposes. */ + @Nullable + private String serializationId; + + /** Whether to allow re-registration of a different definition with the same name. */ + private boolean allowBeanDefinitionOverriding = true; + + /** Whether to allow eager class loading even for lazy-init beans. */ + private boolean allowEagerClassLoading = true; + + /** Optional OrderComparator for dependency Lists and arrays. */ + @Nullable + private Comparator dependencyComparator; + + /** Resolver to use for checking if a bean definition is an autowire candidate. */ + private AutowireCandidateResolver autowireCandidateResolver = SimpleAutowireCandidateResolver.INSTANCE; + + /** Map from dependency type to corresponding autowired value. */ + private final Map, Object> resolvableDependencies = new ConcurrentHashMap<>(16); + + /** Map of bean definition objects, keyed by bean name. */ + private final Map beanDefinitionMap = new ConcurrentHashMap<>(256); + + /** Map from bean name to merged BeanDefinitionHolder. */ + private final Map mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256); + + /** Map of singleton and non-singleton bean names, keyed by dependency type. */ + private final Map, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); + + /** Map of singleton-only bean names, keyed by dependency type. */ + private final Map, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64); + + /** List of bean definition names, in registration order. */ + private volatile List beanDefinitionNames = new ArrayList<>(256); + + /** List of names of manually registered singletons, in registration order. */ + private volatile Set manualSingletonNames = new LinkedHashSet<>(16); + + /** Cached array of bean definition names in case of frozen configuration. */ + @Nullable + private volatile String[] frozenBeanDefinitionNames; + + /** Whether bean definition metadata may be cached for all beans. */ + private volatile boolean configurationFrozen; + + + /** + * Create a new DefaultListableBeanFactory. + */ + public DefaultListableBeanFactory() { + super(); + } + + /** + * Create a new DefaultListableBeanFactory with the given parent. + * @param parentBeanFactory the parent BeanFactory + */ + public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) { + super(parentBeanFactory); + } + + + /** + * Specify an id for serialization purposes, allowing this BeanFactory to be + * deserialized from this id back into the BeanFactory object, if needed. + */ + public void setSerializationId(@Nullable String serializationId) { + if (serializationId != null) { + serializableFactories.put(serializationId, new WeakReference<>(this)); + } + else if (this.serializationId != null) { + serializableFactories.remove(this.serializationId); + } + this.serializationId = serializationId; + } + + /** + * Return an id for serialization purposes, if specified, allowing this BeanFactory + * to be deserialized from this id back into the BeanFactory object, if needed. + * @since 4.1.2 + */ + @Nullable + public String getSerializationId() { + return this.serializationId; + } + + /** + * Set whether it should be allowed to override bean definitions by registering + * a different definition with the same name, automatically replacing the former. + * If not, an exception will be thrown. This also applies to overriding aliases. + *

Default is "true". + * @see #registerBeanDefinition + */ + public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { + this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; + } + + /** + * Return whether it should be allowed to override bean definitions by registering + * a different definition with the same name, automatically replacing the former. + * @since 4.1.2 + */ + public boolean isAllowBeanDefinitionOverriding() { + return this.allowBeanDefinitionOverriding; + } + + /** + * Set whether the factory is allowed to eagerly load bean classes + * even for bean definitions that are marked as "lazy-init". + *

Default is "true". Turn this flag off to suppress class loading + * for lazy-init beans unless such a bean is explicitly requested. + * In particular, by-type lookups will then simply ignore bean definitions + * without resolved class name, instead of loading the bean classes on + * demand just to perform a type check. + * @see AbstractBeanDefinition#setLazyInit + */ + public void setAllowEagerClassLoading(boolean allowEagerClassLoading) { + this.allowEagerClassLoading = allowEagerClassLoading; + } + + /** + * Return whether the factory is allowed to eagerly load bean classes + * even for bean definitions that are marked as "lazy-init". + * @since 4.1.2 + */ + public boolean isAllowEagerClassLoading() { + return this.allowEagerClassLoading; + } + + /** + * Set a {@link java.util.Comparator} for dependency Lists and arrays. + * @since 4.0 + * @see org.springframework.core.OrderComparator + * @see org.springframework.core.annotation.AnnotationAwareOrderComparator + */ + public void setDependencyComparator(@Nullable Comparator dependencyComparator) { + this.dependencyComparator = dependencyComparator; + } + + /** + * Return the dependency comparator for this BeanFactory (may be {@code null}. + * @since 4.0 + */ + @Nullable + public Comparator getDependencyComparator() { + return this.dependencyComparator; + } + + /** + * Set a custom autowire candidate resolver for this BeanFactory to use + * when deciding whether a bean definition should be considered as a + * candidate for autowiring. + */ + public void setAutowireCandidateResolver(AutowireCandidateResolver autowireCandidateResolver) { + Assert.notNull(autowireCandidateResolver, "AutowireCandidateResolver must not be null"); + if (autowireCandidateResolver instanceof BeanFactoryAware) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(this); + return null; + }, getAccessControlContext()); + } + else { + ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(this); + } + } + this.autowireCandidateResolver = autowireCandidateResolver; + } + + /** + * Return the autowire candidate resolver for this BeanFactory (never {@code null}). + */ + public AutowireCandidateResolver getAutowireCandidateResolver() { + return this.autowireCandidateResolver; + } + + + @Override + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { + super.copyConfigurationFrom(otherFactory); + if (otherFactory instanceof DefaultListableBeanFactory) { + DefaultListableBeanFactory otherListableFactory = (DefaultListableBeanFactory) otherFactory; + this.allowBeanDefinitionOverriding = otherListableFactory.allowBeanDefinitionOverriding; + this.allowEagerClassLoading = otherListableFactory.allowEagerClassLoading; + this.dependencyComparator = otherListableFactory.dependencyComparator; + // A clone of the AutowireCandidateResolver since it is potentially BeanFactoryAware + setAutowireCandidateResolver(otherListableFactory.getAutowireCandidateResolver().cloneIfNecessary()); + // Make resolvable dependencies (e.g. ResourceLoader) available here as well + this.resolvableDependencies.putAll(otherListableFactory.resolvableDependencies); + } + } + + + //--------------------------------------------------------------------- + // Implementation of remaining BeanFactory methods + //--------------------------------------------------------------------- + + @Override + public T getBean(Class requiredType) throws BeansException { + return getBean(requiredType, (Object[]) null); + } + + @SuppressWarnings("unchecked") + @Override + public T getBean(Class requiredType, @Nullable Object... args) throws BeansException { + Assert.notNull(requiredType, "Required type must not be null"); + Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return (T) resolved; + } + + @Override + public ObjectProvider getBeanProvider(Class requiredType) { + Assert.notNull(requiredType, "Required type must not be null"); + return getBeanProvider(ResolvableType.forRawClass(requiredType), true); + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + return getBeanProvider(requiredType, true); + } + + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + @Override + public boolean containsBeanDefinition(String beanName) { + Assert.notNull(beanName, "Bean name must not be null"); + return this.beanDefinitionMap.containsKey(beanName); + } + + @Override + public int getBeanDefinitionCount() { + return this.beanDefinitionMap.size(); + } + + @Override + public String[] getBeanDefinitionNames() { + String[] frozenNames = this.frozenBeanDefinitionNames; + if (frozenNames != null) { + return frozenNames.clone(); + } + else { + return StringUtils.toStringArray(this.beanDefinitionNames); + } + } + + @Override + public ObjectProvider getBeanProvider(Class requiredType, boolean allowEagerInit) { + Assert.notNull(requiredType, "Required type must not be null"); + return getBeanProvider(ResolvableType.forRawClass(requiredType), allowEagerInit); + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType, boolean allowEagerInit) { + return new BeanObjectProvider() { + @Override + public T getObject() throws BeansException { + T resolved = resolveBean(requiredType, null, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return resolved; + } + @Override + public T getObject(Object... args) throws BeansException { + T resolved = resolveBean(requiredType, args, false); + if (resolved == null) { + throw new NoSuchBeanDefinitionException(requiredType); + } + return resolved; + } + @Override + @Nullable + public T getIfAvailable() throws BeansException { + try { + return resolveBean(requiredType, null, false); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope + return null; + } + } + @Override + public void ifAvailable(Consumer dependencyConsumer) throws BeansException { + T dependency = getIfAvailable(); + if (dependency != null) { + try { + dependencyConsumer.accept(dependency); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope, even on scoped proxy invocation + } + } + } + @Override + @Nullable + public T getIfUnique() throws BeansException { + try { + return resolveBean(requiredType, null, true); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope + return null; + } + } + @Override + public void ifUnique(Consumer dependencyConsumer) throws BeansException { + T dependency = getIfUnique(); + if (dependency != null) { + try { + dependencyConsumer.accept(dependency); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope, even on scoped proxy invocation + } + } + } + @SuppressWarnings("unchecked") + @Override + public Stream stream() { + return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit)) + .map(name -> (T) getBean(name)) + .filter(bean -> !(bean instanceof NullBean)); + } + @SuppressWarnings("unchecked") + @Override + public Stream orderedStream() { + String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit); + if (beanNames.length == 0) { + return Stream.empty(); + } + Map matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length); + for (String beanName : beanNames) { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + matchingBeans.put(beanName, (T) beanInstance); + } + } + Stream stream = matchingBeans.values().stream(); + return stream.sorted(adaptOrderComparator(matchingBeans)); + } + }; + } + + @Nullable + private T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) { + NamedBeanHolder namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull); + if (namedBean != null) { + return namedBean.getBeanInstance(); + } + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof DefaultListableBeanFactory) { + return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull); + } + else if (parent != null) { + ObjectProvider parentProvider = parent.getBeanProvider(requiredType); + if (args != null) { + return parentProvider.getObject(args); + } + else { + return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable()); + } + } + return null; + } + + private String[] getBeanNamesForTypedStream(ResolvableType requiredType, boolean allowEagerInit) { + return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, allowEagerInit); + } + + @Override + public String[] getBeanNamesForType(ResolvableType type) { + return getBeanNamesForType(type, true, true); + } + + @Override + public String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { + Class resolved = type.resolve(); + if (resolved != null && !type.hasGenerics()) { + return getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit); + } + else { + return doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + } + + @Override + public String[] getBeanNamesForType(@Nullable Class type) { + return getBeanNamesForType(type, true, true); + } + + @Override + public String[] getBeanNamesForType(@Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) { + if (!isConfigurationFrozen() || type == null || !allowEagerInit) { + return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit); + } + Map, String[]> cache = + (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType); + String[] resolvedBeanNames = cache.get(type); + if (resolvedBeanNames != null) { + return resolvedBeanNames; + } + resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true); + if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) { + cache.put(type, resolvedBeanNames); + } + return resolvedBeanNames; + } + + private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { + List result = new ArrayList<>(); + + // Check all bean definitions. + for (String beanName : this.beanDefinitionNames) { + // Only consider bean as eligible if the bean name is not defined as alias for some other bean. + if (!isAlias(beanName)) { + try { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + // Only check bean definition if it is complete. + if (!mbd.isAbstract() && (allowEagerInit || + (mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) && + !requiresEagerInitForType(mbd.getFactoryBeanName()))) { + boolean isFactoryBean = isFactoryBean(beanName, mbd); + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + boolean matchFound = false; + boolean allowFactoryBeanInit = (allowEagerInit || containsSingleton(beanName)); + boolean isNonLazyDecorated = (dbd != null && !mbd.isLazyInit()); + if (!isFactoryBean) { + if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) { + matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit); + } + } + else { + if (includeNonSingletons || isNonLazyDecorated || + (allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) { + matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit); + } + if (!matchFound) { + // In case of FactoryBean, try to match FactoryBean instance itself next. + beanName = FACTORY_BEAN_PREFIX + beanName; + matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit); + } + } + if (matchFound) { + result.add(beanName); + } + } + } + catch (CannotLoadBeanClassException | BeanDefinitionStoreException ex) { + if (allowEagerInit) { + throw ex; + } + // Probably a placeholder: let's ignore it for type matching purposes. + LogMessage message = (ex instanceof CannotLoadBeanClassException ? + LogMessage.format("Ignoring bean class loading failure for bean '%s'", beanName) : + LogMessage.format("Ignoring unresolvable metadata in bean definition '%s'", beanName)); + logger.trace(message, ex); + // Register exception, in case the bean was accidentally unresolvable. + onSuppressedException(ex); + } + catch (NoSuchBeanDefinitionException ex) { + // Bean definition got removed while we were iterating -> ignore. + } + } + } + + // Check manually registered singletons too. + for (String beanName : this.manualSingletonNames) { + try { + // In case of FactoryBean, match object created by FactoryBean. + if (isFactoryBean(beanName)) { + if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) { + result.add(beanName); + // Match found for this bean: do not match FactoryBean itself anymore. + continue; + } + // In case of FactoryBean, try to match FactoryBean itself next. + beanName = FACTORY_BEAN_PREFIX + beanName; + } + // Match raw bean instance (might be raw FactoryBean). + if (isTypeMatch(beanName, type)) { + result.add(beanName); + } + } + catch (NoSuchBeanDefinitionException ex) { + // Shouldn't happen - probably a result of circular reference resolution... + logger.trace(LogMessage.format( + "Failed to check manually registered singleton with name '%s'", beanName), ex); + } + } + + return StringUtils.toStringArray(result); + } + + private boolean isSingleton(String beanName, RootBeanDefinition mbd, @Nullable BeanDefinitionHolder dbd) { + return (dbd != null ? mbd.isSingleton() : isSingleton(beanName)); + } + + /** + * Check whether the specified bean would need to be eagerly initialized + * in order to determine its type. + * @param factoryBeanName a factory-bean reference that the bean definition + * defines a factory method for + * @return whether eager initialization is necessary + */ + private boolean requiresEagerInitForType(@Nullable String factoryBeanName) { + return (factoryBeanName != null && isFactoryBean(factoryBeanName) && !containsSingleton(factoryBeanName)); + } + + @Override + public Map getBeansOfType(@Nullable Class type) throws BeansException { + return getBeansOfType(type, true, true); + } + + @Override + @SuppressWarnings("unchecked") + public Map getBeansOfType( + @Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException { + + String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + Map result = CollectionUtils.newLinkedHashMap(beanNames.length); + for (String beanName : beanNames) { + try { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + result.put(beanName, (T) beanInstance); + } + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + String exBeanName = bce.getBeanName(); + if (exBeanName != null && isCurrentlyInCreation(exBeanName)) { + if (logger.isTraceEnabled()) { + logger.trace("Ignoring match to currently created bean '" + exBeanName + "': " + + ex.getMessage()); + } + onSuppressedException(ex); + // Ignore: indicates a circular reference when autowiring constructors. + // We want to find matches other than the currently created bean itself. + continue; + } + } + throw ex; + } + } + return result; + } + + @Override + public String[] getBeanNamesForAnnotation(Class annotationType) { + List result = new ArrayList<>(); + for (String beanName : this.beanDefinitionNames) { + BeanDefinition bd = this.beanDefinitionMap.get(beanName); + if (bd != null && !bd.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) { + result.add(beanName); + } + } + for (String beanName : this.manualSingletonNames) { + if (!result.contains(beanName) && findAnnotationOnBean(beanName, annotationType) != null) { + result.add(beanName); + } + } + return StringUtils.toStringArray(result); + } + + @Override + public Map getBeansWithAnnotation(Class annotationType) { + String[] beanNames = getBeanNamesForAnnotation(annotationType); + Map result = CollectionUtils.newLinkedHashMap(beanNames.length); + for (String beanName : beanNames) { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + result.put(beanName, beanInstance); + } + } + return result; + } + + @Override + @Nullable + public A findAnnotationOnBean(String beanName, Class annotationType) + throws NoSuchBeanDefinitionException { + + return findMergedAnnotationOnBean(beanName, annotationType) + .synthesize(MergedAnnotation::isPresent).orElse(null); + } + + private MergedAnnotation findMergedAnnotationOnBean( + String beanName, Class annotationType) { + + Class beanType = getType(beanName); + if (beanType != null) { + MergedAnnotation annotation = + MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); + if (annotation.isPresent()) { + return annotation; + } + } + if (containsBeanDefinition(beanName)) { + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + // Check raw bean class, e.g. in case of a proxy. + if (bd.hasBeanClass()) { + Class beanClass = bd.getBeanClass(); + if (beanClass != beanType) { + MergedAnnotation annotation = + MergedAnnotations.from(beanClass, SearchStrategy.TYPE_HIERARCHY).get(annotationType); + if (annotation.isPresent()) { + return annotation; + } + } + } + // Check annotations declared on factory method, if any. + Method factoryMethod = bd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + MergedAnnotation annotation = + MergedAnnotations.from(factoryMethod, SearchStrategy.TYPE_HIERARCHY).get(annotationType); + if (annotation.isPresent()) { + return annotation; + } + } + } + return MergedAnnotation.missing(); + } + + + //--------------------------------------------------------------------- + // Implementation of ConfigurableListableBeanFactory interface + //--------------------------------------------------------------------- + + @Override + public void registerResolvableDependency(Class dependencyType, @Nullable Object autowiredValue) { + Assert.notNull(dependencyType, "Dependency type must not be null"); + if (autowiredValue != null) { + if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) { + throw new IllegalArgumentException("Value [" + autowiredValue + + "] does not implement specified dependency type [" + dependencyType.getName() + "]"); + } + this.resolvableDependencies.put(dependencyType, autowiredValue); + } + } + + @Override + public boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) + throws NoSuchBeanDefinitionException { + + return isAutowireCandidate(beanName, descriptor, getAutowireCandidateResolver()); + } + + /** + * Determine whether the specified bean definition qualifies as an autowire candidate, + * to be injected into other beans which declare a dependency of matching type. + * @param beanName the name of the bean definition to check + * @param descriptor the descriptor of the dependency to resolve + * @param resolver the AutowireCandidateResolver to use for the actual resolution algorithm + * @return whether the bean should be considered as autowire candidate + */ + protected boolean isAutowireCandidate( + String beanName, DependencyDescriptor descriptor, AutowireCandidateResolver resolver) + throws NoSuchBeanDefinitionException { + + String bdName = BeanFactoryUtils.transformedBeanName(beanName); + if (containsBeanDefinition(bdName)) { + return isAutowireCandidate(beanName, getMergedLocalBeanDefinition(bdName), descriptor, resolver); + } + else if (containsSingleton(beanName)) { + return isAutowireCandidate(beanName, new RootBeanDefinition(getType(beanName)), descriptor, resolver); + } + + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof DefaultListableBeanFactory) { + // No bean definition found in this factory -> delegate to parent. + return ((DefaultListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor, resolver); + } + else if (parent instanceof ConfigurableListableBeanFactory) { + // If no DefaultListableBeanFactory, can't pass the resolver along. + return ((ConfigurableListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor); + } + else { + return true; + } + } + + /** + * Determine whether the specified bean definition qualifies as an autowire candidate, + * to be injected into other beans which declare a dependency of matching type. + * @param beanName the name of the bean definition to check + * @param mbd the merged bean definition to check + * @param descriptor the descriptor of the dependency to resolve + * @param resolver the AutowireCandidateResolver to use for the actual resolution algorithm + * @return whether the bean should be considered as autowire candidate + */ + protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd, + DependencyDescriptor descriptor, AutowireCandidateResolver resolver) { + + String bdName = BeanFactoryUtils.transformedBeanName(beanName); + resolveBeanClass(mbd, bdName); + if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) { + new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd); + } + BeanDefinitionHolder holder = (beanName.equals(bdName) ? + this.mergedBeanDefinitionHolders.computeIfAbsent(beanName, + key -> new BeanDefinitionHolder(mbd, beanName, getAliases(bdName))) : + new BeanDefinitionHolder(mbd, beanName, getAliases(bdName))); + return resolver.isAutowireCandidate(holder, descriptor); + } + + @Override + public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + BeanDefinition bd = this.beanDefinitionMap.get(beanName); + if (bd == null) { + if (logger.isTraceEnabled()) { + logger.trace("No bean named '" + beanName + "' found in " + this); + } + throw new NoSuchBeanDefinitionException(beanName); + } + return bd; + } + + @Override + public Iterator getBeanNamesIterator() { + CompositeIterator iterator = new CompositeIterator<>(); + iterator.add(this.beanDefinitionNames.iterator()); + iterator.add(this.manualSingletonNames.iterator()); + return iterator; + } + + @Override + protected void clearMergedBeanDefinition(String beanName) { + super.clearMergedBeanDefinition(beanName); + this.mergedBeanDefinitionHolders.remove(beanName); + } + + @Override + public void clearMetadataCache() { + super.clearMetadataCache(); + this.mergedBeanDefinitionHolders.clear(); + clearByTypeCache(); + } + + @Override + public void freezeConfiguration() { + this.configurationFrozen = true; + this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames); + } + + @Override + public boolean isConfigurationFrozen() { + return this.configurationFrozen; + } + + /** + * Considers all beans as eligible for metadata caching + * if the factory's configuration has been marked as frozen. + * @see #freezeConfiguration() + */ + @Override + protected boolean isBeanEligibleForMetadataCaching(String beanName) { + return (this.configurationFrozen || super.isBeanEligibleForMetadataCaching(beanName)); + } + + @Override + public void preInstantiateSingletons() throws BeansException { + if (logger.isTraceEnabled()) { + logger.trace("Pre-instantiating singletons in " + this); + } + + // Iterate over a copy to allow for init methods which in turn register new bean definitions. + // While this may not be part of the regular factory bootstrap, it does otherwise work fine. + List beanNames = new ArrayList<>(this.beanDefinitionNames); + + // Trigger initialization of all non-lazy singleton beans... + for (String beanName : beanNames) { + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + if (isFactoryBean(beanName)) { + Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); + if (bean instanceof FactoryBean) { + FactoryBean factory = (FactoryBean) bean; + boolean isEagerInit; + if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { + isEagerInit = AccessController.doPrivileged( + (PrivilegedAction) ((SmartFactoryBean) factory)::isEagerInit, + getAccessControlContext()); + } + else { + isEagerInit = (factory instanceof SmartFactoryBean && + ((SmartFactoryBean) factory).isEagerInit()); + } + if (isEagerInit) { + getBean(beanName); + } + } + } + else { + getBean(beanName); + } + } + } + + // Trigger post-initialization callback for all applicable beans... + for (String beanName : beanNames) { + Object singletonInstance = getSingleton(beanName); + if (singletonInstance instanceof SmartInitializingSingleton) { + StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize") + .tag("beanName", beanName); + SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + smartSingleton.afterSingletonsInstantiated(); + return null; + }, getAccessControlContext()); + } + else { + smartSingleton.afterSingletonsInstantiated(); + } + smartInitialize.end(); + } + } + } + + + //--------------------------------------------------------------------- + // Implementation of BeanDefinitionRegistry interface + //--------------------------------------------------------------------- + + @Override + public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException { + + Assert.hasText(beanName, "Bean name must not be empty"); + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + + if (beanDefinition instanceof AbstractBeanDefinition) { + try { + ((AbstractBeanDefinition) beanDefinition).validate(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, + "Validation of bean definition failed", ex); + } + } + + BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); + if (existingDefinition != null) { + if (!isAllowBeanDefinitionOverriding()) { + throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); + } + else if (existingDefinition.getRole() < beanDefinition.getRole()) { + // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE + if (logger.isInfoEnabled()) { + logger.info("Overriding user-defined bean definition for bean '" + beanName + + "' with a framework-generated bean definition: replacing [" + + existingDefinition + "] with [" + beanDefinition + "]"); + } + } + else if (!beanDefinition.equals(existingDefinition)) { + if (logger.isDebugEnabled()) { + logger.debug("Overriding bean definition for bean '" + beanName + + "' with a different definition: replacing [" + existingDefinition + + "] with [" + beanDefinition + "]"); + } + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Overriding bean definition for bean '" + beanName + + "' with an equivalent definition: replacing [" + existingDefinition + + "] with [" + beanDefinition + "]"); + } + } + this.beanDefinitionMap.put(beanName, beanDefinition); + } + else { + if (hasBeanCreationStarted()) { + // Cannot modify startup-time collection elements anymore (for stable iteration) + synchronized (this.beanDefinitionMap) { + this.beanDefinitionMap.put(beanName, beanDefinition); + List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); + updatedDefinitions.addAll(this.beanDefinitionNames); + updatedDefinitions.add(beanName); + this.beanDefinitionNames = updatedDefinitions; + removeManualSingletonName(beanName); + } + } + else { + // Still in startup registration phase + this.beanDefinitionMap.put(beanName, beanDefinition); + this.beanDefinitionNames.add(beanName); + removeManualSingletonName(beanName); + } + this.frozenBeanDefinitionNames = null; + } + + if (existingDefinition != null || containsSingleton(beanName)) { + resetBeanDefinition(beanName); + } + else if (isConfigurationFrozen()) { + clearByTypeCache(); + } + } + + @Override + public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + Assert.hasText(beanName, "'beanName' must not be empty"); + + BeanDefinition bd = this.beanDefinitionMap.remove(beanName); + if (bd == null) { + if (logger.isTraceEnabled()) { + logger.trace("No bean named '" + beanName + "' found in " + this); + } + throw new NoSuchBeanDefinitionException(beanName); + } + + if (hasBeanCreationStarted()) { + // Cannot modify startup-time collection elements anymore (for stable iteration) + synchronized (this.beanDefinitionMap) { + List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames); + updatedDefinitions.remove(beanName); + this.beanDefinitionNames = updatedDefinitions; + } + } + else { + // Still in startup registration phase + this.beanDefinitionNames.remove(beanName); + } + this.frozenBeanDefinitionNames = null; + + resetBeanDefinition(beanName); + } + + /** + * Reset all bean definition caches for the given bean, + * including the caches of beans that are derived from it. + *

Called after an existing bean definition has been replaced or removed, + * triggering {@link #clearMergedBeanDefinition}, {@link #destroySingleton} + * and {@link MergedBeanDefinitionPostProcessor#resetBeanDefinition} on the + * given bean and on all bean definitions that have the given bean as parent. + * @param beanName the name of the bean to reset + * @see #registerBeanDefinition + * @see #removeBeanDefinition + */ + protected void resetBeanDefinition(String beanName) { + // Remove the merged bean definition for the given bean, if already created. + clearMergedBeanDefinition(beanName); + + // Remove corresponding bean from singleton cache, if any. Shouldn't usually + // be necessary, rather just meant for overriding a context's default beans + // (e.g. the default StaticMessageSource in a StaticApplicationContext). + destroySingleton(beanName); + + // Notify all post-processors that the specified bean definition has been reset. + for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { + processor.resetBeanDefinition(beanName); + } + + // Reset all bean definitions that have the given bean as parent (recursively). + for (String bdName : this.beanDefinitionNames) { + if (!beanName.equals(bdName)) { + BeanDefinition bd = this.beanDefinitionMap.get(bdName); + // Ensure bd is non-null due to potential concurrent modification of beanDefinitionMap. + if (bd != null && beanName.equals(bd.getParentName())) { + resetBeanDefinition(bdName); + } + } + } + } + + /** + * Only allows alias overriding if bean definition overriding is allowed. + */ + @Override + protected boolean allowAliasOverriding() { + return isAllowBeanDefinitionOverriding(); + } + + /** + * Also checks for an alias overriding a bean definition of the same name. + */ + @Override + protected void checkForAliasCircle(String name, String alias) { + super.checkForAliasCircle(name, alias); + if (!isAllowBeanDefinitionOverriding() && containsBeanDefinition(alias)) { + throw new IllegalStateException("Cannot register alias '" + alias + + "' for name '" + name + "': Alias would override bean definition '" + alias + "'"); + } + } + + @Override + public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { + super.registerSingleton(beanName, singletonObject); + updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName)); + clearByTypeCache(); + } + + @Override + public void destroySingletons() { + super.destroySingletons(); + updateManualSingletonNames(Set::clear, set -> !set.isEmpty()); + clearByTypeCache(); + } + + @Override + public void destroySingleton(String beanName) { + super.destroySingleton(beanName); + removeManualSingletonName(beanName); + clearByTypeCache(); + } + + private void removeManualSingletonName(String beanName) { + updateManualSingletonNames(set -> set.remove(beanName), set -> set.contains(beanName)); + } + + /** + * Update the factory's internal set of manual singleton names. + * @param action the modification action + * @param condition a precondition for the modification action + * (if this condition does not apply, the action can be skipped) + */ + private void updateManualSingletonNames(Consumer> action, Predicate> condition) { + if (hasBeanCreationStarted()) { + // Cannot modify startup-time collection elements anymore (for stable iteration) + synchronized (this.beanDefinitionMap) { + if (condition.test(this.manualSingletonNames)) { + Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); + action.accept(updatedSingletons); + this.manualSingletonNames = updatedSingletons; + } + } + } + else { + // Still in startup registration phase + if (condition.test(this.manualSingletonNames)) { + action.accept(this.manualSingletonNames); + } + } + } + + /** + * Remove any assumptions about by-type mappings. + */ + private void clearByTypeCache() { + this.allBeanNamesByType.clear(); + this.singletonBeanNamesByType.clear(); + } + + + //--------------------------------------------------------------------- + // Dependency resolution functionality + //--------------------------------------------------------------------- + + @Override + public NamedBeanHolder resolveNamedBean(Class requiredType) throws BeansException { + Assert.notNull(requiredType, "Required type must not be null"); + NamedBeanHolder namedBean = resolveNamedBean(ResolvableType.forRawClass(requiredType), null, false); + if (namedBean != null) { + return namedBean; + } + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof AutowireCapableBeanFactory) { + return ((AutowireCapableBeanFactory) parent).resolveNamedBean(requiredType); + } + throw new NoSuchBeanDefinitionException(requiredType); + } + + @SuppressWarnings("unchecked") + @Nullable + private NamedBeanHolder resolveNamedBean( + ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException { + + Assert.notNull(requiredType, "Required type must not be null"); + String[] candidateNames = getBeanNamesForType(requiredType); + + if (candidateNames.length > 1) { + List autowireCandidates = new ArrayList<>(candidateNames.length); + for (String beanName : candidateNames) { + if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { + autowireCandidates.add(beanName); + } + } + if (!autowireCandidates.isEmpty()) { + candidateNames = StringUtils.toStringArray(autowireCandidates); + } + } + + if (candidateNames.length == 1) { + String beanName = candidateNames[0]; + return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args)); + } + else if (candidateNames.length > 1) { + Map candidates = CollectionUtils.newLinkedHashMap(candidateNames.length); + for (String beanName : candidateNames) { + if (containsSingleton(beanName) && args == null) { + Object beanInstance = getBean(beanName); + candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance)); + } + else { + candidates.put(beanName, getType(beanName)); + } + } + String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass()); + if (candidateName == null) { + candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass()); + } + if (candidateName != null) { + Object beanInstance = candidates.get(candidateName); + if (beanInstance == null || beanInstance instanceof Class) { + beanInstance = getBean(candidateName, requiredType.toClass(), args); + } + return new NamedBeanHolder<>(candidateName, (T) beanInstance); + } + if (!nonUniqueAsNull) { + throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); + } + } + + return null; + } + + @Override + @Nullable + public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { + + descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); + if (Optional.class == descriptor.getDependencyType()) { + return createOptionalDependency(descriptor, requestingBeanName); + } + else if (ObjectFactory.class == descriptor.getDependencyType() || + ObjectProvider.class == descriptor.getDependencyType()) { + return new DependencyObjectProvider(descriptor, requestingBeanName); + } + else if (javaxInjectProviderClass == descriptor.getDependencyType()) { + return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName); + } + else { + Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( + descriptor, requestingBeanName); + if (result == null) { + result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); + } + return result; + } + } + + @Nullable + public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { + + InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); + try { + Object shortcut = descriptor.resolveShortcut(this); + if (shortcut != null) { + return shortcut; + } + + Class type = descriptor.getDependencyType(); + Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); + if (value != null) { + if (value instanceof String) { + String strVal = resolveEmbeddedValue((String) value); + BeanDefinition bd = (beanName != null && containsBean(beanName) ? + getMergedBeanDefinition(beanName) : null); + value = evaluateBeanDefinitionString(strVal, bd); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + try { + return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor()); + } + catch (UnsupportedOperationException ex) { + // A custom TypeConverter which does not support TypeDescriptor resolution... + return (descriptor.getField() != null ? + converter.convertIfNecessary(value, type, descriptor.getField()) : + converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); + } + } + + Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); + if (multipleBeans != null) { + return multipleBeans; + } + + Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); + if (matchingBeans.isEmpty()) { + if (isRequired(descriptor)) { + raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); + } + return null; + } + + String autowiredBeanName; + Object instanceCandidate; + + if (matchingBeans.size() > 1) { + autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); + if (autowiredBeanName == null) { + if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { + return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); + } + else { + // In case of an optional Collection/Map, silently ignore a non-unique case: + // possibly it was meant to be an empty collection of multiple regular beans + // (before 4.3 in particular when we didn't even look for collection beans). + return null; + } + } + instanceCandidate = matchingBeans.get(autowiredBeanName); + } + else { + // We have exactly one match. + Map.Entry entry = matchingBeans.entrySet().iterator().next(); + autowiredBeanName = entry.getKey(); + instanceCandidate = entry.getValue(); + } + + if (autowiredBeanNames != null) { + autowiredBeanNames.add(autowiredBeanName); + } + if (instanceCandidate instanceof Class) { + instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); + } + Object result = instanceCandidate; + if (result instanceof NullBean) { + if (isRequired(descriptor)) { + raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); + } + result = null; + } + if (!ClassUtils.isAssignableValue(type, result)) { + throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass()); + } + return result; + } + finally { + ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); + } + } + + @Nullable + private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { + + Class type = descriptor.getDependencyType(); + + if (descriptor instanceof StreamDependencyDescriptor) { + Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + Stream stream = matchingBeans.keySet().stream() + .map(name -> descriptor.resolveCandidate(name, type, this)) + .filter(bean -> !(bean instanceof NullBean)); + if (((StreamDependencyDescriptor) descriptor).isOrdered()) { + stream = stream.sorted(adaptOrderComparator(matchingBeans)); + } + return stream; + } + else if (type.isArray()) { + Class componentType = type.getComponentType(); + ResolvableType resolvableType = descriptor.getResolvableType(); + Class resolvedArrayType = resolvableType.resolve(type); + if (resolvedArrayType != type) { + componentType = resolvableType.getComponentType().resolve(); + } + if (componentType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, componentType, + new MultiElementDescriptor(descriptor)); + if (matchingBeans.isEmpty()) { + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType); + if (result instanceof Object[]) { + Comparator comparator = adaptDependencyComparator(matchingBeans); + if (comparator != null) { + Arrays.sort((Object[]) result, comparator); + } + } + return result; + } + else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { + Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); + if (elementType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, elementType, + new MultiElementDescriptor(descriptor)); + if (matchingBeans.isEmpty()) { + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + Object result = converter.convertIfNecessary(matchingBeans.values(), type); + if (result instanceof List) { + if (((List) result).size() > 1) { + Comparator comparator = adaptDependencyComparator(matchingBeans); + if (comparator != null) { + ((List) result).sort(comparator); + } + } + } + return result; + } + else if (Map.class == type) { + ResolvableType mapType = descriptor.getResolvableType().asMap(); + Class keyType = mapType.resolveGeneric(0); + if (String.class != keyType) { + return null; + } + Class valueType = mapType.resolveGeneric(1); + if (valueType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, valueType, + new MultiElementDescriptor(descriptor)); + if (matchingBeans.isEmpty()) { + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + return matchingBeans; + } + else { + return null; + } + } + + private boolean isRequired(DependencyDescriptor descriptor) { + return getAutowireCandidateResolver().isRequired(descriptor); + } + + private boolean indicatesMultipleBeans(Class type) { + return (type.isArray() || (type.isInterface() && + (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)))); + } + + @Nullable + private Comparator adaptDependencyComparator(Map matchingBeans) { + Comparator comparator = getDependencyComparator(); + if (comparator instanceof OrderComparator) { + return ((OrderComparator) comparator).withSourceProvider( + createFactoryAwareOrderSourceProvider(matchingBeans)); + } + else { + return comparator; + } + } + + private Comparator adaptOrderComparator(Map matchingBeans) { + Comparator dependencyComparator = getDependencyComparator(); + OrderComparator comparator = (dependencyComparator instanceof OrderComparator ? + (OrderComparator) dependencyComparator : OrderComparator.INSTANCE); + return comparator.withSourceProvider(createFactoryAwareOrderSourceProvider(matchingBeans)); + } + + private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map beans) { + IdentityHashMap instancesToBeanNames = new IdentityHashMap<>(); + beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName)); + return new FactoryAwareOrderSourceProvider(instancesToBeanNames); + } + + /** + * Find bean instances that match the required type. + * Called during autowiring for the specified bean. + * @param beanName the name of the bean that is about to be wired + * @param requiredType the actual type of bean to look for + * (may be an array component type or collection element type) + * @param descriptor the descriptor of the dependency to resolve + * @return a Map of candidate names and candidate instances that match + * the required type (never {@code null}) + * @throws BeansException in case of errors + * @see #autowireByType + * @see #autowireConstructor + */ + protected Map findAutowireCandidates( + @Nullable String beanName, Class requiredType, DependencyDescriptor descriptor) { + + String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this, requiredType, true, descriptor.isEager()); + Map result = CollectionUtils.newLinkedHashMap(candidateNames.length); + for (Map.Entry, Object> classObjectEntry : this.resolvableDependencies.entrySet()) { + Class autowiringType = classObjectEntry.getKey(); + if (autowiringType.isAssignableFrom(requiredType)) { + Object autowiringValue = classObjectEntry.getValue(); + autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); + if (requiredType.isInstance(autowiringValue)) { + result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); + break; + } + } + } + for (String candidate : candidateNames) { + if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { + addCandidateEntry(result, candidate, descriptor, requiredType); + } + } + if (result.isEmpty()) { + boolean multiple = indicatesMultipleBeans(requiredType); + // Consider fallback matches if the first pass failed to find anything... + DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); + for (String candidate : candidateNames) { + if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) && + (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) { + addCandidateEntry(result, candidate, descriptor, requiredType); + } + } + if (result.isEmpty() && !multiple) { + // Consider self references as a final pass... + // but in the case of a dependency collection, not the very same bean itself. + for (String candidate : candidateNames) { + if (isSelfReference(beanName, candidate) && + (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && + isAutowireCandidate(candidate, fallbackDescriptor)) { + addCandidateEntry(result, candidate, descriptor, requiredType); + } + } + } + } + return result; + } + + /** + * Add an entry to the candidate map: a bean instance if available or just the resolved + * type, preventing early bean initialization ahead of primary candidate selection. + */ + private void addCandidateEntry(Map candidates, String candidateName, + DependencyDescriptor descriptor, Class requiredType) { + + if (descriptor instanceof MultiElementDescriptor) { + Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); + if (!(beanInstance instanceof NullBean)) { + candidates.put(candidateName, beanInstance); + } + } + else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor && + ((StreamDependencyDescriptor) descriptor).isOrdered())) { + Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); + candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance)); + } + else { + candidates.put(candidateName, getType(candidateName)); + } + } + + /** + * Determine the autowire candidate in the given set of beans. + *

Looks for {@code @Primary} and {@code @Priority} (in that order). + * @param candidates a Map of candidate names and candidate instances + * that match the required type, as returned by {@link #findAutowireCandidates} + * @param descriptor the target dependency to match against + * @return the name of the autowire candidate, or {@code null} if none found + */ + @Nullable + protected String determineAutowireCandidate(Map candidates, DependencyDescriptor descriptor) { + Class requiredType = descriptor.getDependencyType(); + String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); + if (primaryCandidate != null) { + return primaryCandidate; + } + String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); + if (priorityCandidate != null) { + return priorityCandidate; + } + // Fallback + for (Map.Entry entry : candidates.entrySet()) { + String candidateName = entry.getKey(); + Object beanInstance = entry.getValue(); + if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || + matchesBeanName(candidateName, descriptor.getDependencyName())) { + return candidateName; + } + } + return null; + } + + /** + * Determine the primary candidate in the given set of beans. + * @param candidates a Map of candidate names and candidate instances + * (or candidate classes if not created yet) that match the required type + * @param requiredType the target dependency type to match against + * @return the name of the primary candidate, or {@code null} if none found + * @see #isPrimary(String, Object) + */ + @Nullable + protected String determinePrimaryCandidate(Map candidates, Class requiredType) { + String primaryBeanName = null; + for (Map.Entry entry : candidates.entrySet()) { + String candidateBeanName = entry.getKey(); + Object beanInstance = entry.getValue(); + if (isPrimary(candidateBeanName, beanInstance)) { + if (primaryBeanName != null) { + boolean candidateLocal = containsBeanDefinition(candidateBeanName); + boolean primaryLocal = containsBeanDefinition(primaryBeanName); + if (candidateLocal && primaryLocal) { + throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), + "more than one 'primary' bean found among candidates: " + candidates.keySet()); + } + else if (candidateLocal) { + primaryBeanName = candidateBeanName; + } + } + else { + primaryBeanName = candidateBeanName; + } + } + } + return primaryBeanName; + } + + /** + * Determine the candidate with the highest priority in the given set of beans. + *

Based on {@code @javax.annotation.Priority}. As defined by the related + * {@link org.springframework.core.Ordered} interface, the lowest value has + * the highest priority. + * @param candidates a Map of candidate names and candidate instances + * (or candidate classes if not created yet) that match the required type + * @param requiredType the target dependency type to match against + * @return the name of the candidate with the highest priority, + * or {@code null} if none found + * @see #getPriority(Object) + */ + @Nullable + protected String determineHighestPriorityCandidate(Map candidates, Class requiredType) { + String highestPriorityBeanName = null; + Integer highestPriority = null; + for (Map.Entry entry : candidates.entrySet()) { + String candidateBeanName = entry.getKey(); + Object beanInstance = entry.getValue(); + if (beanInstance != null) { + Integer candidatePriority = getPriority(beanInstance); + if (candidatePriority != null) { + if (highestPriorityBeanName != null) { + if (candidatePriority.equals(highestPriority)) { + throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), + "Multiple beans found with the same priority ('" + highestPriority + + "') among candidates: " + candidates.keySet()); + } + else if (candidatePriority < highestPriority) { + highestPriorityBeanName = candidateBeanName; + highestPriority = candidatePriority; + } + } + else { + highestPriorityBeanName = candidateBeanName; + highestPriority = candidatePriority; + } + } + } + } + return highestPriorityBeanName; + } + + /** + * Return whether the bean definition for the given bean name has been + * marked as a primary bean. + * @param beanName the name of the bean + * @param beanInstance the corresponding bean instance (can be null) + * @return whether the given bean qualifies as primary + */ + protected boolean isPrimary(String beanName, Object beanInstance) { + String transformedBeanName = transformedBeanName(beanName); + if (containsBeanDefinition(transformedBeanName)) { + return getMergedLocalBeanDefinition(transformedBeanName).isPrimary(); + } + BeanFactory parent = getParentBeanFactory(); + return (parent instanceof DefaultListableBeanFactory && + ((DefaultListableBeanFactory) parent).isPrimary(transformedBeanName, beanInstance)); + } + + /** + * Return the priority assigned for the given bean instance by + * the {@code javax.annotation.Priority} annotation. + *

The default implementation delegates to the specified + * {@link #setDependencyComparator dependency comparator}, checking its + * {@link OrderComparator#getPriority method} if it is an extension of + * Spring's common {@link OrderComparator} - typically, an + * {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}. + * If no such comparator is present, this implementation returns {@code null}. + * @param beanInstance the bean instance to check (can be {@code null}) + * @return the priority assigned to that bean or {@code null} if none is set + */ + @Nullable + protected Integer getPriority(Object beanInstance) { + Comparator comparator = getDependencyComparator(); + if (comparator instanceof OrderComparator) { + return ((OrderComparator) comparator).getPriority(beanInstance); + } + return null; + } + + /** + * Determine whether the given candidate name matches the bean name or the aliases + * stored in this bean definition. + */ + protected boolean matchesBeanName(String beanName, @Nullable String candidateName) { + return (candidateName != null && + (candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName))); + } + + /** + * Determine whether the given beanName/candidateName pair indicates a self reference, + * i.e. whether the candidate points back to the original bean or to a factory method + * on the original bean. + */ + private boolean isSelfReference(@Nullable String beanName, @Nullable String candidateName) { + return (beanName != null && candidateName != null && + (beanName.equals(candidateName) || (containsBeanDefinition(candidateName) && + beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName())))); + } + + /** + * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException + * for an unresolvable dependency. + */ + private void raiseNoMatchingBeanFound( + Class type, ResolvableType resolvableType, DependencyDescriptor descriptor) throws BeansException { + + checkBeanNotOfRequiredType(type, descriptor); + + throw new NoSuchBeanDefinitionException(resolvableType, + "expected at least 1 bean which qualifies as autowire candidate. " + + "Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations())); + } + + /** + * Raise a BeanNotOfRequiredTypeException for an unresolvable dependency, if applicable, + * i.e. if the target type of the bean would match but an exposed proxy doesn't. + */ + private void checkBeanNotOfRequiredType(Class type, DependencyDescriptor descriptor) { + for (String beanName : this.beanDefinitionNames) { + try { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class targetType = mbd.getTargetType(); + if (targetType != null && type.isAssignableFrom(targetType) && + isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) { + // Probably a proxy interfering with target type match -> throw meaningful exception. + Object beanInstance = getSingleton(beanName, false); + Class beanType = (beanInstance != null && beanInstance.getClass() != NullBean.class ? + beanInstance.getClass() : predictBeanType(beanName, mbd)); + if (beanType != null && !type.isAssignableFrom(beanType)) { + throw new BeanNotOfRequiredTypeException(beanName, type, beanType); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + // Bean definition got removed while we were iterating -> ignore. + } + } + + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) parent).checkBeanNotOfRequiredType(type, descriptor); + } + } + + /** + * Create an {@link Optional} wrapper for the specified dependency. + */ + private Optional createOptionalDependency( + DependencyDescriptor descriptor, @Nullable String beanName, final Object... args) { + + DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) { + @Override + public boolean isRequired() { + return false; + } + @Override + public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) { + return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) : + super.resolveCandidate(beanName, requiredType, beanFactory)); + } + }; + Object result = doResolveDependency(descriptorToUse, beanName, null, null); + return (result instanceof Optional ? (Optional) result : Optional.ofNullable(result)); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(ObjectUtils.identityToString(this)); + sb.append(": defining beans ["); + sb.append(StringUtils.collectionToCommaDelimitedString(this.beanDefinitionNames)); + sb.append("]; "); + BeanFactory parent = getParentBeanFactory(); + if (parent == null) { + sb.append("root of factory hierarchy"); + } + else { + sb.append("parent: ").append(ObjectUtils.identityToString(parent)); + } + return sb.toString(); + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + throw new NotSerializableException("DefaultListableBeanFactory itself is not deserializable - " + + "just a SerializedBeanFactoryReference is"); + } + + protected Object writeReplace() throws ObjectStreamException { + if (this.serializationId != null) { + return new SerializedBeanFactoryReference(this.serializationId); + } + else { + throw new NotSerializableException("DefaultListableBeanFactory has no serialization id"); + } + } + + + /** + * Minimal id reference to the factory. + * Resolved to the actual factory instance on deserialization. + */ + private static class SerializedBeanFactoryReference implements Serializable { + + private final String id; + + public SerializedBeanFactoryReference(String id) { + this.id = id; + } + + private Object readResolve() { + Reference ref = serializableFactories.get(this.id); + if (ref != null) { + Object result = ref.get(); + if (result != null) { + return result; + } + } + // Lenient fallback: dummy factory in case of original factory not found... + DefaultListableBeanFactory dummyFactory = new DefaultListableBeanFactory(); + dummyFactory.serializationId = this.id; + return dummyFactory; + } + } + + + /** + * A dependency descriptor marker for nested elements. + */ + private static class NestedDependencyDescriptor extends DependencyDescriptor { + + public NestedDependencyDescriptor(DependencyDescriptor original) { + super(original); + increaseNestingLevel(); + } + } + + + /** + * A dependency descriptor for a multi-element declaration with nested elements. + */ + private static class MultiElementDescriptor extends NestedDependencyDescriptor { + + public MultiElementDescriptor(DependencyDescriptor original) { + super(original); + } + } + + + /** + * A dependency descriptor marker for stream access to multiple elements. + */ + private static class StreamDependencyDescriptor extends DependencyDescriptor { + + private final boolean ordered; + + public StreamDependencyDescriptor(DependencyDescriptor original, boolean ordered) { + super(original); + this.ordered = ordered; + } + + public boolean isOrdered() { + return this.ordered; + } + } + + + private interface BeanObjectProvider extends ObjectProvider, Serializable { + } + + + /** + * Serializable ObjectFactory/ObjectProvider for lazy resolution of a dependency. + */ + private class DependencyObjectProvider implements BeanObjectProvider { + + private final DependencyDescriptor descriptor; + + private final boolean optional; + + @Nullable + private final String beanName; + + public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) { + this.descriptor = new NestedDependencyDescriptor(descriptor); + this.optional = (this.descriptor.getDependencyType() == Optional.class); + this.beanName = beanName; + } + + @Override + public Object getObject() throws BeansException { + if (this.optional) { + return createOptionalDependency(this.descriptor, this.beanName); + } + else { + Object result = doResolveDependency(this.descriptor, this.beanName, null, null); + if (result == null) { + throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType()); + } + return result; + } + } + + @Override + public Object getObject(final Object... args) throws BeansException { + if (this.optional) { + return createOptionalDependency(this.descriptor, this.beanName, args); + } + else { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { + @Override + public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) { + return beanFactory.getBean(beanName, args); + } + }; + Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); + if (result == null) { + throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType()); + } + return result; + } + } + + @Override + @Nullable + public Object getIfAvailable() throws BeansException { + try { + if (this.optional) { + return createOptionalDependency(this.descriptor, this.beanName); + } + else { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { + @Override + public boolean isRequired() { + return false; + } + }; + return doResolveDependency(descriptorToUse, this.beanName, null, null); + } + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope + return null; + } + } + + @Override + public void ifAvailable(Consumer dependencyConsumer) throws BeansException { + Object dependency = getIfAvailable(); + if (dependency != null) { + try { + dependencyConsumer.accept(dependency); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope, even on scoped proxy invocation + } + } + } + + @Override + @Nullable + public Object getIfUnique() throws BeansException { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { + @Override + public boolean isRequired() { + return false; + } + + @Override + @Nullable + public Object resolveNotUnique(ResolvableType type, Map matchingBeans) { + return null; + } + }; + try { + if (this.optional) { + return createOptionalDependency(descriptorToUse, this.beanName); + } + else { + return doResolveDependency(descriptorToUse, this.beanName, null, null); + } + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope + return null; + } + } + + @Override + public void ifUnique(Consumer dependencyConsumer) throws BeansException { + Object dependency = getIfUnique(); + if (dependency != null) { + try { + dependencyConsumer.accept(dependency); + } + catch (ScopeNotActiveException ex) { + // Ignore resolved bean in non-active scope, even on scoped proxy invocation + } + } + } + + @Nullable + protected Object getValue() throws BeansException { + if (this.optional) { + return createOptionalDependency(this.descriptor, this.beanName); + } + else { + return doResolveDependency(this.descriptor, this.beanName, null, null); + } + } + + @Override + public Stream stream() { + return resolveStream(false); + } + + @Override + public Stream orderedStream() { + return resolveStream(true); + } + + @SuppressWarnings("unchecked") + private Stream resolveStream(boolean ordered) { + DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered); + Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); + return (result instanceof Stream ? (Stream) result : Stream.of(result)); + } + } + + + /** + * Separate inner class for avoiding a hard dependency on the {@code javax.inject} API. + * Actual {@code javax.inject.Provider} implementation is nested here in order to make it + * invisible for Graal's introspection of DefaultListableBeanFactory's nested classes. + */ + private class Jsr330Factory implements Serializable { + + public Object createDependencyProvider(DependencyDescriptor descriptor, @Nullable String beanName) { + return new Jsr330Provider(descriptor, beanName); + } + + private class Jsr330Provider extends DependencyObjectProvider implements Provider { + + public Jsr330Provider(DependencyDescriptor descriptor, @Nullable String beanName) { + super(descriptor, beanName); + } + + @Override + @Nullable + public Object get() throws BeansException { + return getValue(); + } + } + } + + + /** + * An {@link org.springframework.core.OrderComparator.OrderSourceProvider} implementation + * that is aware of the bean metadata of the instances to sort. + *

Lookup for the method factory of an instance to sort, if any, and let the + * comparator retrieve the {@link org.springframework.core.annotation.Order} + * value defined on it. This essentially allows for the following construct: + */ + private class FactoryAwareOrderSourceProvider implements OrderComparator.OrderSourceProvider { + + private final Map instancesToBeanNames; + + public FactoryAwareOrderSourceProvider(Map instancesToBeanNames) { + this.instancesToBeanNames = instancesToBeanNames; + } + + @Override + @Nullable + public Object getOrderSource(Object obj) { + String beanName = this.instancesToBeanNames.get(obj); + if (beanName == null || !containsBeanDefinition(beanName)) { + return null; + } + RootBeanDefinition beanDefinition = getMergedLocalBeanDefinition(beanName); + List sources = new ArrayList<>(2); + Method factoryMethod = beanDefinition.getResolvedFactoryMethod(); + if (factoryMethod != null) { + sources.add(factoryMethod); + } + Class targetType = beanDefinition.getTargetType(); + if (targetType != null && targetType != obj.getClass()) { + sources.add(targetType); + } + return sources.toArray(); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java new file mode 100644 index 0000000..0a81aef --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -0,0 +1,636 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCreationNotAllowedException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.SingletonBeanRegistry; +import org.springframework.core.SimpleAliasRegistry; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Generic registry for shared bean instances, implementing the + * {@link org.springframework.beans.factory.config.SingletonBeanRegistry}. + * Allows for registering singleton instances that should be shared + * for all callers of the registry, to be obtained via bean name. + * + *

Also supports registration of + * {@link org.springframework.beans.factory.DisposableBean} instances, + * (which might or might not correspond to registered singletons), + * to be destroyed on shutdown of the registry. Dependencies between + * beans can be registered to enforce an appropriate shutdown order. + * + *

This class mainly serves as base class for + * {@link org.springframework.beans.factory.BeanFactory} implementations, + * factoring out the common management of singleton bean instances. Note that + * the {@link org.springframework.beans.factory.config.ConfigurableBeanFactory} + * interface extends the {@link SingletonBeanRegistry} interface. + * + *

Note that this class assumes neither a bean definition concept + * nor a specific creation process for bean instances, in contrast to + * {@link AbstractBeanFactory} and {@link DefaultListableBeanFactory} + * (which inherit from it). Can alternatively also be used as a nested + * helper to delegate to. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #registerSingleton + * @see #registerDisposableBean + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory + */ +public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { + + /** Maximum number of suppressed exceptions to preserve. */ + private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100; + + + /** Cache of singleton objects: bean name to bean instance. */ + private final Map singletonObjects = new ConcurrentHashMap<>(256); + + /** Cache of singleton factories: bean name to ObjectFactory. */ + private final Map> singletonFactories = new HashMap<>(16); + + /** Cache of early singleton objects: bean name to bean instance. */ + private final Map earlySingletonObjects = new ConcurrentHashMap<>(16); + + /** Set of registered singletons, containing the bean names in registration order. */ + private final Set registeredSingletons = new LinkedHashSet<>(256); + + /** Names of beans that are currently in creation. */ + private final Set singletonsCurrentlyInCreation = + Collections.newSetFromMap(new ConcurrentHashMap<>(16)); + + /** Names of beans currently excluded from in creation checks. */ + private final Set inCreationCheckExclusions = + Collections.newSetFromMap(new ConcurrentHashMap<>(16)); + + /** Collection of suppressed Exceptions, available for associating related causes. */ + @Nullable + private Set suppressedExceptions; + + /** Flag that indicates whether we're currently within destroySingletons. */ + private boolean singletonsCurrentlyInDestruction = false; + + /** Disposable bean instances: bean name to disposable instance. */ + private final Map disposableBeans = new LinkedHashMap<>(); + + /** Map between containing bean names: bean name to Set of bean names that the bean contains. */ + private final Map> containedBeanMap = new ConcurrentHashMap<>(16); + + /** Map between dependent bean names: bean name to Set of dependent bean names. */ + private final Map> dependentBeanMap = new ConcurrentHashMap<>(64); + + /** Map between depending bean names: bean name to Set of bean names for the bean's dependencies. */ + private final Map> dependenciesForBeanMap = new ConcurrentHashMap<>(64); + + + @Override + public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { + Assert.notNull(beanName, "Bean name must not be null"); + Assert.notNull(singletonObject, "Singleton object must not be null"); + synchronized (this.singletonObjects) { + Object oldObject = this.singletonObjects.get(beanName); + if (oldObject != null) { + throw new IllegalStateException("Could not register object [" + singletonObject + + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound"); + } + addSingleton(beanName, singletonObject); + } + } + + /** + * Add the given singleton object to the singleton cache of this factory. + *

To be called for eager registration of singletons. + * @param beanName the name of the bean + * @param singletonObject the singleton object + */ + protected void addSingleton(String beanName, Object singletonObject) { + synchronized (this.singletonObjects) { + this.singletonObjects.put(beanName, singletonObject); + this.singletonFactories.remove(beanName); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.add(beanName); + } + } + + /** + * Add the given singleton factory for building the specified singleton + * if necessary. + *

To be called for eager registration of singletons, e.g. to be able to + * resolve circular references. + * @param beanName the name of the bean + * @param singletonFactory the factory for the singleton object + */ + protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(singletonFactory, "Singleton factory must not be null"); + synchronized (this.singletonObjects) { + if (!this.singletonObjects.containsKey(beanName)) { + this.singletonFactories.put(beanName, singletonFactory); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.add(beanName); + } + } + } + + @Override + @Nullable + public Object getSingleton(String beanName) { + return getSingleton(beanName, true); + } + + /** + * Return the (raw) singleton object registered under the given name. + *

Checks already instantiated singletons and also allows for an early + * reference to a currently created singleton (resolving a circular reference). + * @param beanName the name of the bean to look for + * @param allowEarlyReference whether early references should be created or not + * @return the registered singleton object, or {@code null} if none found + */ + @Nullable + protected Object getSingleton(String beanName, boolean allowEarlyReference) { + // Quick check for existing instance without full singleton lock + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { + singletonObject = this.earlySingletonObjects.get(beanName); + if (singletonObject == null && allowEarlyReference) { + synchronized (this.singletonObjects) { + // Consistent creation of early reference within full singleton lock + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + singletonObject = this.earlySingletonObjects.get(beanName); + if (singletonObject == null) { + ObjectFactory singletonFactory = this.singletonFactories.get(beanName); + if (singletonFactory != null) { + singletonObject = singletonFactory.getObject(); + this.earlySingletonObjects.put(beanName, singletonObject); + this.singletonFactories.remove(beanName); + } + } + } + } + } + } + return singletonObject; + } + + /** + * Return the (raw) singleton object registered under the given name, + * creating and registering a new one if none registered yet. + * @param beanName the name of the bean + * @param singletonFactory the ObjectFactory to lazily create the singleton + * with, if necessary + * @return the registered singleton object + */ + public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(beanName, "Bean name must not be null"); + synchronized (this.singletonObjects) { + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + if (this.singletonsCurrentlyInDestruction) { + throw new BeanCreationNotAllowedException(beanName, + "Singleton bean creation not allowed while singletons of this factory are in destruction " + + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); + } + if (logger.isDebugEnabled()) { + logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); + } + beforeSingletonCreation(beanName); + boolean newSingleton = false; + boolean recordSuppressedExceptions = (this.suppressedExceptions == null); + if (recordSuppressedExceptions) { + this.suppressedExceptions = new LinkedHashSet<>(); + } + try { + singletonObject = singletonFactory.getObject(); + newSingleton = true; + } + catch (IllegalStateException ex) { + // Has the singleton object implicitly appeared in the meantime -> + // if yes, proceed with it since the exception indicates that state. + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + throw ex; + } + } + catch (BeanCreationException ex) { + if (recordSuppressedExceptions) { + for (Exception suppressedException : this.suppressedExceptions) { + ex.addRelatedCause(suppressedException); + } + } + throw ex; + } + finally { + if (recordSuppressedExceptions) { + this.suppressedExceptions = null; + } + afterSingletonCreation(beanName); + } + if (newSingleton) { + addSingleton(beanName, singletonObject); + } + } + return singletonObject; + } + } + + /** + * Register an exception that happened to get suppressed during the creation of a + * singleton bean instance, e.g. a temporary circular reference resolution problem. + *

The default implementation preserves any given exception in this registry's + * collection of suppressed exceptions, up to a limit of 100 exceptions, adding + * them as related causes to an eventual top-level {@link BeanCreationException}. + * @param ex the Exception to register + * @see BeanCreationException#getRelatedCauses() + */ + protected void onSuppressedException(Exception ex) { + synchronized (this.singletonObjects) { + if (this.suppressedExceptions != null && this.suppressedExceptions.size() < SUPPRESSED_EXCEPTIONS_LIMIT) { + this.suppressedExceptions.add(ex); + } + } + } + + /** + * Remove the bean with the given name from the singleton cache of this factory, + * to be able to clean up eager registration of a singleton if creation failed. + * @param beanName the name of the bean + * @see #getSingletonMutex() + */ + protected void removeSingleton(String beanName) { + synchronized (this.singletonObjects) { + this.singletonObjects.remove(beanName); + this.singletonFactories.remove(beanName); + this.earlySingletonObjects.remove(beanName); + this.registeredSingletons.remove(beanName); + } + } + + @Override + public boolean containsSingleton(String beanName) { + return this.singletonObjects.containsKey(beanName); + } + + @Override + public String[] getSingletonNames() { + synchronized (this.singletonObjects) { + return StringUtils.toStringArray(this.registeredSingletons); + } + } + + @Override + public int getSingletonCount() { + synchronized (this.singletonObjects) { + return this.registeredSingletons.size(); + } + } + + + public void setCurrentlyInCreation(String beanName, boolean inCreation) { + Assert.notNull(beanName, "Bean name must not be null"); + if (!inCreation) { + this.inCreationCheckExclusions.add(beanName); + } + else { + this.inCreationCheckExclusions.remove(beanName); + } + } + + public boolean isCurrentlyInCreation(String beanName) { + Assert.notNull(beanName, "Bean name must not be null"); + return (!this.inCreationCheckExclusions.contains(beanName) && isActuallyInCreation(beanName)); + } + + protected boolean isActuallyInCreation(String beanName) { + return isSingletonCurrentlyInCreation(beanName); + } + + /** + * Return whether the specified singleton bean is currently in creation + * (within the entire factory). + * @param beanName the name of the bean + */ + public boolean isSingletonCurrentlyInCreation(String beanName) { + return this.singletonsCurrentlyInCreation.contains(beanName); + } + + /** + * Callback before singleton creation. + *

The default implementation register the singleton as currently in creation. + * @param beanName the name of the singleton about to be created + * @see #isSingletonCurrentlyInCreation + */ + protected void beforeSingletonCreation(String beanName) { + if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + } + + /** + * Callback after singleton creation. + *

The default implementation marks the singleton as not in creation anymore. + * @param beanName the name of the singleton that has been created + * @see #isSingletonCurrentlyInCreation + */ + protected void afterSingletonCreation(String beanName) { + if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { + throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); + } + } + + + /** + * Add the given bean to the list of disposable beans in this registry. + *

Disposable beans usually correspond to registered singletons, + * matching the bean name but potentially being a different instance + * (for example, a DisposableBean adapter for a singleton that does not + * naturally implement Spring's DisposableBean interface). + * @param beanName the name of the bean + * @param bean the bean instance + */ + public void registerDisposableBean(String beanName, DisposableBean bean) { + synchronized (this.disposableBeans) { + this.disposableBeans.put(beanName, bean); + } + } + + /** + * Register a containment relationship between two beans, + * e.g. between an inner bean and its containing outer bean. + *

Also registers the containing bean as dependent on the contained bean + * in terms of destruction order. + * @param containedBeanName the name of the contained (inner) bean + * @param containingBeanName the name of the containing (outer) bean + * @see #registerDependentBean + */ + public void registerContainedBean(String containedBeanName, String containingBeanName) { + synchronized (this.containedBeanMap) { + Set containedBeans = + this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8)); + if (!containedBeans.add(containedBeanName)) { + return; + } + } + registerDependentBean(containedBeanName, containingBeanName); + } + + /** + * Register a dependent bean for the given bean, + * to be destroyed before the given bean is destroyed. + * @param beanName the name of the bean + * @param dependentBeanName the name of the dependent bean + */ + public void registerDependentBean(String beanName, String dependentBeanName) { + String canonicalName = canonicalName(beanName); + + synchronized (this.dependentBeanMap) { + Set dependentBeans = + this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8)); + if (!dependentBeans.add(dependentBeanName)) { + return; + } + } + + synchronized (this.dependenciesForBeanMap) { + Set dependenciesForBean = + this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8)); + dependenciesForBean.add(canonicalName); + } + } + + /** + * Determine whether the specified dependent bean has been registered as + * dependent on the given bean or on any of its transitive dependencies. + * @param beanName the name of the bean to check + * @param dependentBeanName the name of the dependent bean + * @since 4.0 + */ + protected boolean isDependent(String beanName, String dependentBeanName) { + synchronized (this.dependentBeanMap) { + return isDependent(beanName, dependentBeanName, null); + } + } + + private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set alreadySeen) { + if (alreadySeen != null && alreadySeen.contains(beanName)) { + return false; + } + String canonicalName = canonicalName(beanName); + Set dependentBeans = this.dependentBeanMap.get(canonicalName); + if (dependentBeans == null) { + return false; + } + if (dependentBeans.contains(dependentBeanName)) { + return true; + } + for (String transitiveDependency : dependentBeans) { + if (alreadySeen == null) { + alreadySeen = new HashSet<>(); + } + alreadySeen.add(beanName); + if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) { + return true; + } + } + return false; + } + + /** + * Determine whether a dependent bean has been registered for the given name. + * @param beanName the name of the bean to check + */ + protected boolean hasDependentBean(String beanName) { + return this.dependentBeanMap.containsKey(beanName); + } + + /** + * Return the names of all beans which depend on the specified bean, if any. + * @param beanName the name of the bean + * @return the array of dependent bean names, or an empty array if none + */ + public String[] getDependentBeans(String beanName) { + Set dependentBeans = this.dependentBeanMap.get(beanName); + if (dependentBeans == null) { + return new String[0]; + } + synchronized (this.dependentBeanMap) { + return StringUtils.toStringArray(dependentBeans); + } + } + + /** + * Return the names of all beans that the specified bean depends on, if any. + * @param beanName the name of the bean + * @return the array of names of beans which the bean depends on, + * or an empty array if none + */ + public String[] getDependenciesForBean(String beanName) { + Set dependenciesForBean = this.dependenciesForBeanMap.get(beanName); + if (dependenciesForBean == null) { + return new String[0]; + } + synchronized (this.dependenciesForBeanMap) { + return StringUtils.toStringArray(dependenciesForBean); + } + } + + public void destroySingletons() { + if (logger.isTraceEnabled()) { + logger.trace("Destroying singletons in " + this); + } + synchronized (this.singletonObjects) { + this.singletonsCurrentlyInDestruction = true; + } + + String[] disposableBeanNames; + synchronized (this.disposableBeans) { + disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet()); + } + for (int i = disposableBeanNames.length - 1; i >= 0; i--) { + destroySingleton(disposableBeanNames[i]); + } + + this.containedBeanMap.clear(); + this.dependentBeanMap.clear(); + this.dependenciesForBeanMap.clear(); + + clearSingletonCache(); + } + + /** + * Clear all cached singleton instances in this registry. + * @since 4.3.15 + */ + protected void clearSingletonCache() { + synchronized (this.singletonObjects) { + this.singletonObjects.clear(); + this.singletonFactories.clear(); + this.earlySingletonObjects.clear(); + this.registeredSingletons.clear(); + this.singletonsCurrentlyInDestruction = false; + } + } + + /** + * Destroy the given bean. Delegates to {@code destroyBean} + * if a corresponding disposable bean instance is found. + * @param beanName the name of the bean + * @see #destroyBean + */ + public void destroySingleton(String beanName) { + // Remove a registered singleton of the given name, if any. + removeSingleton(beanName); + + // Destroy the corresponding DisposableBean instance. + DisposableBean disposableBean; + synchronized (this.disposableBeans) { + disposableBean = (DisposableBean) this.disposableBeans.remove(beanName); + } + destroyBean(beanName, disposableBean); + } + + /** + * Destroy the given bean. Must destroy beans that depend on the given + * bean before the bean itself. Should not throw any exceptions. + * @param beanName the name of the bean + * @param bean the bean instance to destroy + */ + protected void destroyBean(String beanName, @Nullable DisposableBean bean) { + // Trigger destruction of dependent beans first... + Set dependencies; + synchronized (this.dependentBeanMap) { + // Within full synchronization in order to guarantee a disconnected Set + dependencies = this.dependentBeanMap.remove(beanName); + } + if (dependencies != null) { + if (logger.isTraceEnabled()) { + logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies); + } + for (String dependentBeanName : dependencies) { + destroySingleton(dependentBeanName); + } + } + + // Actually destroy the bean now... + if (bean != null) { + try { + bean.destroy(); + } + catch (Throwable ex) { + if (logger.isWarnEnabled()) { + logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex); + } + } + } + + // Trigger destruction of contained beans... + Set containedBeans; + synchronized (this.containedBeanMap) { + // Within full synchronization in order to guarantee a disconnected Set + containedBeans = this.containedBeanMap.remove(beanName); + } + if (containedBeans != null) { + for (String containedBeanName : containedBeans) { + destroySingleton(containedBeanName); + } + } + + // Remove destroyed bean from other beans' dependencies. + synchronized (this.dependentBeanMap) { + for (Iterator>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry = it.next(); + Set dependenciesToClean = entry.getValue(); + dependenciesToClean.remove(beanName); + if (dependenciesToClean.isEmpty()) { + it.remove(); + } + } + } + + // Remove destroyed bean's prepared dependency information. + this.dependenciesForBeanMap.remove(beanName); + } + + /** + * Exposes the singleton mutex to subclasses and external collaborators. + *

Subclasses should synchronize on the given Object if they perform + * any sort of extended singleton creation phase. In particular, subclasses + * should not have their own mutexes involved in singleton creation, + * to avoid the potential for deadlocks in lazy-init situations. + */ + @Override + public final Object getSingletonMutex() { + return this.singletonObjects; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java new file mode 100644 index 0000000..fb277b6 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -0,0 +1,411 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Adapter that implements the {@link DisposableBean} and {@link Runnable} + * interfaces performing various destruction steps on a given bean instance: + *

    + *
  • DestructionAwareBeanPostProcessors; + *
  • the bean implementing DisposableBean itself; + *
  • a custom destroy method specified on the bean definition. + *
+ * + * @author Juergen Hoeller + * @author Costin Leau + * @author Stephane Nicoll + * @since 2.0 + * @see AbstractBeanFactory + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor + * @see AbstractBeanDefinition#getDestroyMethodName() + */ +@SuppressWarnings("serial") +class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { + + private static final String CLOSE_METHOD_NAME = "close"; + + private static final String SHUTDOWN_METHOD_NAME = "shutdown"; + + private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class); + + + private final Object bean; + + private final String beanName; + + private final boolean invokeDisposableBean; + + private final boolean nonPublicAccessAllowed; + + @Nullable + private final AccessControlContext acc; + + @Nullable + private String destroyMethodName; + + @Nullable + private transient Method destroyMethod; + + @Nullable + private final List beanPostProcessors; + + + /** + * Create a new DisposableBeanAdapter for the given bean. + * @param bean the bean instance (never {@code null}) + * @param beanName the name of the bean + * @param beanDefinition the merged bean definition + * @param postProcessors the List of BeanPostProcessors + * (potentially DestructionAwareBeanPostProcessor), if any + */ + public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition, + List postProcessors, @Nullable AccessControlContext acc) { + + Assert.notNull(bean, "Disposable bean must not be null"); + this.bean = bean; + this.beanName = beanName; + this.invokeDisposableBean = + (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy")); + this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); + this.acc = acc; + String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); + if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) && + !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { + this.destroyMethodName = destroyMethodName; + Method destroyMethod = determineDestroyMethod(destroyMethodName); + if (destroyMethod == null) { + if (beanDefinition.isEnforceDestroyMethod()) { + throw new BeanDefinitionValidationException("Could not find a destroy method named '" + + destroyMethodName + "' on bean with name '" + beanName + "'"); + } + } + else { + Class[] paramTypes = destroyMethod.getParameterTypes(); + if (paramTypes.length > 1) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has more than one parameter - not supported as destroy method"); + } + else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has a non-boolean parameter - not supported as destroy method"); + } + destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); + } + this.destroyMethod = destroyMethod; + } + this.beanPostProcessors = filterPostProcessors(postProcessors, bean); + } + + /** + * Create a new DisposableBeanAdapter for the given bean. + * @param bean the bean instance (never {@code null}) + * @param postProcessors the List of BeanPostProcessors + * (potentially DestructionAwareBeanPostProcessor), if any + */ + public DisposableBeanAdapter( + Object bean, List postProcessors, AccessControlContext acc) { + + Assert.notNull(bean, "Disposable bean must not be null"); + this.bean = bean; + this.beanName = bean.getClass().getName(); + this.invokeDisposableBean = (this.bean instanceof DisposableBean); + this.nonPublicAccessAllowed = true; + this.acc = acc; + this.beanPostProcessors = filterPostProcessors(postProcessors, bean); + } + + /** + * Create a new DisposableBeanAdapter for the given bean. + */ + private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean, + boolean nonPublicAccessAllowed, @Nullable String destroyMethodName, + @Nullable List postProcessors) { + + this.bean = bean; + this.beanName = beanName; + this.invokeDisposableBean = invokeDisposableBean; + this.nonPublicAccessAllowed = nonPublicAccessAllowed; + this.acc = null; + this.destroyMethodName = destroyMethodName; + this.beanPostProcessors = postProcessors; + } + + + /** + * If the current value of the given beanDefinition's "destroyMethodName" property is + * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. + * Candidate methods are currently limited to public, no-arg methods named "close" or + * "shutdown" (whether declared locally or inherited). The given BeanDefinition's + * "destroyMethodName" is updated to be null if no such method is found, otherwise set + * to the name of the inferred method. This constant serves as the default for the + * {@code @Bean#destroyMethod} attribute and the value of the constant may also be + * used in XML within the {@code } or {@code + * } attributes. + *

Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} + * interfaces, reflectively calling the "close" method on implementing beans as well. + */ + @Nullable + private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { + String destroyMethodName = beanDefinition.getDestroyMethodName(); + if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || + (destroyMethodName == null && bean instanceof AutoCloseable)) { + // Only perform destroy method inference or Closeable detection + // in case of the bean not explicitly implementing DisposableBean + if (!(bean instanceof DisposableBean)) { + try { + return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex) { + try { + return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex2) { + // no candidate destroy method found + } + } + } + return null; + } + return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); + } + + /** + * Search for all DestructionAwareBeanPostProcessors in the List. + * @param processors the List to search + * @return the filtered List of DestructionAwareBeanPostProcessors + */ + @Nullable + private List filterPostProcessors( + List processors, Object bean) { + + List filteredPostProcessors = null; + if (!CollectionUtils.isEmpty(processors)) { + filteredPostProcessors = new ArrayList<>(processors.size()); + for (DestructionAwareBeanPostProcessor processor : processors) { + if (processor.requiresDestruction(bean)) { + filteredPostProcessors.add(processor); + } + } + } + return filteredPostProcessors; + } + + + @Override + public void run() { + destroy(); + } + + @Override + public void destroy() { + if (!CollectionUtils.isEmpty(this.beanPostProcessors)) { + for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) { + processor.postProcessBeforeDestruction(this.bean, this.beanName); + } + } + + if (this.invokeDisposableBean) { + if (logger.isTraceEnabled()) { + logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'"); + } + try { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + ((DisposableBean) this.bean).destroy(); + return null; + }, this.acc); + } + else { + ((DisposableBean) this.bean).destroy(); + } + } + catch (Throwable ex) { + String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex); + } + else { + logger.warn(msg + ": " + ex); + } + } + } + + if (this.destroyMethod != null) { + invokeCustomDestroyMethod(this.destroyMethod); + } + else if (this.destroyMethodName != null) { + Method methodToInvoke = determineDestroyMethod(this.destroyMethodName); + if (methodToInvoke != null) { + invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke)); + } + } + } + + + @Nullable + private Method determineDestroyMethod(String name) { + try { + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedAction) () -> findDestroyMethod(name)); + } + else { + return findDestroyMethod(name); + } + } + catch (IllegalArgumentException ex) { + throw new BeanDefinitionValidationException("Could not find unique destroy method on bean with name '" + + this.beanName + ": " + ex.getMessage()); + } + } + + @Nullable + private Method findDestroyMethod(String name) { + return (this.nonPublicAccessAllowed ? + BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), name) : + BeanUtils.findMethodWithMinimalParameters(this.bean.getClass().getMethods(), name)); + } + + /** + * Invoke the specified custom destroy method on the given bean. + *

This implementation invokes a no-arg method if found, else checking + * for a method with a single boolean argument (passing in "true", + * assuming a "force" parameter), else logging an error. + */ + private void invokeCustomDestroyMethod(final Method destroyMethod) { + int paramCount = destroyMethod.getParameterCount(); + final Object[] args = new Object[paramCount]; + if (paramCount == 1) { + args[0] = Boolean.TRUE; + } + if (logger.isTraceEnabled()) { + logger.trace("Invoking destroy method '" + this.destroyMethodName + + "' on bean with name '" + this.beanName + "'"); + } + try { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + ReflectionUtils.makeAccessible(destroyMethod); + return null; + }); + try { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> + destroyMethod.invoke(this.bean, args), this.acc); + } + catch (PrivilegedActionException pax) { + throw (InvocationTargetException) pax.getException(); + } + } + else { + ReflectionUtils.makeAccessible(destroyMethod); + destroyMethod.invoke(this.bean, args); + } + } + catch (InvocationTargetException ex) { + String msg = "Destroy method '" + this.destroyMethodName + "' on bean with name '" + + this.beanName + "' threw an exception"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex.getTargetException()); + } + else { + logger.warn(msg + ": " + ex.getTargetException()); + } + } + catch (Throwable ex) { + logger.warn("Failed to invoke destroy method '" + this.destroyMethodName + + "' on bean with name '" + this.beanName + "'", ex); + } + } + + + /** + * Serializes a copy of the state of this class, + * filtering out non-serializable BeanPostProcessors. + */ + protected Object writeReplace() { + List serializablePostProcessors = null; + if (this.beanPostProcessors != null) { + serializablePostProcessors = new ArrayList<>(); + for (DestructionAwareBeanPostProcessor postProcessor : this.beanPostProcessors) { + if (postProcessor instanceof Serializable) { + serializablePostProcessors.add(postProcessor); + } + } + } + return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean, + this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors); + } + + + /** + * Check whether the given bean has any kind of destroy method to call. + * @param bean the bean instance + * @param beanDefinition the corresponding bean definition + */ + public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) { + if (bean instanceof DisposableBean || bean instanceof AutoCloseable) { + return true; + } + String destroyMethodName = beanDefinition.getDestroyMethodName(); + if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { + return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || + ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); + } + return StringUtils.hasLength(destroyMethodName); + } + + /** + * Check whether the given bean has destruction-aware post-processors applying to it. + * @param bean the bean instance + * @param postProcessors the post-processor candidates + */ + public static boolean hasApplicableProcessors(Object bean, List postProcessors) { + if (!CollectionUtils.isEmpty(postProcessors)) { + for (DestructionAwareBeanPostProcessor processor : postProcessors) { + if (processor.requiresDestruction(bean)) { + return true; + } + } + } + return false; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java new file mode 100644 index 0000000..ca779d1 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2019 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.beans.factory.support; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * GenericBeanDefinition is a one-stop shop for standard bean definition purposes. + * Like any bean definition, it allows for specifying a class plus optionally + * constructor argument values and property values. Additionally, deriving from a + * parent bean definition can be flexibly configured through the "parentName" property. + * + *

In general, use this {@code GenericBeanDefinition} class for the purpose of + * registering user-visible bean definitions (which a post-processor might operate on, + * potentially even reconfiguring the parent name). Use {@code RootBeanDefinition} / + * {@code ChildBeanDefinition} where parent/child relationships happen to be pre-determined. + * + * @author Juergen Hoeller + * @since 2.5 + * @see #setParentName + * @see RootBeanDefinition + * @see ChildBeanDefinition + */ +@SuppressWarnings("serial") +public class GenericBeanDefinition extends AbstractBeanDefinition { + + @Nullable + private String parentName; + + + /** + * Create a new GenericBeanDefinition, to be configured through its bean + * properties and configuration methods. + * @see #setBeanClass + * @see #setScope + * @see #setConstructorArgumentValues + * @see #setPropertyValues + */ + public GenericBeanDefinition() { + super(); + } + + /** + * Create a new GenericBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + public GenericBeanDefinition(BeanDefinition original) { + super(original); + } + + + @Override + public void setParentName(@Nullable String parentName) { + this.parentName = parentName; + } + + @Override + @Nullable + public String getParentName() { + return this.parentName; + } + + + @Override + public AbstractBeanDefinition cloneBeanDefinition() { + return new GenericBeanDefinition(this); + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof GenericBeanDefinition)) { + return false; + } + GenericBeanDefinition that = (GenericBeanDefinition) other; + return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other)); + } + + @Override + public String toString() { + if (this.parentName != null) { + return "Generic bean with parent '" + this.parentName + "': " + super.toString(); + } + return "Generic bean: " + super.toString(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java new file mode 100644 index 0000000..01b8145 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -0,0 +1,197 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.lang.reflect.Method; +import java.util.Properties; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * Basic {@link AutowireCandidateResolver} that performs a full generic type + * match with the candidate's type if the dependency is declared as a generic type + * (e.g. Repository<Customer>). + * + *

This is the base class for + * {@link org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver}, + * providing an implementation all non-annotation-based resolution steps at this level. + * + * @author Juergen Hoeller + * @since 4.0 + */ +public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCandidateResolver + implements BeanFactoryAware, Cloneable { + + @Nullable + private BeanFactory beanFactory; + + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Nullable + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + @Override + public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + if (!super.isAutowireCandidate(bdHolder, descriptor)) { + // If explicitly false, do not proceed with any other checks... + return false; + } + return checkGenericTypeMatch(bdHolder, descriptor); + } + + /** + * Match the given dependency type with its generic type information against the given + * candidate bean definition. + */ + protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + ResolvableType dependencyType = descriptor.getResolvableType(); + if (dependencyType.getType() instanceof Class) { + // No generic type -> we know it's a Class type-match, so no need to check again. + return true; + } + + ResolvableType targetType = null; + boolean cacheType = false; + RootBeanDefinition rbd = null; + if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) { + rbd = (RootBeanDefinition) bdHolder.getBeanDefinition(); + } + if (rbd != null) { + targetType = rbd.targetType; + if (targetType == null) { + cacheType = true; + // First, check factory method return type, if applicable + targetType = getReturnTypeForFactoryMethod(rbd, descriptor); + if (targetType == null) { + RootBeanDefinition dbd = getResolvedDecoratedDefinition(rbd); + if (dbd != null) { + targetType = dbd.targetType; + if (targetType == null) { + targetType = getReturnTypeForFactoryMethod(dbd, descriptor); + } + } + } + } + } + + if (targetType == null) { + // Regular case: straight bean instance, with BeanFactory available. + if (this.beanFactory != null) { + Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); + if (beanType != null) { + targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanType)); + } + } + // Fallback: no BeanFactory set, or no type resolvable through it + // -> best-effort match against the target class if applicable. + if (targetType == null && rbd != null && rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) { + Class beanClass = rbd.getBeanClass(); + if (!FactoryBean.class.isAssignableFrom(beanClass)) { + targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanClass)); + } + } + } + + if (targetType == null) { + return true; + } + if (cacheType) { + rbd.targetType = targetType; + } + if (descriptor.fallbackMatchAllowed() && + (targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) { + // Fallback matches allow unresolvable generics, e.g. plain HashMap to Map; + // and pragmatically also java.util.Properties to any Map (since despite formally being a + // Map, java.util.Properties is usually perceived as a Map). + return true; + } + // Full check for complex generic type match... + return dependencyType.isAssignableFrom(targetType); + } + + @Nullable + protected RootBeanDefinition getResolvedDecoratedDefinition(RootBeanDefinition rbd) { + BeanDefinitionHolder decDef = rbd.getDecoratedDefinition(); + if (decDef != null && this.beanFactory instanceof ConfigurableListableBeanFactory) { + ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) this.beanFactory; + if (clbf.containsBeanDefinition(decDef.getBeanName())) { + BeanDefinition dbd = clbf.getMergedBeanDefinition(decDef.getBeanName()); + if (dbd instanceof RootBeanDefinition) { + return (RootBeanDefinition) dbd; + } + } + } + return null; + } + + @Nullable + protected ResolvableType getReturnTypeForFactoryMethod(RootBeanDefinition rbd, DependencyDescriptor descriptor) { + // Should typically be set for any kind of factory method, since the BeanFactory + // pre-resolves them before reaching out to the AutowireCandidateResolver... + ResolvableType returnType = rbd.factoryMethodReturnType; + if (returnType == null) { + Method factoryMethod = rbd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + returnType = ResolvableType.forMethodReturnType(factoryMethod); + } + } + if (returnType != null) { + Class resolvedClass = returnType.resolve(); + if (resolvedClass != null && descriptor.getDependencyType().isAssignableFrom(resolvedClass)) { + // Only use factory method metadata if the return type is actually expressive enough + // for our dependency. Otherwise, the returned instance type may have matched instead + // in case of a singleton instance having been registered with the container already. + return returnType; + } + } + return null; + } + + + /** + * This implementation clones all instance fields through standard + * {@link Cloneable} support, allowing for subsequent reconfiguration + * of the cloned instance through a fresh {@link #setBeanFactory} call. + * @see #clone() + */ + @Override + public AutowireCandidateResolver cloneIfNecessary() { + try { + return (AutowireCandidateResolver) clone(); + } + catch (CloneNotSupportedException ex) { + throw new IllegalStateException(ex); + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ImplicitlyAppearedSingletonException.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ImplicitlyAppearedSingletonException.java new file mode 100644 index 0000000..eabec72 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ImplicitlyAppearedSingletonException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2016 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.beans.factory.support; + +/** + * Internal exception to be propagated from {@link ConstructorResolver}, + * passed through to the initiating {@link DefaultSingletonBeanRegistry} + * (without wrapping in a {@code BeanCreationException}). + * + * @author Juergen Hoeller + * @since 5.0 + */ +@SuppressWarnings("serial") +class ImplicitlyAppearedSingletonException extends IllegalStateException { + + public ImplicitlyAppearedSingletonException() { + super("About-to-be-created singleton instance implicitly appeared through the " + + "creation of the factory bean that its bean definition points to"); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java new file mode 100644 index 0000000..b424c38 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2017 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.beans.factory.support; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * Represents an override of a method that looks up an object in the same IoC context. + * + *

Methods eligible for lookup override must not have arguments. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1 + */ +public class LookupOverride extends MethodOverride { + + @Nullable + private final String beanName; + + @Nullable + private Method method; + + + /** + * Construct a new LookupOverride. + * @param methodName the name of the method to override + * @param beanName the name of the bean in the current {@code BeanFactory} + * that the overridden method should return (may be {@code null}) + */ + public LookupOverride(String methodName, @Nullable String beanName) { + super(methodName); + this.beanName = beanName; + } + + /** + * Construct a new LookupOverride. + * @param method the method to override + * @param beanName the name of the bean in the current {@code BeanFactory} + * that the overridden method should return (may be {@code null}) + */ + public LookupOverride(Method method, @Nullable String beanName) { + super(method.getName()); + this.method = method; + this.beanName = beanName; + } + + + /** + * Return the name of the bean that should be returned by this method. + */ + @Nullable + public String getBeanName() { + return this.beanName; + } + + /** + * Match the specified method by {@link Method} reference or method name. + *

For backwards compatibility reasons, in a scenario with overloaded + * non-abstract methods of the given name, only the no-arg variant of a + * method will be turned into a container-driven lookup method. + *

In case of a provided {@link Method}, only straight matches will + * be considered, usually demarcated by the {@code @Lookup} annotation. + */ + @Override + public boolean matches(Method method) { + if (this.method != null) { + return method.equals(this.method); + } + else { + return (method.getName().equals(getMethodName()) && (!isOverloaded() || + Modifier.isAbstract(method.getModifiers()) || method.getParameterCount() == 0)); + } + } + + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof LookupOverride) || !super.equals(other)) { + return false; + } + LookupOverride that = (LookupOverride) other; + return (ObjectUtils.nullSafeEquals(this.method, that.method) && + ObjectUtils.nullSafeEquals(this.beanName, that.beanName)); + } + + @Override + public int hashCode() { + return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName)); + } + + @Override + public String toString() { + return "LookupOverride for method '" + getMethodName() + "'"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java new file mode 100644 index 0000000..7a282ed --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; +import org.springframework.lang.Nullable; + +/** + * Tag collection class used to hold managed List elements, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 27.05.2003 + * @param the element type + */ +@SuppressWarnings("serial") +public class ManagedList extends ArrayList implements Mergeable, BeanMetadataElement { + + @Nullable + private Object source; + + @Nullable + private String elementTypeName; + + private boolean mergeEnabled; + + + public ManagedList() { + } + + public ManagedList(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Set the default element type name (class name) to be used for this list. + */ + public void setElementTypeName(String elementTypeName) { + this.elementTypeName = elementTypeName; + } + + /** + * Return the default element type name (class name) to be used for this list. + */ + @Nullable + public String getElementTypeName() { + return this.elementTypeName; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + @Override + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + @Override + @SuppressWarnings("unchecked") + public List merge(@Nullable Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof List)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + List merged = new ManagedList<>(); + merged.addAll((List) parent); + merged.addAll(this); + return merged; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java new file mode 100644 index 0000000..55b76f0 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; +import org.springframework.lang.Nullable; + +/** + * Tag collection class used to hold managed Map values, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 27.05.2003 + * @param the key type + * @param the value type + */ +@SuppressWarnings("serial") +public class ManagedMap extends LinkedHashMap implements Mergeable, BeanMetadataElement { + + @Nullable + private Object source; + + @Nullable + private String keyTypeName; + + @Nullable + private String valueTypeName; + + private boolean mergeEnabled; + + + public ManagedMap() { + } + + public ManagedMap(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Set the default key type name (class name) to be used for this map. + */ + public void setKeyTypeName(@Nullable String keyTypeName) { + this.keyTypeName = keyTypeName; + } + + /** + * Return the default key type name (class name) to be used for this map. + */ + @Nullable + public String getKeyTypeName() { + return this.keyTypeName; + } + + /** + * Set the default value type name (class name) to be used for this map. + */ + public void setValueTypeName(@Nullable String valueTypeName) { + this.valueTypeName = valueTypeName; + } + + /** + * Return the default value type name (class name) to be used for this map. + */ + @Nullable + public String getValueTypeName() { + return this.valueTypeName; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + @Override + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + @Override + @SuppressWarnings("unchecked") + public Object merge(@Nullable Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Map)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Map merged = new ManagedMap<>(); + merged.putAll((Map) parent); + merged.putAll(this); + return merged; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java new file mode 100644 index 0000000..d4d5f03 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedProperties.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2017 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.beans.factory.support; + +import java.util.Properties; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; +import org.springframework.lang.Nullable; + +/** + * Tag class which represents a Spring-managed {@link Properties} instance + * that supports merging of parent/child definitions. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +@SuppressWarnings("serial") +public class ManagedProperties extends Properties implements Mergeable, BeanMetadataElement { + + @Nullable + private Object source; + + private boolean mergeEnabled; + + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + @Override + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + + @Override + public Object merge(@Nullable Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Properties)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Properties merged = new ManagedProperties(); + merged.putAll((Properties) parent); + merged.putAll(this); + return merged; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java new file mode 100644 index 0000000..7f84ec7 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.Mergeable; +import org.springframework.lang.Nullable; + +/** + * Tag collection class used to hold managed Set values, which may + * include runtime bean references (to be resolved into bean objects). + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 21.01.2004 + * @param the element type + */ +@SuppressWarnings("serial") +public class ManagedSet extends LinkedHashSet implements Mergeable, BeanMetadataElement { + + @Nullable + private Object source; + + @Nullable + private String elementTypeName; + + private boolean mergeEnabled; + + + public ManagedSet() { + } + + public ManagedSet(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Set the default element type name (class name) to be used for this set. + */ + public void setElementTypeName(@Nullable String elementTypeName) { + this.elementTypeName = elementTypeName; + } + + /** + * Return the default element type name (class name) to be used for this set. + */ + @Nullable + public String getElementTypeName() { + return this.elementTypeName; + } + + /** + * Set whether merging should be enabled for this collection, + * in case of a 'parent' collection value being present. + */ + public void setMergeEnabled(boolean mergeEnabled) { + this.mergeEnabled = mergeEnabled; + } + + @Override + public boolean isMergeEnabled() { + return this.mergeEnabled; + } + + @Override + @SuppressWarnings("unchecked") + public Set merge(@Nullable Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof Set)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + Set merged = new ManagedSet<>(); + merged.addAll((Set) parent); + merged.addAll(this); + return merged; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java new file mode 100644 index 0000000..7306171 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * Post-processor callback interface for merged bean definitions at runtime. + * {@link BeanPostProcessor} implementations may implement this sub-interface in order + * to post-process the merged bean definition (a processed copy of the original bean + * definition) that the Spring {@code BeanFactory} uses to create a bean instance. + * + *

The {@link #postProcessMergedBeanDefinition} method may for example introspect + * the bean definition in order to prepare some cached metadata before post-processing + * actual instances of a bean. It is also allowed to modify the bean definition but + * only for definition properties which are actually intended for concurrent + * modification. Essentially, this only applies to operations defined on the + * {@link RootBeanDefinition} itself but not to the properties of its base classes. + * + * @author Juergen Hoeller + * @since 2.5 + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getMergedBeanDefinition + */ +public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor { + + /** + * Post-process the given merged bean definition for the specified bean. + * @param beanDefinition the merged bean definition for the bean + * @param beanType the actual type of the managed bean instance + * @param beanName the name of the bean + * @see AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors + */ + void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName); + + /** + * A notification that the bean definition for the specified name has been reset, + * and that this post-processor should clear any metadata for the affected bean. + *

The default implementation is empty. + * @param beanName the name of the bean + * @since 5.1 + * @see DefaultListableBeanFactory#resetBeanDefinition + */ + default void resetBeanDefinition(String beanName) { + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java new file mode 100644 index 0000000..24a2056 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2017 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.beans.factory.support; + +import java.lang.reflect.Method; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Object representing the override of a method on a managed object by the IoC + * container. + * + *

Note that the override mechanism is not intended as a generic + * means of inserting crosscutting code: use AOP for that. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 1.1 + */ +public abstract class MethodOverride implements BeanMetadataElement { + + private final String methodName; + + private boolean overloaded = true; + + @Nullable + private Object source; + + + /** + * Construct a new override for the given method. + * @param methodName the name of the method to override + */ + protected MethodOverride(String methodName) { + Assert.notNull(methodName, "Method name must not be null"); + this.methodName = methodName; + } + + + /** + * Return the name of the method to be overridden. + */ + public String getMethodName() { + return this.methodName; + } + + /** + * Set whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). + *

Default is {@code true}; can be switched to {@code false} to optimize + * runtime performance. + */ + protected void setOverloaded(boolean overloaded) { + this.overloaded = overloaded; + } + + /** + * Return whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). + */ + protected boolean isOverloaded() { + return this.overloaded; + } + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(@Nullable Object source) { + this.source = source; + } + + @Override + @Nullable + public Object getSource() { + return this.source; + } + + /** + * Subclasses must override this to indicate whether they match the + * given method. This allows for argument list checking as well as method + * name checking. + * @param method the method to check + * @return whether this override matches the given method + */ + public abstract boolean matches(Method method); + + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MethodOverride)) { + return false; + } + MethodOverride that = (MethodOverride) other; + return (ObjectUtils.nullSafeEquals(this.methodName, that.methodName) && + ObjectUtils.nullSafeEquals(this.source, that.source)); + } + + @Override + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(this.methodName); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.source); + return hashCode; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java new file mode 100644 index 0000000..5677d94 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2012 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.beans.factory.support; + +import java.lang.reflect.Method; + +/** + * Interface to be implemented by classes that can reimplement any method + * on an IoC-managed object: the Method Injection form of + * Dependency Injection. + * + *

Such methods may be (but need not be) abstract, in which case the + * container will create a concrete subclass to instantiate. + * + * @author Rod Johnson + * @since 1.1 + */ +public interface MethodReplacer { + + /** + * Reimplement the given method. + * @param obj the instance we're reimplementing the method for + * @param method the method to reimplement + * @param args arguments to the method + * @return return value for the method + */ + Object reimplement(Object obj, Method method, Object[] args) throws Throwable; + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/NullBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/NullBean.java new file mode 100644 index 0000000..6a87a11 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/NullBean.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2017 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.beans.factory.support; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.lang.Nullable; + +/** + * Internal representation of a null bean instance, e.g. for a {@code null} value + * returned from {@link FactoryBean#getObject()} or from a factory method. + * + *

Each such null bean is represented by a dedicated {@code NullBean} instance + * which are not equal to each other, uniquely differentiating each bean as returned + * from all variants of {@link org.springframework.beans.factory.BeanFactory#getBean}. + * However, each such instance will return {@code true} for {@code #equals(null)} + * and returns "null" from {@code #toString()}, which is how they can be tested + * externally (since this class itself is not public). + * + * @author Juergen Hoeller + * @since 5.0 + */ +final class NullBean { + + NullBean() { + } + + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj || obj == null); + } + + @Override + public int hashCode() { + return NullBean.class.hashCode(); + } + + @Override + public String toString() { + return "null"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java new file mode 100644 index 0000000..d70c804 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -0,0 +1,485 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A root bean definition represents the merged bean definition that backs + * a specific bean in a Spring BeanFactory at runtime. It might have been created + * from multiple original bean definitions that inherit from each other, + * typically registered as {@link GenericBeanDefinition GenericBeanDefinitions}. + * A root bean definition is essentially the 'unified' bean definition view at runtime. + * + *

Root bean definitions may also be used for registering individual bean definitions + * in the configuration phase. However, since Spring 2.5, the preferred way to register + * bean definitions programmatically is the {@link GenericBeanDefinition} class. + * GenericBeanDefinition has the advantage that it allows to dynamically define + * parent dependencies, not 'hard-coding' the role as a root bean definition. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see GenericBeanDefinition + * @see ChildBeanDefinition + */ +@SuppressWarnings("serial") +public class RootBeanDefinition extends AbstractBeanDefinition { + + @Nullable + private BeanDefinitionHolder decoratedDefinition; + + @Nullable + private AnnotatedElement qualifiedElement; + + /** Determines if the definition needs to be re-merged. */ + volatile boolean stale; + + boolean allowCaching = true; + + boolean isFactoryMethodUnique; + + @Nullable + volatile ResolvableType targetType; + + /** Package-visible field for caching the determined Class of a given bean definition. */ + @Nullable + volatile Class resolvedTargetType; + + /** Package-visible field for caching if the bean is a factory bean. */ + @Nullable + volatile Boolean isFactoryBean; + + /** Package-visible field for caching the return type of a generically typed factory method. */ + @Nullable + volatile ResolvableType factoryMethodReturnType; + + /** Package-visible field for caching a unique factory method candidate for introspection. */ + @Nullable + volatile Method factoryMethodToIntrospect; + + /** Common lock for the four constructor fields below. */ + final Object constructorArgumentLock = new Object(); + + /** Package-visible field for caching the resolved constructor or factory method. */ + @Nullable + Executable resolvedConstructorOrFactoryMethod; + + /** Package-visible field that marks the constructor arguments as resolved. */ + boolean constructorArgumentsResolved = false; + + /** Package-visible field for caching fully resolved constructor arguments. */ + @Nullable + Object[] resolvedConstructorArguments; + + /** Package-visible field for caching partly prepared constructor arguments. */ + @Nullable + Object[] preparedConstructorArguments; + + /** Common lock for the two post-processing fields below. */ + final Object postProcessingLock = new Object(); + + /** Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied. */ + boolean postProcessed = false; + + /** Package-visible field that indicates a before-instantiation post-processor having kicked in. */ + @Nullable + volatile Boolean beforeInstantiationResolved; + + @Nullable + private Set externallyManagedConfigMembers; + + @Nullable + private Set externallyManagedInitMethods; + + @Nullable + private Set externallyManagedDestroyMethods; + + + /** + * Create a new RootBeanDefinition, to be configured through its bean + * properties and configuration methods. + * @see #setBeanClass + * @see #setScope + * @see #setConstructorArgumentValues + * @see #setPropertyValues + */ + public RootBeanDefinition() { + super(); + } + + /** + * Create a new RootBeanDefinition for a singleton. + * @param beanClass the class of the bean to instantiate + * @see #setBeanClass + */ + public RootBeanDefinition(@Nullable Class beanClass) { + super(); + setBeanClass(beanClass); + } + + /** + * Create a new RootBeanDefinition for a singleton bean, constructing each instance + * through calling the given supplier (possibly a lambda or method reference). + * @param beanClass the class of the bean to instantiate + * @param instanceSupplier the supplier to construct a bean instance, + * as an alternative to a declaratively specified factory method + * @since 5.0 + * @see #setInstanceSupplier + */ + public RootBeanDefinition(@Nullable Class beanClass, @Nullable Supplier instanceSupplier) { + super(); + setBeanClass(beanClass); + setInstanceSupplier(instanceSupplier); + } + + /** + * Create a new RootBeanDefinition for a scoped bean, constructing each instance + * through calling the given supplier (possibly a lambda or method reference). + * @param beanClass the class of the bean to instantiate + * @param scope the name of the corresponding scope + * @param instanceSupplier the supplier to construct a bean instance, + * as an alternative to a declaratively specified factory method + * @since 5.0 + * @see #setInstanceSupplier + */ + public RootBeanDefinition(@Nullable Class beanClass, String scope, @Nullable Supplier instanceSupplier) { + super(); + setBeanClass(beanClass); + setScope(scope); + setInstanceSupplier(instanceSupplier); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * using the given autowire mode. + * @param beanClass the class of the bean to instantiate + * @param autowireMode by name or type, using the constants in this interface + * @param dependencyCheck whether to perform a dependency check for objects + * (not applicable to autowiring a constructor, thus ignored there) + */ + public RootBeanDefinition(@Nullable Class beanClass, int autowireMode, boolean dependencyCheck) { + super(); + setBeanClass(beanClass); + setAutowireMode(autowireMode); + if (dependencyCheck && getResolvedAutowireMode() != AUTOWIRE_CONSTRUCTOR) { + setDependencyCheck(DEPENDENCY_CHECK_OBJECTS); + } + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing constructor arguments and property values. + * @param beanClass the class of the bean to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public RootBeanDefinition(@Nullable Class beanClass, @Nullable ConstructorArgumentValues cargs, + @Nullable MutablePropertyValues pvs) { + + super(cargs, pvs); + setBeanClass(beanClass); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing constructor arguments and property values. + *

Takes a bean class name to avoid eager loading of the bean class. + * @param beanClassName the name of the class to instantiate + */ + public RootBeanDefinition(String beanClassName) { + setBeanClassName(beanClassName); + } + + /** + * Create a new RootBeanDefinition for a singleton, + * providing constructor arguments and property values. + *

Takes a bean class name to avoid eager loading of the bean class. + * @param beanClassName the name of the class to instantiate + * @param cargs the constructor argument values to apply + * @param pvs the property values to apply + */ + public RootBeanDefinition(String beanClassName, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + super(cargs, pvs); + setBeanClassName(beanClassName); + } + + /** + * Create a new RootBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + public RootBeanDefinition(RootBeanDefinition original) { + super(original); + this.decoratedDefinition = original.decoratedDefinition; + this.qualifiedElement = original.qualifiedElement; + this.allowCaching = original.allowCaching; + this.isFactoryMethodUnique = original.isFactoryMethodUnique; + this.targetType = original.targetType; + this.factoryMethodToIntrospect = original.factoryMethodToIntrospect; + } + + /** + * Create a new RootBeanDefinition as deep copy of the given + * bean definition. + * @param original the original bean definition to copy from + */ + RootBeanDefinition(BeanDefinition original) { + super(original); + } + + + @Override + public String getParentName() { + return null; + } + + @Override + public void setParentName(@Nullable String parentName) { + if (parentName != null) { + throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference"); + } + } + + /** + * Register a target definition that is being decorated by this bean definition. + */ + public void setDecoratedDefinition(@Nullable BeanDefinitionHolder decoratedDefinition) { + this.decoratedDefinition = decoratedDefinition; + } + + /** + * Return the target definition that is being decorated by this bean definition, if any. + */ + @Nullable + public BeanDefinitionHolder getDecoratedDefinition() { + return this.decoratedDefinition; + } + + /** + * Specify the {@link AnnotatedElement} defining qualifiers, + * to be used instead of the target class or factory method. + * @since 4.3.3 + * @see #setTargetType(ResolvableType) + * @see #getResolvedFactoryMethod() + */ + public void setQualifiedElement(@Nullable AnnotatedElement qualifiedElement) { + this.qualifiedElement = qualifiedElement; + } + + /** + * Return the {@link AnnotatedElement} defining qualifiers, if any. + * Otherwise, the factory method and target class will be checked. + * @since 4.3.3 + */ + @Nullable + public AnnotatedElement getQualifiedElement() { + return this.qualifiedElement; + } + + /** + * Specify a generics-containing target type of this bean definition, if known in advance. + * @since 4.3.3 + */ + public void setTargetType(ResolvableType targetType) { + this.targetType = targetType; + } + + /** + * Specify the target type of this bean definition, if known in advance. + * @since 3.2.2 + */ + public void setTargetType(@Nullable Class targetType) { + this.targetType = (targetType != null ? ResolvableType.forClass(targetType) : null); + } + + /** + * Return the target type of this bean definition, if known + * (either specified in advance or resolved on first instantiation). + * @since 3.2.2 + */ + @Nullable + public Class getTargetType() { + if (this.resolvedTargetType != null) { + return this.resolvedTargetType; + } + ResolvableType targetType = this.targetType; + return (targetType != null ? targetType.resolve() : null); + } + + /** + * Return a {@link ResolvableType} for this bean definition, + * either from runtime-cached type information or from configuration-time + * {@link #setTargetType(ResolvableType)} or {@link #setBeanClass(Class)}, + * also considering resolved factory method definitions. + * @since 5.1 + * @see #setTargetType(ResolvableType) + * @see #setBeanClass(Class) + * @see #setResolvedFactoryMethod(Method) + */ + @Override + public ResolvableType getResolvableType() { + ResolvableType targetType = this.targetType; + if (targetType != null) { + return targetType; + } + ResolvableType returnType = this.factoryMethodReturnType; + if (returnType != null) { + return returnType; + } + Method factoryMethod = this.factoryMethodToIntrospect; + if (factoryMethod != null) { + return ResolvableType.forMethodReturnType(factoryMethod); + } + return super.getResolvableType(); + } + + /** + * Determine preferred constructors to use for default construction, if any. + * Constructor arguments will be autowired if necessary. + * @return one or more preferred constructors, or {@code null} if none + * (in which case the regular no-arg default constructor will be called) + * @since 5.1 + */ + @Nullable + public Constructor[] getPreferredConstructors() { + return null; + } + + /** + * Specify a factory method name that refers to a non-overloaded method. + */ + public void setUniqueFactoryMethodName(String name) { + Assert.hasText(name, "Factory method name must not be empty"); + setFactoryMethodName(name); + this.isFactoryMethodUnique = true; + } + + /** + * Specify a factory method name that refers to an overloaded method. + * @since 5.2 + */ + public void setNonUniqueFactoryMethodName(String name) { + Assert.hasText(name, "Factory method name must not be empty"); + setFactoryMethodName(name); + this.isFactoryMethodUnique = false; + } + + /** + * Check whether the given candidate qualifies as a factory method. + */ + public boolean isFactoryMethod(Method candidate) { + return candidate.getName().equals(getFactoryMethodName()); + } + + /** + * Set a resolved Java Method for the factory method on this bean definition. + * @param method the resolved factory method, or {@code null} to reset it + * @since 5.2 + */ + public void setResolvedFactoryMethod(@Nullable Method method) { + this.factoryMethodToIntrospect = method; + } + + /** + * Return the resolved factory method as a Java Method object, if available. + * @return the factory method, or {@code null} if not found or not resolved yet + */ + @Nullable + public Method getResolvedFactoryMethod() { + return this.factoryMethodToIntrospect; + } + + public void registerExternallyManagedConfigMember(Member configMember) { + synchronized (this.postProcessingLock) { + if (this.externallyManagedConfigMembers == null) { + this.externallyManagedConfigMembers = new HashSet<>(1); + } + this.externallyManagedConfigMembers.add(configMember); + } + } + + public boolean isExternallyManagedConfigMember(Member configMember) { + synchronized (this.postProcessingLock) { + return (this.externallyManagedConfigMembers != null && + this.externallyManagedConfigMembers.contains(configMember)); + } + } + + public void registerExternallyManagedInitMethod(String initMethod) { + synchronized (this.postProcessingLock) { + if (this.externallyManagedInitMethods == null) { + this.externallyManagedInitMethods = new HashSet<>(1); + } + this.externallyManagedInitMethods.add(initMethod); + } + } + + public boolean isExternallyManagedInitMethod(String initMethod) { + synchronized (this.postProcessingLock) { + return (this.externallyManagedInitMethods != null && + this.externallyManagedInitMethods.contains(initMethod)); + } + } + + public void registerExternallyManagedDestroyMethod(String destroyMethod) { + synchronized (this.postProcessingLock) { + if (this.externallyManagedDestroyMethods == null) { + this.externallyManagedDestroyMethods = new HashSet<>(1); + } + this.externallyManagedDestroyMethods.add(destroyMethod); + } + } + + public boolean isExternallyManagedDestroyMethod(String destroyMethod) { + synchronized (this.postProcessingLock) { + return (this.externallyManagedDestroyMethods != null && + this.externallyManagedDestroyMethods.contains(destroyMethod)); + } + } + + + @Override + public RootBeanDefinition cloneBeanDefinition() { + return new RootBeanDefinition(this); + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof RootBeanDefinition && super.equals(other))); + } + + @Override + public String toString() { + return "Root bean: " + super.toString(); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java new file mode 100644 index 0000000..d2f70c4 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2012 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.beans.factory.support; + +import java.security.AccessControlContext; + +/** + * Provider of the security context of the code running inside the bean factory. + * + * @author Costin Leau + * @since 3.0 + */ +public interface SecurityContextProvider { + + /** + * Provides a security access control context relevant to a bean factory. + * @return bean factory security control context + */ + AccessControlContext getAccessControlContext(); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java new file mode 100644 index 0000000..e74ebc5 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleBeanDefinitionRegistry.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.SimpleAliasRegistry; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Simple implementation of the {@link BeanDefinitionRegistry} interface. + * Provides registry capabilities only, with no factory capabilities built in. + * Can for example be used for testing bean definition readers. + * + * @author Juergen Hoeller + * @since 2.5.2 + */ +public class SimpleBeanDefinitionRegistry extends SimpleAliasRegistry implements BeanDefinitionRegistry { + + /** Map of bean definition objects, keyed by bean name. */ + private final Map beanDefinitionMap = new ConcurrentHashMap<>(64); + + + @Override + public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) + throws BeanDefinitionStoreException { + + Assert.hasText(beanName, "'beanName' must not be empty"); + Assert.notNull(beanDefinition, "BeanDefinition must not be null"); + this.beanDefinitionMap.put(beanName, beanDefinition); + } + + @Override + public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + if (this.beanDefinitionMap.remove(beanName) == null) { + throw new NoSuchBeanDefinitionException(beanName); + } + } + + @Override + public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + BeanDefinition bd = this.beanDefinitionMap.get(beanName); + if (bd == null) { + throw new NoSuchBeanDefinitionException(beanName); + } + return bd; + } + + @Override + public boolean containsBeanDefinition(String beanName) { + return this.beanDefinitionMap.containsKey(beanName); + } + + @Override + public String[] getBeanDefinitionNames() { + return StringUtils.toStringArray(this.beanDefinitionMap.keySet()); + } + + @Override + public int getBeanDefinitionCount() { + return this.beanDefinitionMap.size(); + } + + @Override + public boolean isBeanNameInUse(String beanName) { + return isAlias(beanName) || containsBeanDefinition(beanName); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java new file mode 100644 index 0000000..b28fbff --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2018 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.beans.factory.support; + +import java.security.AccessControlContext; +import java.security.AccessController; + +import org.springframework.lang.Nullable; + +/** + * Simple {@link SecurityContextProvider} implementation. + * + * @author Costin Leau + * @since 3.0 + */ +public class SimpleSecurityContextProvider implements SecurityContextProvider { + + @Nullable + private final AccessControlContext acc; + + + /** + * Construct a new {@code SimpleSecurityContextProvider} instance. + *

The security context will be retrieved on each call from the current + * thread. + */ + public SimpleSecurityContextProvider() { + this(null); + } + + /** + * Construct a new {@code SimpleSecurityContextProvider} instance. + *

If the given control context is null, the security context will be + * retrieved on each call from the current thread. + * @param acc access control context (can be {@code null}) + * @see AccessController#getContext() + */ + public SimpleSecurityContextProvider(@Nullable AccessControlContext acc) { + this.acc = acc; + } + + + @Override + public AccessControlContext getAccessControlContext() { + return (this.acc != null ? this.acc : AccessController.getContext()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java new file mode 100644 index 0000000..a543012 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -0,0 +1,466 @@ +/* + * Copyright 2002-2020 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.beans.factory.support; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanIsNotAFactoryException; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.core.OrderComparator; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Static {@link org.springframework.beans.factory.BeanFactory} implementation + * which allows one to register existing singleton instances programmatically. + * + *

Does not have support for prototype beans or aliases. + * + *

Serves as an example for a simple implementation of the + * {@link org.springframework.beans.factory.ListableBeanFactory} interface, + * managing existing bean instances rather than creating new ones based on bean + * definitions, and not implementing any extended SPI interfaces (such as + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}). + * + *

For a full-fledged factory based on bean definitions, have a look at + * {@link DefaultListableBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 06.01.2003 + * @see DefaultListableBeanFactory + */ +public class StaticListableBeanFactory implements ListableBeanFactory { + + /** Map from bean name to bean instance. */ + private final Map beans; + + + /** + * Create a regular {@code StaticListableBeanFactory}, to be populated + * with singleton bean instances through {@link #addBean} calls. + */ + public StaticListableBeanFactory() { + this.beans = new LinkedHashMap<>(); + } + + /** + * Create a {@code StaticListableBeanFactory} wrapping the given {@code Map}. + *

Note that the given {@code Map} may be pre-populated with beans; + * or new, still allowing for beans to be registered via {@link #addBean}; + * or {@link java.util.Collections#emptyMap()} for a dummy factory which + * enforces operating against an empty set of beans. + * @param beans a {@code Map} for holding this factory's beans, with the + * bean name as key and the corresponding singleton object as value + * @since 4.3 + */ + public StaticListableBeanFactory(Map beans) { + Assert.notNull(beans, "Beans Map must not be null"); + this.beans = beans; + } + + + /** + * Add a new singleton bean. + *

Will overwrite any existing instance for the given name. + * @param name the name of the bean + * @param bean the bean instance + */ + public void addBean(String name, Object bean) { + this.beans.put(name, bean); + } + + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + @Override + public Object getBean(String name) throws BeansException { + String beanName = BeanFactoryUtils.transformedBeanName(name); + Object bean = this.beans.get(beanName); + + if (bean == null) { + throw new NoSuchBeanDefinitionException(beanName, + "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]"); + } + + // Don't let calling code try to dereference the + // bean factory if the bean isn't a factory + if (BeanFactoryUtils.isFactoryDereference(name) && !(bean instanceof FactoryBean)) { + throw new BeanIsNotAFactoryException(beanName, bean.getClass()); + } + + if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + try { + Object exposedObject = ((FactoryBean) bean).getObject(); + if (exposedObject == null) { + throw new BeanCreationException(beanName, "FactoryBean exposed null object"); + } + return exposedObject; + } + catch (Exception ex) { + throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); + } + } + else { + return bean; + } + } + + @Override + @SuppressWarnings("unchecked") + public T getBean(String name, @Nullable Class requiredType) throws BeansException { + Object bean = getBean(name); + if (requiredType != null && !requiredType.isInstance(bean)) { + throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); + } + return (T) bean; + } + + @Override + public Object getBean(String name, Object... args) throws BeansException { + if (!ObjectUtils.isEmpty(args)) { + throw new UnsupportedOperationException( + "StaticListableBeanFactory does not support explicit bean creation arguments"); + } + return getBean(name); + } + + @Override + public T getBean(Class requiredType) throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return getBean(beanNames[0], requiredType); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + throw new NoSuchBeanDefinitionException(requiredType); + } + } + + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { + if (!ObjectUtils.isEmpty(args)) { + throw new UnsupportedOperationException( + "StaticListableBeanFactory does not support explicit bean creation arguments"); + } + return getBean(requiredType); + } + + @Override + public ObjectProvider getBeanProvider(Class requiredType) throws BeansException { + return getBeanProvider(ResolvableType.forRawClass(requiredType), true); + } + + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType) { + return getBeanProvider(requiredType, true); + } + + @Override + public boolean containsBean(String name) { + return this.beans.containsKey(name); + } + + @Override + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + Object bean = getBean(name); + // In case of FactoryBean, return singleton status of created object. + if (bean instanceof FactoryBean) { + return ((FactoryBean) bean).isSingleton(); + } + return true; + } + + @Override + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + Object bean = getBean(name); + // In case of FactoryBean, return prototype status of created object. + return ((bean instanceof SmartFactoryBean && ((SmartFactoryBean) bean).isPrototype()) || + (bean instanceof FactoryBean && !((FactoryBean) bean).isSingleton())); + } + + @Override + public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { + Class type = getType(name); + return (type != null && typeToMatch.isAssignableFrom(type)); + } + + @Override + public boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException { + Class type = getType(name); + return (typeToMatch == null || (type != null && typeToMatch.isAssignableFrom(type))); + } + + @Override + public Class getType(String name) throws NoSuchBeanDefinitionException { + return getType(name, true); + } + + @Override + public Class getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { + String beanName = BeanFactoryUtils.transformedBeanName(name); + + Object bean = this.beans.get(beanName); + if (bean == null) { + throw new NoSuchBeanDefinitionException(beanName, + "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]"); + } + + if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + return ((FactoryBean) bean).getObjectType(); + } + return bean.getClass(); + } + + @Override + public String[] getAliases(String name) { + return new String[0]; + } + + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + @Override + public boolean containsBeanDefinition(String name) { + return this.beans.containsKey(name); + } + + @Override + public int getBeanDefinitionCount() { + return this.beans.size(); + } + + @Override + public String[] getBeanDefinitionNames() { + return StringUtils.toStringArray(this.beans.keySet()); + } + + @Override + public ObjectProvider getBeanProvider(Class requiredType, boolean allowEagerInit) { + return getBeanProvider(ResolvableType.forRawClass(requiredType), allowEagerInit); + } + + @SuppressWarnings("unchecked") + @Override + public ObjectProvider getBeanProvider(ResolvableType requiredType, boolean allowEagerInit) { + return new ObjectProvider() { + @Override + public T getObject() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0], requiredType); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + throw new NoSuchBeanDefinitionException(requiredType); + } + } + @Override + public T getObject(Object... args) throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0], args); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + throw new NoSuchBeanDefinitionException(requiredType); + } + } + @Override + @Nullable + public T getIfAvailable() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0]); + } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else { + return null; + } + } + @Override + @Nullable + public T getIfUnique() throws BeansException { + String[] beanNames = getBeanNamesForType(requiredType); + if (beanNames.length == 1) { + return (T) getBean(beanNames[0]); + } + else { + return null; + } + } + @Override + public Stream stream() { + return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name)); + } + @Override + public Stream orderedStream() { + return stream().sorted(OrderComparator.INSTANCE); + } + }; + } + + @Override + public String[] getBeanNamesForType(@Nullable ResolvableType type) { + return getBeanNamesForType(type, true, true); + } + + @Override + public String[] getBeanNamesForType(@Nullable ResolvableType type, + boolean includeNonSingletons, boolean allowEagerInit) { + + Class resolved = (type != null ? type.resolve() : null); + boolean isFactoryType = resolved != null && FactoryBean.class.isAssignableFrom(resolved); + List matches = new ArrayList<>(); + + for (Map.Entry entry : this.beans.entrySet()) { + String beanName = entry.getKey(); + Object beanInstance = entry.getValue(); + if (beanInstance instanceof FactoryBean && !isFactoryType) { + FactoryBean factoryBean = (FactoryBean) beanInstance; + Class objectType = factoryBean.getObjectType(); + if ((includeNonSingletons || factoryBean.isSingleton()) && + objectType != null && (type == null || type.isAssignableFrom(objectType))) { + matches.add(beanName); + } + } + else { + if (type == null || type.isInstance(beanInstance)) { + matches.add(beanName); + } + } + } + return StringUtils.toStringArray(matches); + } + + @Override + public String[] getBeanNamesForType(@Nullable Class type) { + return getBeanNamesForType(ResolvableType.forClass(type)); + } + + @Override + public String[] getBeanNamesForType(@Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) { + return getBeanNamesForType(ResolvableType.forClass(type), includeNonSingletons, allowEagerInit); + } + + @Override + public Map getBeansOfType(@Nullable Class type) throws BeansException { + return getBeansOfType(type, true, true); + } + + @Override + @SuppressWarnings("unchecked") + public Map getBeansOfType(@Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type)); + Map matches = new LinkedHashMap<>(); + + for (Map.Entry entry : this.beans.entrySet()) { + String beanName = entry.getKey(); + Object beanInstance = entry.getValue(); + // Is bean a FactoryBean? + if (beanInstance instanceof FactoryBean && !isFactoryType) { + // Match object created by FactoryBean. + FactoryBean factory = (FactoryBean) beanInstance; + Class objectType = factory.getObjectType(); + if ((includeNonSingletons || factory.isSingleton()) && + objectType != null && (type == null || type.isAssignableFrom(objectType))) { + matches.put(beanName, getBean(beanName, type)); + } + } + else { + if (type == null || type.isInstance(beanInstance)) { + // If type to match is FactoryBean, return FactoryBean itself. + // Else, return bean instance. + if (isFactoryType) { + beanName = FACTORY_BEAN_PREFIX + beanName; + } + matches.put(beanName, (T) beanInstance); + } + } + } + return matches; + } + + @Override + public String[] getBeanNamesForAnnotation(Class annotationType) { + List results = new ArrayList<>(); + for (String beanName : this.beans.keySet()) { + if (findAnnotationOnBean(beanName, annotationType) != null) { + results.add(beanName); + } + } + return StringUtils.toStringArray(results); + } + + @Override + public Map getBeansWithAnnotation(Class annotationType) + throws BeansException { + + Map results = new LinkedHashMap<>(); + for (String beanName : this.beans.keySet()) { + if (findAnnotationOnBean(beanName, annotationType) != null) { + results.put(beanName, getBean(beanName)); + } + } + return results; + } + + @Override + @Nullable + public A findAnnotationOnBean(String beanName, Class annotationType) + throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName); + return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/package-info.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/package-info.java new file mode 100644 index 0000000..0a5599d --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/package-info.java @@ -0,0 +1,10 @@ +/** + * Classes supporting the {@code org.springframework.beans.factory} package. + * Contains abstract base classes for {@code BeanFactory} implementations. + */ +@NonNullApi +@NonNullFields +package org.springframework.beans.factory.support; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java new file mode 100644 index 0000000..3d50568 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2018 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.beans.factory.wiring; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Convenient base class for bean configurers that can perform Dependency Injection + * on objects (however they may be created). Typically subclassed by AspectJ aspects. + * + *

Subclasses may also need a custom metadata resolution strategy, in the + * {@link BeanWiringInfoResolver} interface. The default implementation looks for + * a bean with the same name as the fully-qualified class name. (This is the default + * name of the bean in a Spring XML file if the '{@code id}' attribute is not used.) + + * @author Rob Harrop + * @author Rod Johnson + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 2.0 + * @see #setBeanWiringInfoResolver + * @see ClassNameBeanWiringInfoResolver + */ +public class BeanConfigurerSupport implements BeanFactoryAware, InitializingBean, DisposableBean { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + @Nullable + private volatile BeanWiringInfoResolver beanWiringInfoResolver; + + @Nullable + private volatile ConfigurableListableBeanFactory beanFactory; + + + /** + * Set the {@link BeanWiringInfoResolver} to use. + *

The default behavior is to look for a bean with the same name as the class. + * As an alternative, consider using annotation-driven bean wiring. + * @see ClassNameBeanWiringInfoResolver + * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver + */ + public void setBeanWiringInfoResolver(BeanWiringInfoResolver beanWiringInfoResolver) { + Assert.notNull(beanWiringInfoResolver, "BeanWiringInfoResolver must not be null"); + this.beanWiringInfoResolver = beanWiringInfoResolver; + } + + /** + * Set the {@link BeanFactory} in which this aspect must configure beans. + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalArgumentException( + "Bean configurer aspect needs to run in a ConfigurableListableBeanFactory: " + beanFactory); + } + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + if (this.beanWiringInfoResolver == null) { + this.beanWiringInfoResolver = createDefaultBeanWiringInfoResolver(); + } + } + + /** + * Create the default BeanWiringInfoResolver to be used if none was + * specified explicitly. + *

The default implementation builds a {@link ClassNameBeanWiringInfoResolver}. + * @return the default BeanWiringInfoResolver (never {@code null}) + */ + @Nullable + protected BeanWiringInfoResolver createDefaultBeanWiringInfoResolver() { + return new ClassNameBeanWiringInfoResolver(); + } + + /** + * Check that a {@link BeanFactory} has been set. + */ + @Override + public void afterPropertiesSet() { + Assert.notNull(this.beanFactory, "BeanFactory must be set"); + } + + /** + * Release references to the {@link BeanFactory} and + * {@link BeanWiringInfoResolver} when the container is destroyed. + */ + @Override + public void destroy() { + this.beanFactory = null; + this.beanWiringInfoResolver = null; + } + + + /** + * Configure the bean instance. + *

Subclasses can override this to provide custom configuration logic. + * Typically called by an aspect, for all bean instances matched by a pointcut. + * @param beanInstance the bean instance to configure (must not be {@code null}) + */ + public void configureBean(Object beanInstance) { + if (this.beanFactory == null) { + if (logger.isDebugEnabled()) { + logger.debug("BeanFactory has not been set on " + ClassUtils.getShortName(getClass()) + ": " + + "Make sure this configurer runs in a Spring container. Unable to configure bean of type [" + + ClassUtils.getDescriptiveType(beanInstance) + "]. Proceeding without injection."); + } + return; + } + + BeanWiringInfoResolver bwiResolver = this.beanWiringInfoResolver; + Assert.state(bwiResolver != null, "No BeanWiringInfoResolver available"); + BeanWiringInfo bwi = bwiResolver.resolveWiringInfo(beanInstance); + if (bwi == null) { + // Skip the bean if no wiring info given. + return; + } + + + ConfigurableListableBeanFactory beanFactory = this.beanFactory; + Assert.state(beanFactory != null, "No BeanFactory available"); + try { + String beanName = bwi.getBeanName(); + if (bwi.indicatesAutowiring() || (bwi.isDefaultBeanName() && beanName != null && + !beanFactory.containsBean(beanName))) { + // Perform autowiring (also applying standard factory / post-processor callbacks). + beanFactory.autowireBeanProperties(beanInstance, bwi.getAutowireMode(), bwi.getDependencyCheck()); + beanFactory.initializeBean(beanInstance, (beanName != null ? beanName : "")); + } + else { + // Perform explicit wiring based on the specified bean definition. + beanFactory.configureBean(beanInstance, (beanName != null ? beanName : "")); + } + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + String bceBeanName = bce.getBeanName(); + if (bceBeanName != null && beanFactory.isCurrentlyInCreation(bceBeanName)) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to create target bean '" + bce.getBeanName() + + "' while configuring object of type [" + beanInstance.getClass().getName() + + "] - probably due to a circular reference. This is a common startup situation " + + "and usually not fatal. Proceeding without injection. Original exception: " + ex); + } + return; + } + } + throw ex; + } + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java new file mode 100644 index 0000000..ac8e634 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfo.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2017 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.beans.factory.wiring; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Holder for bean wiring metadata information about a particular class. Used in + * conjunction with the {@link org.springframework.beans.factory.annotation.Configurable} + * annotation and the AspectJ {@code AnnotationBeanConfigurerAspect}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see BeanWiringInfoResolver + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory + * @see org.springframework.beans.factory.annotation.Configurable + */ +public class BeanWiringInfo { + + /** + * Constant that indicates autowiring bean properties by name. + * @see #BeanWiringInfo(int, boolean) + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME + */ + public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + + /** + * Constant that indicates autowiring bean properties by type. + * @see #BeanWiringInfo(int, boolean) + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE + */ + public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; + + + @Nullable + private String beanName; + + private boolean isDefaultBeanName = false; + + private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_NO; + + private boolean dependencyCheck = false; + + + /** + * Create a default BeanWiringInfo that suggests plain initialization of + * factory and post-processor callbacks that the bean class may expect. + */ + public BeanWiringInfo() { + } + + /** + * Create a new BeanWiringInfo that points to the given bean name. + * @param beanName the name of the bean definition to take the property values from + * @throws IllegalArgumentException if the supplied beanName is {@code null}, + * is empty, or consists wholly of whitespace + */ + public BeanWiringInfo(String beanName) { + this(beanName, false); + } + + /** + * Create a new BeanWiringInfo that points to the given bean name. + * @param beanName the name of the bean definition to take the property values from + * @param isDefaultBeanName whether the given bean name is a suggested + * default bean name, not necessarily matching an actual bean definition + * @throws IllegalArgumentException if the supplied beanName is {@code null}, + * is empty, or consists wholly of whitespace + */ + public BeanWiringInfo(String beanName, boolean isDefaultBeanName) { + Assert.hasText(beanName, "'beanName' must not be empty"); + this.beanName = beanName; + this.isDefaultBeanName = isDefaultBeanName; + } + + /** + * Create a new BeanWiringInfo that indicates autowiring. + * @param autowireMode one of the constants {@link #AUTOWIRE_BY_NAME} / + * {@link #AUTOWIRE_BY_TYPE} + * @param dependencyCheck whether to perform a dependency check for object + * references in the bean instance (after autowiring) + * @throws IllegalArgumentException if the supplied {@code autowireMode} + * is not one of the allowed values + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + */ + public BeanWiringInfo(int autowireMode, boolean dependencyCheck) { + if (autowireMode != AUTOWIRE_BY_NAME && autowireMode != AUTOWIRE_BY_TYPE) { + throw new IllegalArgumentException("Only constants AUTOWIRE_BY_NAME and AUTOWIRE_BY_TYPE supported"); + } + this.autowireMode = autowireMode; + this.dependencyCheck = dependencyCheck; + } + + + /** + * Return whether this BeanWiringInfo indicates autowiring. + */ + public boolean indicatesAutowiring() { + return (this.beanName == null); + } + + /** + * Return the specific bean name that this BeanWiringInfo points to, if any. + */ + @Nullable + public String getBeanName() { + return this.beanName; + } + + /** + * Return whether the specific bean name is a suggested default bean name, + * not necessarily matching an actual bean definition in the factory. + */ + public boolean isDefaultBeanName() { + return this.isDefaultBeanName; + } + + /** + * Return one of the constants {@link #AUTOWIRE_BY_NAME} / + * {@link #AUTOWIRE_BY_TYPE}, if autowiring is indicated. + */ + public int getAutowireMode() { + return this.autowireMode; + } + + /** + * Return whether to perform a dependency check for object references + * in the bean instance (after autowiring). + */ + public boolean getDependencyCheck() { + return this.dependencyCheck; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java new file mode 100644 index 0000000..f6dc9bf --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanWiringInfoResolver.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2012 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.beans.factory.wiring; + +import org.springframework.lang.Nullable; + +/** + * Strategy interface to be implemented by objects than can resolve bean name + * information, given a newly instantiated bean object. Invocations to the + * {@link #resolveWiringInfo} method on this interface will be driven by + * the AspectJ pointcut in the relevant concrete aspect. + * + *

Metadata resolution strategy can be pluggable. A good default is + * {@link ClassNameBeanWiringInfoResolver}, which uses the fully-qualified + * class name as bean name. + * + * @author Rod Johnson + * @since 2.0 + * @see BeanWiringInfo + * @see ClassNameBeanWiringInfoResolver + * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver + */ +public interface BeanWiringInfoResolver { + + /** + * Resolve the BeanWiringInfo for the given bean instance. + * @param beanInstance the bean instance to resolve info for + * @return the BeanWiringInfo, or {@code null} if not found + */ + @Nullable + BeanWiringInfo resolveWiringInfo(Object beanInstance); + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java new file mode 100644 index 0000000..66e6f33 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolver.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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.beans.factory.wiring; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Simple default implementation of the {@link BeanWiringInfoResolver} interface, + * looking for a bean with the same name as the fully-qualified class name. + * This matches the default name of the bean in a Spring XML file if the + * bean tag's "id" attribute is not used. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class ClassNameBeanWiringInfoResolver implements BeanWiringInfoResolver { + + @Override + public BeanWiringInfo resolveWiringInfo(Object beanInstance) { + Assert.notNull(beanInstance, "Bean instance must not be null"); + return new BeanWiringInfo(ClassUtils.getUserClass(beanInstance).getName(), true); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/package-info.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/package-info.java new file mode 100644 index 0000000..c069d7d --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/package-info.java @@ -0,0 +1,10 @@ +/** + * Mechanism to determine bean wiring metadata from a bean instance. + * Foundation for aspect-driven bean configuration. + */ +@NonNullApi +@NonNullFields +package org.springframework.beans.factory.wiring; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields;