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.lang.annotation.Annotation;
020import java.util.ArrayList;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.springframework.beans.BeansException;
026import org.springframework.beans.factory.BeanCreationException;
027import org.springframework.beans.factory.BeanFactoryUtils;
028import org.springframework.beans.factory.BeanIsNotAFactoryException;
029import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.ListableBeanFactory;
032import org.springframework.beans.factory.NoSuchBeanDefinitionException;
033import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
034import org.springframework.beans.factory.SmartFactoryBean;
035import org.springframework.core.ResolvableType;
036import org.springframework.core.annotation.AnnotationUtils;
037import org.springframework.util.Assert;
038import org.springframework.util.ObjectUtils;
039import org.springframework.util.StringUtils;
040
041/**
042 * Static {@link org.springframework.beans.factory.BeanFactory} implementation
043 * which allows one to register existing singleton instances programmatically.
044 *
045 * <p>Does not have support for prototype beans or aliases.
046 *
047 * <p>Serves as an example for a simple implementation of the
048 * {@link org.springframework.beans.factory.ListableBeanFactory} interface,
049 * managing existing bean instances rather than creating new ones based on bean
050 * definitions, and not implementing any extended SPI interfaces (such as
051 * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}).
052 *
053 * <p>For a full-fledged factory based on bean definitions, have a look at
054 * {@link DefaultListableBeanFactory}.
055 *
056 * @author Rod Johnson
057 * @author Juergen Hoeller
058 * @author Sam Brannen
059 * @since 06.01.2003
060 * @see DefaultListableBeanFactory
061 */
062public class StaticListableBeanFactory implements ListableBeanFactory {
063
064        /** Map from bean name to bean instance */
065        private final Map<String, Object> beans;
066
067
068        /**
069         * Create a regular {@code StaticListableBeanFactory}, to be populated
070         * with singleton bean instances through {@link #addBean} calls.
071         */
072        public StaticListableBeanFactory() {
073                this.beans = new LinkedHashMap<String, Object>();
074        }
075
076        /**
077         * Create a {@code StaticListableBeanFactory} wrapping the given {@code Map}.
078         * <p>Note that the given {@code Map} may be pre-populated with beans;
079         * or new, still allowing for beans to be registered via {@link #addBean};
080         * or {@link java.util.Collections#emptyMap()} for a dummy factory which
081         * enforces operating against an empty set of beans.
082         * @param beans a {@code Map} for holding this factory's beans, with the
083         * bean name as key and the corresponding singleton object as value
084         * @since 4.3
085         */
086        public StaticListableBeanFactory(Map<String, Object> beans) {
087                Assert.notNull(beans, "Beans Map must not be null");
088                this.beans = beans;
089        }
090
091
092        /**
093         * Add a new singleton bean.
094         * <p>Will overwrite any existing instance for the given name.
095         * @param name the name of the bean
096         * @param bean the bean instance
097         */
098        public void addBean(String name, Object bean) {
099                this.beans.put(name, bean);
100        }
101
102
103        //---------------------------------------------------------------------
104        // Implementation of BeanFactory interface
105        //---------------------------------------------------------------------
106
107        @Override
108        public Object getBean(String name) throws BeansException {
109                String beanName = BeanFactoryUtils.transformedBeanName(name);
110                Object bean = this.beans.get(beanName);
111
112                if (bean == null) {
113                        throw new NoSuchBeanDefinitionException(beanName,
114                                        "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]");
115                }
116
117                // Don't let calling code try to dereference the
118                // bean factory if the bean isn't a factory
119                if (BeanFactoryUtils.isFactoryDereference(name) && !(bean instanceof FactoryBean)) {
120                        throw new BeanIsNotAFactoryException(beanName, bean.getClass());
121                }
122
123                if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
124                        try {
125                                return ((FactoryBean<?>) bean).getObject();
126                        }
127                        catch (Exception ex) {
128                                throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
129                        }
130                }
131                else {
132                        return bean;
133                }
134        }
135
136        @Override
137        @SuppressWarnings("unchecked")
138        public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
139                Object bean = getBean(name);
140                if (requiredType != null && !requiredType.isInstance(bean)) {
141                        throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
142                }
143                return (T) bean;
144        }
145
146        @Override
147        public Object getBean(String name, Object... args) throws BeansException {
148                if (!ObjectUtils.isEmpty(args)) {
149                        throw new UnsupportedOperationException(
150                                        "StaticListableBeanFactory does not support explicit bean creation arguments");
151                }
152                return getBean(name);
153        }
154
155        @Override
156        public <T> T getBean(Class<T> requiredType) throws BeansException {
157                String[] beanNames = getBeanNamesForType(requiredType);
158                if (beanNames.length == 1) {
159                        return getBean(beanNames[0], requiredType);
160                }
161                else if (beanNames.length > 1) {
162                        throw new NoUniqueBeanDefinitionException(requiredType, beanNames);
163                }
164                else {
165                        throw new NoSuchBeanDefinitionException(requiredType);
166                }
167        }
168
169        @Override
170        public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
171                if (!ObjectUtils.isEmpty(args)) {
172                        throw new UnsupportedOperationException(
173                                        "StaticListableBeanFactory does not support explicit bean creation arguments");
174                }
175                return getBean(requiredType);
176        }
177
178        @Override
179        public boolean containsBean(String name) {
180                return this.beans.containsKey(name);
181        }
182
183        @Override
184        public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
185                Object bean = getBean(name);
186                // In case of FactoryBean, return singleton status of created object.
187                if (bean instanceof FactoryBean) {
188                        return ((FactoryBean<?>) bean).isSingleton();
189                }
190                return true;
191        }
192
193        @Override
194        public boolean isPrototype(String name) throws NoSuchBeanDefinitionException {
195                Object bean = getBean(name);
196                // In case of FactoryBean, return prototype status of created object.
197                return ((bean instanceof SmartFactoryBean && ((SmartFactoryBean<?>) bean).isPrototype()) ||
198                                (bean instanceof FactoryBean && !((FactoryBean<?>) bean).isSingleton()));
199        }
200
201        @Override
202        public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
203                Class<?> type = getType(name);
204                return (type != null && typeToMatch.isAssignableFrom(type));
205        }
206
207        @Override
208        public boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException {
209                Class<?> type = getType(name);
210                return (typeToMatch == null || (type != null && typeToMatch.isAssignableFrom(type)));
211        }
212
213        @Override
214        public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
215                String beanName = BeanFactoryUtils.transformedBeanName(name);
216
217                Object bean = this.beans.get(beanName);
218                if (bean == null) {
219                        throw new NoSuchBeanDefinitionException(beanName,
220                                        "Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]");
221                }
222
223                if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
224                        // If it's a FactoryBean, we want to look at what it creates, not the factory class.
225                        return ((FactoryBean<?>) bean).getObjectType();
226                }
227                return bean.getClass();
228        }
229
230        @Override
231        public String[] getAliases(String name) {
232                return new String[0];
233        }
234
235
236        //---------------------------------------------------------------------
237        // Implementation of ListableBeanFactory interface
238        //---------------------------------------------------------------------
239
240        @Override
241        public boolean containsBeanDefinition(String name) {
242                return this.beans.containsKey(name);
243        }
244
245        @Override
246        public int getBeanDefinitionCount() {
247                return this.beans.size();
248        }
249
250        @Override
251        public String[] getBeanDefinitionNames() {
252                return StringUtils.toStringArray(this.beans.keySet());
253        }
254
255        @Override
256        public String[] getBeanNamesForType(ResolvableType type) {
257                boolean isFactoryType = false;
258                if (type != null) {
259                        Class<?> resolved = type.resolve();
260                        if (resolved != null && FactoryBean.class.isAssignableFrom(resolved)) {
261                                isFactoryType = true;
262                        }
263                }
264                List<String> matches = new ArrayList<String>();
265                for (Map.Entry<String, Object> entry : this.beans.entrySet()) {
266                        String name = entry.getKey();
267                        Object beanInstance = entry.getValue();
268                        if (beanInstance instanceof FactoryBean && !isFactoryType) {
269                                Class<?> objectType = ((FactoryBean<?>) beanInstance).getObjectType();
270                                if (objectType != null && (type == null || type.isAssignableFrom(objectType))) {
271                                        matches.add(name);
272                                }
273                        }
274                        else {
275                                if (type == null || type.isInstance(beanInstance)) {
276                                        matches.add(name);
277                                }
278                        }
279                }
280                return StringUtils.toStringArray(matches);
281        }
282
283        @Override
284        public String[] getBeanNamesForType(Class<?> type) {
285                return getBeanNamesForType(ResolvableType.forClass(type));
286        }
287
288        @Override
289        public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
290                return getBeanNamesForType(ResolvableType.forClass(type));
291        }
292
293        @Override
294        public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
295                return getBeansOfType(type, true, true);
296        }
297
298        @Override
299        @SuppressWarnings("unchecked")
300        public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
301                        throws BeansException {
302
303                boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type));
304                Map<String, T> matches = new LinkedHashMap<String, T>();
305
306                for (Map.Entry<String, Object> entry : this.beans.entrySet()) {
307                        String beanName = entry.getKey();
308                        Object beanInstance = entry.getValue();
309                        // Is bean a FactoryBean?
310                        if (beanInstance instanceof FactoryBean && !isFactoryType) {
311                                // Match object created by FactoryBean.
312                                FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
313                                Class<?> objectType = factory.getObjectType();
314                                if ((includeNonSingletons || factory.isSingleton()) &&
315                                                objectType != null && (type == null || type.isAssignableFrom(objectType))) {
316                                        matches.put(beanName, getBean(beanName, type));
317                                }
318                        }
319                        else {
320                                if (type == null || type.isInstance(beanInstance)) {
321                                        // If type to match is FactoryBean, return FactoryBean itself.
322                                        // Else, return bean instance.
323                                        if (isFactoryType) {
324                                                beanName = FACTORY_BEAN_PREFIX + beanName;
325                                        }
326                                        matches.put(beanName, (T) beanInstance);
327                                }
328                        }
329                }
330                return matches;
331        }
332
333        @Override
334        public String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType) {
335                List<String> results = new ArrayList<String>();
336                for (String beanName : this.beans.keySet()) {
337                        if (findAnnotationOnBean(beanName, annotationType) != null) {
338                                results.add(beanName);
339                        }
340                }
341                return StringUtils.toStringArray(results);
342        }
343
344        @Override
345        public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)
346                        throws BeansException {
347
348                Map<String, Object> results = new LinkedHashMap<String, Object>();
349                for (String beanName : this.beans.keySet()) {
350                        if (findAnnotationOnBean(beanName, annotationType) != null) {
351                                results.put(beanName, getBean(beanName));
352                        }
353                }
354                return results;
355        }
356
357        @Override
358        public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
359                        throws NoSuchBeanDefinitionException {
360
361                Class<?> beanType = getType(beanName);
362                return (beanType != null ? AnnotationUtils.findAnnotation(beanType, annotationType) : null);
363        }
364
365}