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