001/* 002 * Copyright 2012-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 * http://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.boot.web.reactive.context; 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.context.support.AbstractRefreshableConfigApplicationContext; 031import org.springframework.core.env.ConfigurableEnvironment; 032import org.springframework.core.io.Resource; 033import org.springframework.stereotype.Component; 034import org.springframework.util.Assert; 035import org.springframework.util.ClassUtils; 036import org.springframework.util.StringUtils; 037 038/** 039 * {@link ConfigurableReactiveWebApplicationContext} that accepts annotated classes as 040 * input - in particular 041 * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated 042 * classes, but also plain {@link Component @Component} classes and JSR-330 compliant 043 * classes using {@code javax.inject} annotations. Allows for registering classes one by 044 * one (specifying class names as config location) as well as for classpath scanning 045 * (specifying base packages as config location). 046 * <p> 047 * Note: In case of multiple {@code @Configuration} classes, later {@code @Bean} 048 * definitions will override ones defined in earlier loaded files. This can be leveraged 049 * to deliberately override certain bean definitions via an extra Configuration class. 050 * 051 * @author Phillip Webb 052 * @since 2.0.0 053 * @see #register(Class...) 054 * @see #scan(String...) 055 */ 056public class AnnotationConfigReactiveWebApplicationContext 057 extends AbstractRefreshableConfigApplicationContext 058 implements ConfigurableReactiveWebApplicationContext, AnnotationConfigRegistry { 059 060 private BeanNameGenerator beanNameGenerator; 061 062 private ScopeMetadataResolver scopeMetadataResolver; 063 064 private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>(); 065 066 private final Set<String> basePackages = new LinkedHashSet<>(); 067 068 @Override 069 protected ConfigurableEnvironment createEnvironment() { 070 return new StandardReactiveWebEnvironment(); 071 } 072 073 /** 074 * Set a custom {@link BeanNameGenerator} for use with 075 * {@link AnnotatedBeanDefinitionReader} and/or 076 * {@link ClassPathBeanDefinitionScanner}. 077 * <p> 078 * Default is 079 * {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. 080 * @param beanNameGenerator the bean name generator 081 * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator 082 * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator 083 */ 084 public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { 085 this.beanNameGenerator = beanNameGenerator; 086 } 087 088 /** 089 * Return the custom {@link BeanNameGenerator} for use with 090 * {@link AnnotatedBeanDefinitionReader} and/or 091 * {@link ClassPathBeanDefinitionScanner}, if any. 092 * @return the bean name generator 093 */ 094 protected BeanNameGenerator getBeanNameGenerator() { 095 return this.beanNameGenerator; 096 } 097 098 /** 099 * Set a custom {@link ScopeMetadataResolver} for use with 100 * {@link AnnotatedBeanDefinitionReader} and/or 101 * {@link ClassPathBeanDefinitionScanner}. 102 * <p> 103 * Default is an 104 * {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}. 105 * @param scopeMetadataResolver the scope metadata resolver 106 * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver 107 * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver 108 */ 109 public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { 110 this.scopeMetadataResolver = scopeMetadataResolver; 111 } 112 113 /** 114 * Return the custom {@link ScopeMetadataResolver} for use with 115 * {@link AnnotatedBeanDefinitionReader} and/or 116 * {@link ClassPathBeanDefinitionScanner}, if any. 117 * @return the scope metadata resolver 118 */ 119 protected ScopeMetadataResolver getScopeMetadataResolver() { 120 return this.scopeMetadataResolver; 121 } 122 123 /** 124 * Register one or more annotated classes to be processed. 125 * <p> 126 * Note that {@link #refresh()} must be called in order for the context to fully 127 * process the new classes. 128 * @param annotatedClasses one or more annotated classes, e.g. 129 * {@link org.springframework.context.annotation.Configuration @Configuration} classes 130 * @see #scan(String...) 131 * @see #loadBeanDefinitions(DefaultListableBeanFactory) 132 * @see #setConfigLocation(String) 133 * @see #refresh() 134 */ 135 @Override 136 public void register(Class<?>... annotatedClasses) { 137 Assert.notEmpty(annotatedClasses, 138 "At least one annotated class must be specified"); 139 this.annotatedClasses.addAll(Arrays.asList(annotatedClasses)); 140 } 141 142 /** 143 * Perform a scan within the specified base packages. 144 * <p> 145 * Note that {@link #refresh()} must be called in order for the context to fully 146 * process the new classes. 147 * @param basePackages the packages to check for annotated classes 148 * @see #loadBeanDefinitions(DefaultListableBeanFactory) 149 * @see #register(Class...) 150 * @see #setConfigLocation(String) 151 * @see #refresh() 152 */ 153 @Override 154 public void scan(String... basePackages) { 155 Assert.notEmpty(basePackages, "At least one base package must be specified"); 156 this.basePackages.addAll(Arrays.asList(basePackages)); 157 } 158 159 /** 160 * Register a {@link org.springframework.beans.factory.config.BeanDefinition} for any 161 * classes specified by {@link #register(Class...)} and scan any packages specified by 162 * {@link #scan(String...)}. 163 * <p> 164 * For any values specified by {@link #setConfigLocation(String)} or 165 * {@link #setConfigLocations(String[])}, attempt first to load each location as a 166 * class, registering a {@code BeanDefinition} if class loading is successful, and if 167 * class loading fails (i.e. a {@code ClassNotFoundException} is raised), assume the 168 * value is a package and attempt to scan it for annotated classes. 169 * <p> 170 * Enables the default set of annotation configuration post processors, such that 171 * {@code @Autowired}, {@code @Required}, and associated annotations can be used. 172 * <p> 173 * Configuration class bean definitions are registered with generated bean definition 174 * names unless the {@code value} attribute is provided to the stereotype annotation. 175 * @param beanFactory the bean factory to load bean definitions into 176 * @see #register(Class...) 177 * @see #scan(String...) 178 * @see #setConfigLocation(String) 179 * @see #setConfigLocations(String[]) 180 * @see AnnotatedBeanDefinitionReader 181 * @see ClassPathBeanDefinitionScanner 182 */ 183 @Override 184 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { 185 AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader( 186 beanFactory); 187 ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner( 188 beanFactory); 189 applyBeanNameGenerator(beanFactory, reader, scanner); 190 applyScopeMetadataResolver(reader, scanner); 191 loadBeanDefinitions(reader, scanner); 192 } 193 194 private void applyBeanNameGenerator(DefaultListableBeanFactory beanFactory, 195 AnnotatedBeanDefinitionReader reader, 196 ClassPathBeanDefinitionScanner scanner) { 197 BeanNameGenerator beanNameGenerator = getBeanNameGenerator(); 198 if (beanNameGenerator != null) { 199 reader.setBeanNameGenerator(beanNameGenerator); 200 scanner.setBeanNameGenerator(beanNameGenerator); 201 beanFactory.registerSingleton( 202 AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, 203 beanNameGenerator); 204 } 205 } 206 207 private void applyScopeMetadataResolver(AnnotatedBeanDefinitionReader reader, 208 ClassPathBeanDefinitionScanner scanner) { 209 ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver(); 210 if (scopeMetadataResolver != null) { 211 reader.setScopeMetadataResolver(scopeMetadataResolver); 212 scanner.setScopeMetadataResolver(scopeMetadataResolver); 213 } 214 } 215 216 private void loadBeanDefinitions(AnnotatedBeanDefinitionReader reader, 217 ClassPathBeanDefinitionScanner scanner) throws LinkageError { 218 if (!this.annotatedClasses.isEmpty()) { 219 registerAnnotatedClasses(reader); 220 } 221 if (!this.basePackages.isEmpty()) { 222 scanBasePackages(scanner); 223 } 224 String[] configLocations = getConfigLocations(); 225 if (configLocations != null) { 226 registerConfigLocations(reader, scanner, configLocations); 227 } 228 } 229 230 private void registerAnnotatedClasses(AnnotatedBeanDefinitionReader reader) { 231 if (this.logger.isInfoEnabled()) { 232 this.logger.info("Registering annotated classes: [" 233 + StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) 234 + "]"); 235 } 236 reader.register(ClassUtils.toClassArray(this.annotatedClasses)); 237 } 238 239 private void scanBasePackages(ClassPathBeanDefinitionScanner scanner) { 240 if (this.logger.isInfoEnabled()) { 241 this.logger.info("Scanning base packages: [" 242 + StringUtils.collectionToCommaDelimitedString(this.basePackages) 243 + "]"); 244 } 245 scanner.scan(StringUtils.toStringArray(this.basePackages)); 246 } 247 248 private void registerConfigLocations(AnnotatedBeanDefinitionReader reader, 249 ClassPathBeanDefinitionScanner scanner, String[] configLocations) 250 throws LinkageError { 251 for (String configLocation : configLocations) { 252 try { 253 register(reader, configLocation); 254 } 255 catch (ClassNotFoundException ex) { 256 if (this.logger.isDebugEnabled()) { 257 this.logger.debug("Could not load class for config location [" 258 + configLocation + "] - trying package scan. " + ex); 259 } 260 int count = scanner.scan(configLocation); 261 if (this.logger.isInfoEnabled()) { 262 logScanResult(configLocation, count); 263 } 264 } 265 } 266 } 267 268 private void register(AnnotatedBeanDefinitionReader reader, String configLocation) 269 throws ClassNotFoundException, LinkageError { 270 Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader()); 271 if (this.logger.isInfoEnabled()) { 272 this.logger.info("Successfully resolved class for [" + configLocation + "]"); 273 } 274 reader.register(clazz); 275 } 276 277 private void logScanResult(String configLocation, int count) { 278 if (count == 0) { 279 this.logger.info("No annotated classes found for specified class/package [" 280 + configLocation + "]"); 281 } 282 else { 283 this.logger.info("Found " + count + " annotated classes in package [" 284 + configLocation + "]"); 285 } 286 } 287 288 /** 289 * Build an {@link AnnotatedBeanDefinitionReader} for the given bean factory. 290 * <p> 291 * This should be pre-configured with the {@code Environment} (if desired) but not 292 * with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet. 293 * @param beanFactory the bean factory to load bean definitions into 294 * @return the annotated bean definition reader 295 * @see #getEnvironment() 296 * @see #getBeanNameGenerator() 297 * @see #getScopeMetadataResolver() 298 */ 299 protected AnnotatedBeanDefinitionReader getAnnotatedBeanDefinitionReader( 300 DefaultListableBeanFactory beanFactory) { 301 return new AnnotatedBeanDefinitionReader(beanFactory, getEnvironment()); 302 } 303 304 /** 305 * Build a {@link ClassPathBeanDefinitionScanner} for the given bean factory. 306 * <p> 307 * This should be pre-configured with the {@code Environment} (if desired) but not 308 * with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet. 309 * @param beanFactory the bean factory to load bean definitions into 310 * @return the class path bean definition scanner 311 * @see #getEnvironment() 312 * @see #getBeanNameGenerator() 313 * @see #getScopeMetadataResolver() 314 */ 315 protected ClassPathBeanDefinitionScanner getClassPathBeanDefinitionScanner( 316 DefaultListableBeanFactory beanFactory) { 317 return new ClassPathBeanDefinitionScanner(beanFactory, true, getEnvironment()); 318 } 319 320 @Override 321 protected Resource getResourceByPath(String path) { 322 // We must be careful not to expose classpath resources 323 return new FilteredReactiveWebContextResource(path); 324 } 325 326}