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 — 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}