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.autoconfigure; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.LinkedHashMap; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.concurrent.TimeUnit; 030import java.util.stream.Collectors; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034 035import org.springframework.beans.BeansException; 036import org.springframework.beans.factory.Aware; 037import org.springframework.beans.factory.BeanClassLoaderAware; 038import org.springframework.beans.factory.BeanFactory; 039import org.springframework.beans.factory.BeanFactoryAware; 040import org.springframework.beans.factory.NoSuchBeanDefinitionException; 041import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 042import org.springframework.boot.context.properties.bind.Binder; 043import org.springframework.context.EnvironmentAware; 044import org.springframework.context.ResourceLoaderAware; 045import org.springframework.context.annotation.Configuration; 046import org.springframework.context.annotation.DeferredImportSelector; 047import org.springframework.core.Ordered; 048import org.springframework.core.annotation.AnnotationAttributes; 049import org.springframework.core.env.ConfigurableEnvironment; 050import org.springframework.core.env.Environment; 051import org.springframework.core.io.ResourceLoader; 052import org.springframework.core.io.support.SpringFactoriesLoader; 053import org.springframework.core.type.AnnotationMetadata; 054import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 055import org.springframework.core.type.classreading.MetadataReaderFactory; 056import org.springframework.util.Assert; 057import org.springframework.util.ClassUtils; 058import org.springframework.util.StringUtils; 059 060/** 061 * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration 062 * auto-configuration}. This class can also be subclassed if a custom variant of 063 * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed. 064 * 065 * @author Phillip Webb 066 * @author Andy Wilkinson 067 * @author Stephane Nicoll 068 * @author Madhura Bhave 069 * @since 1.3.0 070 * @see EnableAutoConfiguration 071 */ 072public class AutoConfigurationImportSelector 073 implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, 074 BeanFactoryAware, EnvironmentAware, Ordered { 075 076 private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry(); 077 078 private static final String[] NO_IMPORTS = {}; 079 080 private static final Log logger = LogFactory 081 .getLog(AutoConfigurationImportSelector.class); 082 083 private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude"; 084 085 private ConfigurableListableBeanFactory beanFactory; 086 087 private Environment environment; 088 089 private ClassLoader beanClassLoader; 090 091 private ResourceLoader resourceLoader; 092 093 @Override 094 public String[] selectImports(AnnotationMetadata annotationMetadata) { 095 if (!isEnabled(annotationMetadata)) { 096 return NO_IMPORTS; 097 } 098 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader 099 .loadMetadata(this.beanClassLoader); 100 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( 101 autoConfigurationMetadata, annotationMetadata); 102 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 103 } 104 105 /** 106 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata} 107 * of the importing {@link Configuration @Configuration} class. 108 * @param autoConfigurationMetadata the auto-configuration metadata 109 * @param annotationMetadata the annotation metadata of the configuration class 110 * @return the auto-configurations that should be imported 111 */ 112 protected AutoConfigurationEntry getAutoConfigurationEntry( 113 AutoConfigurationMetadata autoConfigurationMetadata, 114 AnnotationMetadata annotationMetadata) { 115 if (!isEnabled(annotationMetadata)) { 116 return EMPTY_ENTRY; 117 } 118 AnnotationAttributes attributes = getAttributes(annotationMetadata); 119 List<String> configurations = getCandidateConfigurations(annotationMetadata, 120 attributes); 121 configurations = removeDuplicates(configurations); 122 Set<String> exclusions = getExclusions(annotationMetadata, attributes); 123 checkExcludedClasses(configurations, exclusions); 124 configurations.removeAll(exclusions); 125 configurations = filter(configurations, autoConfigurationMetadata); 126 fireAutoConfigurationImportEvents(configurations, exclusions); 127 return new AutoConfigurationEntry(configurations, exclusions); 128 } 129 130 @Override 131 public Class<? extends Group> getImportGroup() { 132 return AutoConfigurationGroup.class; 133 } 134 135 protected boolean isEnabled(AnnotationMetadata metadata) { 136 if (getClass() == AutoConfigurationImportSelector.class) { 137 return getEnvironment().getProperty( 138 EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, 139 true); 140 } 141 return true; 142 } 143 144 /** 145 * Return the appropriate {@link AnnotationAttributes} from the 146 * {@link AnnotationMetadata}. By default this method will return attributes for 147 * {@link #getAnnotationClass()}. 148 * @param metadata the annotation metadata 149 * @return annotation attributes 150 */ 151 protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { 152 String name = getAnnotationClass().getName(); 153 AnnotationAttributes attributes = AnnotationAttributes 154 .fromMap(metadata.getAnnotationAttributes(name, true)); 155 Assert.notNull(attributes, 156 () -> "No auto-configuration attributes found. Is " 157 + metadata.getClassName() + " annotated with " 158 + ClassUtils.getShortName(name) + "?"); 159 return attributes; 160 } 161 162 /** 163 * Return the source annotation class used by the selector. 164 * @return the annotation class 165 */ 166 protected Class<?> getAnnotationClass() { 167 return EnableAutoConfiguration.class; 168 } 169 170 /** 171 * Return the auto-configuration class names that should be considered. By default 172 * this method will load candidates using {@link SpringFactoriesLoader} with 173 * {@link #getSpringFactoriesLoaderFactoryClass()}. 174 * @param metadata the source metadata 175 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation 176 * attributes} 177 * @return a list of candidate configurations 178 */ 179 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 180 AnnotationAttributes attributes) { 181 List<String> configurations = SpringFactoriesLoader.loadFactoryNames( 182 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); 183 Assert.notEmpty(configurations, 184 "No auto configuration classes found in META-INF/spring.factories. If you " 185 + "are using a custom packaging, make sure that file is correct."); 186 return configurations; 187 } 188 189 /** 190 * Return the class used by {@link SpringFactoriesLoader} to load configuration 191 * candidates. 192 * @return the factory class 193 */ 194 protected Class<?> getSpringFactoriesLoaderFactoryClass() { 195 return EnableAutoConfiguration.class; 196 } 197 198 private void checkExcludedClasses(List<String> configurations, 199 Set<String> exclusions) { 200 List<String> invalidExcludes = new ArrayList<>(exclusions.size()); 201 for (String exclusion : exclusions) { 202 if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) 203 && !configurations.contains(exclusion)) { 204 invalidExcludes.add(exclusion); 205 } 206 } 207 if (!invalidExcludes.isEmpty()) { 208 handleInvalidExcludes(invalidExcludes); 209 } 210 } 211 212 /** 213 * Handle any invalid excludes that have been specified. 214 * @param invalidExcludes the list of invalid excludes (will always have at least one 215 * element) 216 */ 217 protected void handleInvalidExcludes(List<String> invalidExcludes) { 218 StringBuilder message = new StringBuilder(); 219 for (String exclude : invalidExcludes) { 220 message.append("\t- ").append(exclude).append(String.format("%n")); 221 } 222 throw new IllegalStateException(String 223 .format("The following classes could not be excluded because they are" 224 + " not auto-configuration classes:%n%s", message)); 225 } 226 227 /** 228 * Return any exclusions that limit the candidate configurations. 229 * @param metadata the source metadata 230 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation 231 * attributes} 232 * @return exclusions or an empty set 233 */ 234 protected Set<String> getExclusions(AnnotationMetadata metadata, 235 AnnotationAttributes attributes) { 236 Set<String> excluded = new LinkedHashSet<>(); 237 excluded.addAll(asList(attributes, "exclude")); 238 excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); 239 excluded.addAll(getExcludeAutoConfigurationsProperty()); 240 return excluded; 241 } 242 243 private List<String> getExcludeAutoConfigurationsProperty() { 244 if (getEnvironment() instanceof ConfigurableEnvironment) { 245 Binder binder = Binder.get(getEnvironment()); 246 return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class) 247 .map(Arrays::asList).orElse(Collections.emptyList()); 248 } 249 String[] excludes = getEnvironment() 250 .getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class); 251 return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList(); 252 } 253 254 private List<String> filter(List<String> configurations, 255 AutoConfigurationMetadata autoConfigurationMetadata) { 256 long startTime = System.nanoTime(); 257 String[] candidates = StringUtils.toStringArray(configurations); 258 boolean[] skip = new boolean[candidates.length]; 259 boolean skipped = false; 260 for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { 261 invokeAwareMethods(filter); 262 boolean[] match = filter.match(candidates, autoConfigurationMetadata); 263 for (int i = 0; i < match.length; i++) { 264 if (!match[i]) { 265 skip[i] = true; 266 candidates[i] = null; 267 skipped = true; 268 } 269 } 270 } 271 if (!skipped) { 272 return configurations; 273 } 274 List<String> result = new ArrayList<>(candidates.length); 275 for (int i = 0; i < candidates.length; i++) { 276 if (!skip[i]) { 277 result.add(candidates[i]); 278 } 279 } 280 if (logger.isTraceEnabled()) { 281 int numberFiltered = configurations.size() - result.size(); 282 logger.trace("Filtered " + numberFiltered + " auto configuration class in " 283 + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) 284 + " ms"); 285 } 286 return new ArrayList<>(result); 287 } 288 289 protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { 290 return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, 291 this.beanClassLoader); 292 } 293 294 protected final <T> List<T> removeDuplicates(List<T> list) { 295 return new ArrayList<>(new LinkedHashSet<>(list)); 296 } 297 protected final List<String> asList(AnnotationAttributes attributes, String name) { 299 String[] value = attributes.getStringArray(name); 300 return Arrays.asList((value != null) ? value : new String[0]); 301 } 302 303 private void fireAutoConfigurationImportEvents(List<String> configurations, 304 Set<String> exclusions) { 305 List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); 306 if (!listeners.isEmpty()) { 307 AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, 308 configurations, exclusions); 309 for (AutoConfigurationImportListener listener : listeners) { 310 invokeAwareMethods(listener); 311 listener.onAutoConfigurationImportEvent(event); 312 } 313 } 314 } 315 316 protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { 317 return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, 318 this.beanClassLoader); 319 } 320 321 private void invokeAwareMethods(Object instance) { 322 if (instance instanceof Aware) { 323 if (instance instanceof BeanClassLoaderAware) { 324 ((BeanClassLoaderAware) instance) 325 .setBeanClassLoader(this.beanClassLoader); 326 } 327 if (instance instanceof BeanFactoryAware) { 328 ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory); 329 } 330 if (instance instanceof EnvironmentAware) { 331 ((EnvironmentAware) instance).setEnvironment(this.environment); 332 } 333 if (instance instanceof ResourceLoaderAware) { 334 ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader); 335 } 336 } 337 } 338 339 @Override 340 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 341 Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); 342 this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; 343 } 344 345 protected final ConfigurableListableBeanFactory getBeanFactory() { 346 return this.beanFactory; 347 } 348 349 @Override 350 public void setBeanClassLoader(ClassLoader classLoader) { 351 this.beanClassLoader = classLoader; 352 } 353 354 protected ClassLoader getBeanClassLoader() { 355 return this.beanClassLoader; 356 } 357 358 @Override 359 public void setEnvironment(Environment environment) { 360 this.environment = environment; 361 } 362 363 protected final Environment getEnvironment() { 364 return this.environment; 365 } 366 367 @Override 368 public void setResourceLoader(ResourceLoader resourceLoader) { 369 this.resourceLoader = resourceLoader; 370 } 371 372 protected final ResourceLoader getResourceLoader() { 373 return this.resourceLoader; 374 } 375 376 @Override 377 public int getOrder() { 378 return Ordered.LOWEST_PRECEDENCE - 1; 379 } 380 381 private static class AutoConfigurationGroup implements DeferredImportSelector.Group, 382 BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware { 383 384 private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>(); 385 386 private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>(); 387 388 private ClassLoader beanClassLoader; 389 390 private BeanFactory beanFactory; 391 392 private ResourceLoader resourceLoader; 393 394 private AutoConfigurationMetadata autoConfigurationMetadata; 395 396 @Override 397 public void setBeanClassLoader(ClassLoader classLoader) { 398 this.beanClassLoader = classLoader; 399 } 400 401 @Override 402 public void setBeanFactory(BeanFactory beanFactory) { 403 this.beanFactory = beanFactory; 404 } 405 406 @Override 407 public void setResourceLoader(ResourceLoader resourceLoader) { 408 this.resourceLoader = resourceLoader; 409 } 410 411 @Override 412 public void process(AnnotationMetadata annotationMetadata, 413 DeferredImportSelector deferredImportSelector) { 414 Assert.state( 415 deferredImportSelector instanceof AutoConfigurationImportSelector, 416 () -> String.format("Only %s implementations are supported, got %s", 417 AutoConfigurationImportSelector.class.getSimpleName(), 418 deferredImportSelector.getClass().getName())); 419 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) 420 .getAutoConfigurationEntry(getAutoConfigurationMetadata(), 421 annotationMetadata); 422 this.autoConfigurationEntries.add(autoConfigurationEntry); 423 for (String importClassName : autoConfigurationEntry.getConfigurations()) { 424 this.entries.putIfAbsent(importClassName, annotationMetadata); 425 } 426 } 427 428 @Override 429 public Iterable<Entry> selectImports() { 430 if (this.autoConfigurationEntries.isEmpty()) { 431 return Collections.emptyList(); 432 } 433 Set<String> allExclusions = this.autoConfigurationEntries.stream() 434 .map(AutoConfigurationEntry::getExclusions) 435 .flatMap(Collection::stream).collect(Collectors.toSet()); 436 Set<String> processedConfigurations = this.autoConfigurationEntries.stream() 437 .map(AutoConfigurationEntry::getConfigurations) 438 .flatMap(Collection::stream) 439 .collect(Collectors.toCollection(LinkedHashSet::new)); 440 processedConfigurations.removeAll(allExclusions); 441 442 return sortAutoConfigurations(processedConfigurations, 443 getAutoConfigurationMetadata()) 444 .stream() 445 .map((importClassName) -> new Entry( 446 this.entries.get(importClassName), importClassName)) 447 .collect(Collectors.toList()); 448 } 449 450 private AutoConfigurationMetadata getAutoConfigurationMetadata() { 451 if (this.autoConfigurationMetadata == null) { 452 this.autoConfigurationMetadata = AutoConfigurationMetadataLoader 453 .loadMetadata(this.beanClassLoader); 454 } 455 return this.autoConfigurationMetadata; 456 } 457 458 private List<String> sortAutoConfigurations(Set<String> configurations, 459 AutoConfigurationMetadata autoConfigurationMetadata) { 460 return new AutoConfigurationSorter(getMetadataReaderFactory(), 461 autoConfigurationMetadata).getInPriorityOrder(configurations); 462 } 463 464 private MetadataReaderFactory getMetadataReaderFactory() { 465 try { 466 return this.beanFactory.getBean( 467 SharedMetadataReaderFactoryContextInitializer.BEAN_NAME, 468 MetadataReaderFactory.class); 469 } 470 catch (NoSuchBeanDefinitionException ex) { 471 return new CachingMetadataReaderFactory(this.resourceLoader); 472 } 473 } 474 475 } 476 477 protected static class AutoConfigurationEntry { 478 479 private final List<String> configurations; 480 481 private final Set<String> exclusions; 482 483 private AutoConfigurationEntry() { 484 this.configurations = Collections.emptyList(); 485 this.exclusions = Collections.emptySet(); 486 } 487 488 /** 489 * Create an entry with the configurations that were contributed and their 490 * exclusions. 491 * @param configurations the configurations that should be imported 492 * @param exclusions the exclusions that were applied to the original list 493 */ 494 AutoConfigurationEntry(Collection<String> configurations, 495 Collection<String> exclusions) { 496 this.configurations = new ArrayList<>(configurations); 497 this.exclusions = new HashSet<>(exclusions); 498 } 499 500 public List<String> getConfigurations() { 501 return this.configurations; 502 } 503 504 public Set<String> getExclusions() { 505 return this.exclusions; 506 } 507 508 } 509 510}