001/*
002 * Copyright 2002-2019 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.web.context.support;
018
019import java.util.Collections;
020import java.util.LinkedHashSet;
021import java.util.Set;
022
023import org.springframework.beans.factory.support.BeanNameGenerator;
024import org.springframework.beans.factory.support.DefaultListableBeanFactory;
025import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
026import org.springframework.context.annotation.AnnotationConfigRegistry;
027import org.springframework.context.annotation.AnnotationConfigUtils;
028import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
029import org.springframework.context.annotation.ScopeMetadataResolver;
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.util.ClassUtils;
033import org.springframework.util.StringUtils;
034import org.springframework.web.context.ContextLoader;
035
036/**
037 * {@link org.springframework.web.context.WebApplicationContext WebApplicationContext}
038 * implementation which accepts <em>component classes</em> as input &mdash; in particular
039 * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated
040 * classes, but also plain {@link org.springframework.stereotype.Component @Component}
041 * classes and JSR-330 compliant classes using {@code javax.inject} annotations.
042 *
043 * <p>Allows for registering classes one by one (specifying class names as config
044 * location) as well as for classpath scanning (specifying base packages as config location).
045 *
046 * <p>This is essentially the equivalent of
047 * {@link org.springframework.context.annotation.AnnotationConfigApplicationContext
048 * AnnotationConfigApplicationContext} for a web environment.
049 *
050 * <p>To make use of this application context, the
051 * {@linkplain ContextLoader#CONTEXT_CLASS_PARAM "contextClass"} context-param for
052 * ContextLoader and/or "contextClass" init-param for FrameworkServlet must be set to
053 * the fully-qualified name of this class.
054 *
055 * <p>As of Spring 3.1, this class may also be directly instantiated and injected into
056 * Spring's {@code DispatcherServlet} or {@code ContextLoaderListener} when using the
057 * {@link org.springframework.web.WebApplicationInitializer WebApplicationInitializer}
058 * code-based alternative to {@code web.xml}. See its Javadoc for details and usage examples.
059 *
060 * <p>Unlike {@link XmlWebApplicationContext}, no default configuration class locations
061 * are assumed. Rather, it is a requirement to set the
062 * {@linkplain ContextLoader#CONFIG_LOCATION_PARAM "contextConfigLocation"}
063 * context-param for {@link ContextLoader} and/or "contextConfigLocation" init-param for
064 * FrameworkServlet.  The param-value may contain both fully-qualified
065 * class names and base packages to scan for components. See {@link #loadBeanDefinitions}
066 * for exact details on how these locations are processed.
067 *
068 * <p>As an alternative to setting the "contextConfigLocation" parameter, users may
069 * implement an {@link org.springframework.context.ApplicationContextInitializer
070 * ApplicationContextInitializer} and set the
071 * {@linkplain ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM "contextInitializerClasses"}
072 * context-param / init-param. In such cases, users should favor the {@link #refresh()}
073 * and {@link #scan(String...)} methods over the {@link #setConfigLocation(String)}
074 * method, which is primarily for use by {@code ContextLoader}.
075 *
076 * <p>Note: In case of multiple {@code @Configuration} classes, later {@code @Bean}
077 * definitions will override ones defined in earlier loaded files. This can be leveraged
078 * to deliberately override certain bean definitions via an extra {@code @Configuration}
079 * class.
080 *
081 * @author Chris Beams
082 * @author Juergen Hoeller
083 * @since 3.0
084 * @see org.springframework.context.annotation.AnnotationConfigApplicationContext
085 */
086public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext
087                implements AnnotationConfigRegistry {
088
089        @Nullable
090        private BeanNameGenerator beanNameGenerator;
091
092        @Nullable
093        private ScopeMetadataResolver scopeMetadataResolver;
094
095        private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
096
097        private final Set<String> basePackages = new LinkedHashSet<>();
098
099
100        /**
101         * Set a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader}
102         * and/or {@link ClassPathBeanDefinitionScanner}.
103         * <p>Default is {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
104         * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
105         * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
106         */
107        public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
108                this.beanNameGenerator = beanNameGenerator;
109        }
110
111        /**
112         * Return the custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader}
113         * and/or {@link ClassPathBeanDefinitionScanner}, if any.
114         */
115        @Nullable
116        protected BeanNameGenerator getBeanNameGenerator() {
117                return this.beanNameGenerator;
118        }
119
120        /**
121         * Set a custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader}
122         * and/or {@link ClassPathBeanDefinitionScanner}.
123         * <p>Default is an {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}.
124         * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver
125         * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver
126         */
127        public void setScopeMetadataResolver(@Nullable ScopeMetadataResolver scopeMetadataResolver) {
128                this.scopeMetadataResolver = scopeMetadataResolver;
129        }
130
131        /**
132         * Return the custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader}
133         * and/or {@link ClassPathBeanDefinitionScanner}, if any.
134         */
135        @Nullable
136        protected ScopeMetadataResolver getScopeMetadataResolver() {
137                return this.scopeMetadataResolver;
138        }
139
140
141        /**
142         * Register one or more component classes to be processed.
143         * <p>Note that {@link #refresh()} must be called in order for the context
144         * to fully process the new classes.
145         * @param componentClasses one or more component classes,
146         * e.g. {@link org.springframework.context.annotation.Configuration @Configuration} classes
147         * @see #scan(String...)
148         * @see #loadBeanDefinitions(DefaultListableBeanFactory)
149         * @see #setConfigLocation(String)
150         * @see #refresh()
151         */
152        @Override
153        public void register(Class<?>... componentClasses) {
154                Assert.notEmpty(componentClasses, "At least one component class must be specified");
155                Collections.addAll(this.componentClasses, componentClasses);
156        }
157
158        /**
159         * Perform a scan within the specified base packages.
160         * <p>Note that {@link #refresh()} must be called in order for the context
161         * to fully process the new classes.
162         * @param basePackages the packages to check for component classes
163         * @see #loadBeanDefinitions(DefaultListableBeanFactory)
164         * @see #register(Class...)
165         * @see #setConfigLocation(String)
166         * @see #refresh()
167         */
168        @Override
169        public void scan(String... basePackages) {
170                Assert.notEmpty(basePackages, "At least one base package must be specified");
171                Collections.addAll(this.basePackages, basePackages);
172        }
173
174
175        /**
176         * Register a {@link org.springframework.beans.factory.config.BeanDefinition} for
177         * any classes specified by {@link #register(Class...)} and scan any packages
178         * specified by {@link #scan(String...)}.
179         * <p>For any values specified by {@link #setConfigLocation(String)} or
180         * {@link #setConfigLocations(String[])}, attempt first to load each location as a
181         * class, registering a {@code BeanDefinition} if class loading is successful,
182         * and if class loading fails (i.e. a {@code ClassNotFoundException} is raised),
183         * assume the value is a package and attempt to scan it for component classes.
184         * <p>Enables the default set of annotation configuration post processors, such that
185         * {@code @Autowired}, {@code @Required}, and associated annotations can be used.
186         * <p>Configuration class bean definitions are registered with generated bean
187         * definition names unless the {@code value} attribute is provided to the stereotype
188         * annotation.
189         * @param beanFactory the bean factory to load bean definitions into
190         * @see #register(Class...)
191         * @see #scan(String...)
192         * @see #setConfigLocation(String)
193         * @see #setConfigLocations(String[])
194         * @see AnnotatedBeanDefinitionReader
195         * @see ClassPathBeanDefinitionScanner
196         */
197        @Override
198        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
199                AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
200                ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
201
202                BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
203                if (beanNameGenerator != null) {
204                        reader.setBeanNameGenerator(beanNameGenerator);
205                        scanner.setBeanNameGenerator(beanNameGenerator);
206                        beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
207                }
208
209                ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
210                if (scopeMetadataResolver != null) {
211                        reader.setScopeMetadataResolver(scopeMetadataResolver);
212                        scanner.setScopeMetadataResolver(scopeMetadataResolver);
213                }
214
215                if (!this.componentClasses.isEmpty()) {
216                        if (logger.isDebugEnabled()) {
217                                logger.debug("Registering component classes: [" +
218                                                StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
219                        }
220                        reader.register(ClassUtils.toClassArray(this.componentClasses));
221                }
222
223                if (!this.basePackages.isEmpty()) {
224                        if (logger.isDebugEnabled()) {
225                                logger.debug("Scanning base packages: [" +
226                                                StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
227                        }
228                        scanner.scan(StringUtils.toStringArray(this.basePackages));
229                }
230
231                String[] configLocations = getConfigLocations();
232                if (configLocations != null) {
233                        for (String configLocation : configLocations) {
234                                try {
235                                        Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
236                                        if (logger.isTraceEnabled()) {
237                                                logger.trace("Registering [" + configLocation + "]");
238                                        }
239                                        reader.register(clazz);
240                                }
241                                catch (ClassNotFoundException ex) {
242                                        if (logger.isTraceEnabled()) {
243                                                logger.trace("Could not load class for config location [" + configLocation +
244                                                                "] - trying package scan. " + ex);
245                                        }
246                                        int count = scanner.scan(configLocation);
247                                        if (count == 0 && logger.isDebugEnabled()) {
248                                                logger.debug("No component classes found for specified class/package [" + configLocation + "]");
249                                        }
250                                }
251                        }
252                }
253        }
254
255
256        /**
257         * Build an {@link AnnotatedBeanDefinitionReader} for the given bean factory.
258         * <p>This should be pre-configured with the {@code Environment} (if desired)
259         * but not with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet.
260         * @param beanFactory the bean factory to load bean definitions into
261         * @since 4.1.9
262         * @see #getEnvironment()
263         * @see #getBeanNameGenerator()
264         * @see #getScopeMetadataResolver()
265         */
266        protected AnnotatedBeanDefinitionReader getAnnotatedBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
267                return new AnnotatedBeanDefinitionReader(beanFactory, getEnvironment());
268        }
269
270        /**
271         * Build a {@link ClassPathBeanDefinitionScanner} for the given bean factory.
272         * <p>This should be pre-configured with the {@code Environment} (if desired)
273         * but not with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet.
274         * @param beanFactory the bean factory to load bean definitions into
275         * @since 4.1.9
276         * @see #getEnvironment()
277         * @see #getBeanNameGenerator()
278         * @see #getScopeMetadataResolver()
279         */
280        protected ClassPathBeanDefinitionScanner getClassPathBeanDefinitionScanner(DefaultListableBeanFactory beanFactory) {
281                return new ClassPathBeanDefinitionScanner(beanFactory, true, getEnvironment());
282        }
283
284}