001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.support;
018
019import java.security.AccessControlContext;
020import java.security.AccessController;
021import java.security.PrivilegedAction;
022import java.security.PrivilegedActionException;
023import java.security.PrivilegedExceptionAction;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.BeanCreationException;
029import org.springframework.beans.factory.BeanCurrentlyInCreationException;
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.FactoryBeanNotInitializedException;
032import org.springframework.lang.Nullable;
033
034/**
035 * Support base class for singleton registries which need to handle
036 * {@link org.springframework.beans.factory.FactoryBean} instances,
037 * integrated with {@link DefaultSingletonBeanRegistry}'s singleton management.
038 *
039 * <p>Serves as base class for {@link AbstractBeanFactory}.
040 *
041 * @author Juergen Hoeller
042 * @since 2.5.1
043 */
044public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
045
046        /** Cache of singleton objects created by FactoryBeans: FactoryBean name to object. */
047        private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
048
049
050        /**
051         * Determine the type for the given FactoryBean.
052         * @param factoryBean the FactoryBean instance to check
053         * @return the FactoryBean's object type,
054         * or {@code null} if the type cannot be determined yet
055         */
056        @Nullable
057        protected Class<?> getTypeForFactoryBean(FactoryBean<?> factoryBean) {
058                try {
059                        if (System.getSecurityManager() != null) {
060                                return AccessController.doPrivileged(
061                                                (PrivilegedAction<Class<?>>) factoryBean::getObjectType, getAccessControlContext());
062                        }
063                        else {
064                                return factoryBean.getObjectType();
065                        }
066                }
067                catch (Throwable ex) {
068                        // Thrown from the FactoryBean's getObjectType implementation.
069                        logger.info("FactoryBean threw exception from getObjectType, despite the contract saying " +
070                                        "that it should return null if the type of its object cannot be determined yet", ex);
071                        return null;
072                }
073        }
074
075        /**
076         * Obtain an object to expose from the given FactoryBean, if available
077         * in cached form. Quick check for minimal synchronization.
078         * @param beanName the name of the bean
079         * @return the object obtained from the FactoryBean,
080         * or {@code null} if not available
081         */
082        @Nullable
083        protected Object getCachedObjectForFactoryBean(String beanName) {
084                return this.factoryBeanObjectCache.get(beanName);
085        }
086
087        /**
088         * Obtain an object to expose from the given FactoryBean.
089         * @param factory the FactoryBean instance
090         * @param beanName the name of the bean
091         * @param shouldPostProcess whether the bean is subject to post-processing
092         * @return the object obtained from the FactoryBean
093         * @throws BeanCreationException if FactoryBean object creation failed
094         * @see org.springframework.beans.factory.FactoryBean#getObject()
095         */
096        protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
097                if (factory.isSingleton() && containsSingleton(beanName)) {
098                        synchronized (getSingletonMutex()) {
099                                Object object = this.factoryBeanObjectCache.get(beanName);
100                                if (object == null) {
101                                        object = doGetObjectFromFactoryBean(factory, beanName);
102                                        // Only post-process and store if not put there already during getObject() call above
103                                        // (e.g. because of circular reference processing triggered by custom getBean calls)
104                                        Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
105                                        if (alreadyThere != null) {
106                                                object = alreadyThere;
107                                        }
108                                        else {
109                                                if (shouldPostProcess) {
110                                                        if (isSingletonCurrentlyInCreation(beanName)) {
111                                                                // Temporarily return non-post-processed object, not storing it yet..
112                                                                return object;
113                                                        }
114                                                        beforeSingletonCreation(beanName);
115                                                        try {
116                                                                object = postProcessObjectFromFactoryBean(object, beanName);
117                                                        }
118                                                        catch (Throwable ex) {
119                                                                throw new BeanCreationException(beanName,
120                                                                                "Post-processing of FactoryBean's singleton object failed", ex);
121                                                        }
122                                                        finally {
123                                                                afterSingletonCreation(beanName);
124                                                        }
125                                                }
126                                                if (containsSingleton(beanName)) {
127                                                        this.factoryBeanObjectCache.put(beanName, object);
128                                                }
129                                        }
130                                }
131                                return object;
132                        }
133                }
134                else {
135                        Object object = doGetObjectFromFactoryBean(factory, beanName);
136                        if (shouldPostProcess) {
137                                try {
138                                        object = postProcessObjectFromFactoryBean(object, beanName);
139                                }
140                                catch (Throwable ex) {
141                                        throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
142                                }
143                        }
144                        return object;
145                }
146        }
147
148        /**
149         * Obtain an object to expose from the given FactoryBean.
150         * @param factory the FactoryBean instance
151         * @param beanName the name of the bean
152         * @return the object obtained from the FactoryBean
153         * @throws BeanCreationException if FactoryBean object creation failed
154         * @see org.springframework.beans.factory.FactoryBean#getObject()
155         */
156        private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
157                Object object;
158                try {
159                        if (System.getSecurityManager() != null) {
160                                AccessControlContext acc = getAccessControlContext();
161                                try {
162                                        object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
163                                }
164                                catch (PrivilegedActionException pae) {
165                                        throw pae.getException();
166                                }
167                        }
168                        else {
169                                object = factory.getObject();
170                        }
171                }
172                catch (FactoryBeanNotInitializedException ex) {
173                        throw new BeanCurrentlyInCreationException(beanName, ex.toString());
174                }
175                catch (Throwable ex) {
176                        throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
177                }
178
179                // Do not accept a null value for a FactoryBean that's not fully
180                // initialized yet: Many FactoryBeans just return null then.
181                if (object == null) {
182                        if (isSingletonCurrentlyInCreation(beanName)) {
183                                throw new BeanCurrentlyInCreationException(
184                                                beanName, "FactoryBean which is currently in creation returned null from getObject");
185                        }
186                        object = new NullBean();
187                }
188                return object;
189        }
190
191        /**
192         * Post-process the given object that has been obtained from the FactoryBean.
193         * The resulting object will get exposed for bean references.
194         * <p>The default implementation simply returns the given object as-is.
195         * Subclasses may override this, for example, to apply post-processors.
196         * @param object the object obtained from the FactoryBean.
197         * @param beanName the name of the bean
198         * @return the object to expose
199         * @throws org.springframework.beans.BeansException if any post-processing failed
200         */
201        protected Object postProcessObjectFromFactoryBean(Object object, String beanName) throws BeansException {
202                return object;
203        }
204
205        /**
206         * Get a FactoryBean for the given bean if possible.
207         * @param beanName the name of the bean
208         * @param beanInstance the corresponding bean instance
209         * @return the bean instance as FactoryBean
210         * @throws BeansException if the given bean cannot be exposed as a FactoryBean
211         */
212        protected FactoryBean<?> getFactoryBean(String beanName, Object beanInstance) throws BeansException {
213                if (!(beanInstance instanceof FactoryBean)) {
214                        throw new BeanCreationException(beanName,
215                                        "Bean instance of type [" + beanInstance.getClass() + "] is not a FactoryBean");
216                }
217                return (FactoryBean<?>) beanInstance;
218        }
219
220        /**
221         * Overridden to clear the FactoryBean object cache as well.
222         */
223        @Override
224        protected void removeSingleton(String beanName) {
225                synchronized (getSingletonMutex()) {
226                        super.removeSingleton(beanName);
227                        this.factoryBeanObjectCache.remove(beanName);
228                }
229        }
230
231        /**
232         * Overridden to clear the FactoryBean object cache as well.
233         */
234        @Override
235        protected void clearSingletonCache() {
236                synchronized (getSingletonMutex()) {
237                        super.clearSingletonCache();
238                        this.factoryBeanObjectCache.clear();
239                }
240        }
241
242        /**
243         * Return the security context for this bean factory. If a security manager
244         * is set, interaction with the user code will be executed using the privileged
245         * of the security context returned by this method.
246         * @see AccessController#getContext()
247         */
248        protected AccessControlContext getAccessControlContext() {
249                return AccessController.getContext();
250        }
251
252}