文章结构

  1. 三种使用方式
  2. 加载时机
  3. 对不同使用方式的处理方式

简介

Spring 3.0之前,创建Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内。而在Spring 3.0之后提供了JavaConfig的方式,也就是将IOC容器里Bean的元信息以java代码的方式进行描述。我们可以通过@Configuration与@Bean这两个注解配合使用来将原来配置在xml文件里的bean通过java代码的方式进行描述

@Import注解提供了@Bean注解的功能,同时还有xml配置文件里标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration

先看一下@Import注解的源码:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Import {
  5. /**
  6. * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
  7. * or regular component classes to import.
  8. */
  9. Class<?>[] value();
  10. }

从源码里可以看出@Import可以配合 Configuration ,ImportSelector, ImportBeanDefinitionRegistrar 来使用,下面的or表示也可以把Import当成普通的Bean使用
@Import只允许放到类上面,不能放到方法上。下面我们来看具体的使用方式

普通使用方法

这种方式可以直接把类加入到Spring IOC容器

  1. @Configuration
  2. @Import(value={UserServiceImpl.class})
  3. public class Config {
  4. }

但是这种方式有一些问题,那就是只能使用类的无参构造方法来创建bean,对于有参数的构造方法就无能为力了

结合ImportBeanDefinitionRegistrar接口

ImportBeanDefinitionRegistrar接口的源码如下:

  1. public interface ImportBeanDefinitionRegistrar {
  2. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
  3. }

可以看到这个接口唯一的方法是有两个参数的

  1. AnnotationMetadata:通过这个参数可以拿到类的元数据信息
  2. BeanDefinitionRegistry:通过这个参数可以操作IOC容器

我们可以使用一个类来实现这个接口,这样可以指定注入某些bean

  1. public class UserServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  2. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
  3. BeanDefinitionBuilder userService = BeanDefinitionBuilder.rootBeanDefinition(UserServiceImpl.class);
  4. registry.registerBeanDefinition("userService", userService.getBeanDefinition());
  5. }
  6. }
  7. @Configuration
  8. @Import(value={UserServiceBeanDefinitionRegistrar.class})
  9. public class Config {
  10. }

可以看到我们在这个方法里面做一些特殊操作什么的都是可以的,相比较于普通的方式可是灵活了很多

接着我们在@Import注解引入的地方只需要修改为引入UserServiceBeanDefinitionRegistrar就可以了

  1. @Configuration
  2. @Import(value={UserServiceBeanDefinitionRegistrar.class})
  3. public class Config {
  4. }

结合ImportSelector接口

相比较与实现ImportBeanDefinitionRegistrar接口之后直接操作Bean容器来说,使用ImportSelector会更加优雅一些,只需要返回需要注入类的全限定名就可以了

ImportSelector接口的源码如下:

  1. public interface ImportSelector {
  2. String[] selectImports(AnnotationMetadata importingClassMetadata);
  3. }
  4. public class UserServiceImportSelect implements ImportSelector{
  5. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  6. return new String[]{UserServiceImpl.class.getName()};
  7. }
  8. }
  9. @Configuration()
  10. @Import(value={UserServiceImportSelect.class})
  11. public class Config {
  12. }

相比较三种方式来说可以看到最后这种才是最优雅的方式

源码解析

首先我们就以第三种比较优雅的方式出发,使用Call Hierarchy看一下ImportSelector接口的selectImports方法调用链关系:

找一个ImportSelector的实现类,Ctrl+H 找继承关系,
image.png
Ctrl + Alt + H 查询方法的调用链 (往下走是爸爸,找谁调用了它)
image.png

看过之前Spring源码解析文章的同学都知道,refresh方法是用来初始化容器上下文的。跟着这个调用链走下来到中间有一个类是ConfigurationClassPostProcessor,根据类名我们就可以猜到这个类应该是处理配置类(也就是标注@Configuration)的。那么从这开始看吧

  1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  2. List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
  3. String[] candidateNames = registry.getBeanDefinitionNames();
  4. for (String beanName : candidateNames) {
  5. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
  6. if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
  7. ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
  8. if (logger.isDebugEnabled()) {
  9. logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
  10. }
  11. }
  12. //查看是否是配置类
  13. else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
  14. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
  15. }
  16. }
  17. //如果没有配置类就直接返回
  18. if (configCandidates.isEmpty()) {
  19. return;
  20. }
  21. //对这些配置类根据Order排序
  22. Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
  23. @Override
  24. public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
  25. int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
  26. int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
  27. return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
  28. }
  29. });
  30. SingletonBeanRegistry sbr = null;
  31. if (registry instanceof SingletonBeanRegistry) {
  32. sbr = (SingletonBeanRegistry) registry;
  33. if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
  34. BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
  35. this.componentScanBeanNameGenerator = generator;
  36. this.importBeanNameGenerator = generator;
  37. }
  38. }
  39. //创建配置类的解析类
  40. ConfigurationClassParser parser = new ConfigurationClassParser(
  41. this.metadataReaderFactory, this.problemReporter, this.environment,
  42. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
  43. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
  44. Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
  45. do {
  46. ConfigurationClassParserparse方法进行解析,重点哈
  47. parser.parse(candidates);
  48. parser.validate();
  49. Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
  50. configClasses.removeAll(alreadyParsed);
  51. // Read the model and create bean definitions based on its content
  52. if (this.reader == null) {
  53. this.reader = new ConfigurationClassBeanDefinitionReader(
  54. registry, this.sourceExtractor, this.resourceLoader, this.environment,
  55. this.importBeanNameGenerator, parser.getImportRegistry());
  56. }
  57. this.reader.loadBeanDefinitions(configClasses);
  58. alreadyParsed.addAll(configClasses);
  59. candidates.clear();
  60. if (registry.getBeanDefinitionCount() > candidateNames.length) {
  61. String[] newCandidateNames = registry.getBeanDefinitionNames();
  62. Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
  63. Set<String> alreadyParsedClasses = new HashSet<String>();
  64. for (ConfigurationClass configurationClass : alreadyParsed) {
  65. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
  66. }
  67. for (String candidateName : newCandidateNames) {
  68. if (!oldCandidateNames.contains(candidateName)) {
  69. BeanDefinition bd = registry.getBeanDefinition(candidateName);
  70. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
  71. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
  72. candidates.add(new BeanDefinitionHolder(bd, candidateName));
  73. }
  74. }
  75. }
  76. candidateNames = newCandidateNames;
  77. }
  78. }
  79. while (!candidates.isEmpty());
  80. if (sbr != null) {
  81. if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
  82. sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
  83. }
  84. }
  85. if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  86. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
  87. }
  88. }

ConfigurationClassParser

现在该进入ConfigurationClassParser类了

  1. public void parse(Set<BeanDefinitionHolder> configCandidates) {
  2. this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
  3. for (BeanDefinitionHolder holder : configCandidates) {
  4. BeanDefinition bd = holder.getBeanDefinition();
  5. try {
  6. if (bd instanceof AnnotatedBeanDefinition) {
  7. parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
  8. }
  9. else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
  10. parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
  11. }
  12. else {
  13. parse(bd.getBeanClassName(), holder.getBeanName());
  14. }
  15. }
  16. catch (BeanDefinitionStoreException ex) {
  17. throw ex;
  18. }
  19. catch (Throwable ex) {
  20. throw new BeanDefinitionStoreException(
  21. "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
  22. }
  23. }
  24. processDeferredImportSelectors();
  25. }
  26. //下面三种方法用于处理不同的BeanDefinition 类型,但最终都是使用的processConfigurationClass方法
  27. protected final void parse(String className, String beanName) throws IOException {
  28. MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
  29. processConfigurationClass(new ConfigurationClass(reader, beanName));
  30. }
  31. protected final void parse(Class<?> clazz, String beanName) throws IOException {
  32. processConfigurationClass(new ConfigurationClass(clazz, beanName));
  33. }
  34. protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  35. processConfigurationClass(new ConfigurationClass(metadata, beanName));
  36. }

可以看到配置类可能会是三种形式的存在,这三种形式的Bean在操作上有着部分不一样,但是大部分又是一样,所以Spring用这种模式来处理。不得不感叹人家设计的真好

接着往下看

  1. protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  2. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  3. return;
  4. }
  5. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  6. //在这里处理Configuration重复import
  7. //如果同一个配置类被处理两次,两次都属于被import的则合并导入类,返回。如果配置类不是被导入的,则移除旧使用新的配置类
  8. if (existingClass != null) {
  9. if (configClass.isImported()) {
  10. if (existingClass.isImported()) {
  11. existingClass.mergeImportedBy(configClass);
  12. }
  13. return;
  14. }
  15. else {
  16. this.configurationClasses.remove(configClass);
  17. for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
  18. if (configClass.equals(it.next())) {
  19. it.remove();
  20. }
  21. }
  22. }
  23. }
  24. SourceClass sourceClass = asSourceClass(configClass);
  25. do {
  26. //接着往下看吧
  27. sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  28. }
  29. while (sourceClass != null);
  30. this.configurationClasses.put(configClass, configClass);
  31. }
  32. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  33. throws IOException {
  34. // 处理递归类
  35. processMemberClasses(configClass, sourceClass);
  36. // 处理@PropertySource注解
  37. for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
  38. sourceClass.getMetadata(), PropertySources.class,
  39. org.springframework.context.annotation.PropertySource.class)) {
  40. if (this.environment instanceof ConfigurableEnvironment) {
  41. processPropertySource(propertySource);
  42. }
  43. else {
  44. logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
  45. "]. Reason: Environment must implement ConfigurableEnvironment");
  46. }
  47. }
  48. // 处理 @ComponentScan 注解
  49. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  50. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  51. if (!componentScans.isEmpty() &&
  52. !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  53. for (AnnotationAttributes componentScan : componentScans) {
  54. // The config class is annotated with @ComponentScan -> perform the scan immediately
  55. Set<BeanDefinitionHolder> scannedBeanDefinitions =
  56. this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
  57. // Check the set of scanned definitions for any further config classes and parse recursively if needed
  58. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
  59. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
  60. if (bdCand == null) {
  61. bdCand = holder.getBeanDefinition();
  62. }
  63. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
  64. parse(bdCand.getBeanClassName(), holder.getBeanName());
  65. }
  66. }
  67. }
  68. }
  69. //处理Import注解,这个是咱们的菜
  70. processImports(configClass, sourceClass, getImports(sourceClass), true);
  71. // 处理@ImportResource 注解
  72. if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
  73. AnnotationAttributes importResource =
  74. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
  75. String[] resources = importResource.getStringArray("locations");
  76. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
  77. for (String resource : resources) {
  78. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
  79. configClass.addImportedResource(resolvedResource, readerClass);
  80. }
  81. }
  82. //处理包含@Bean注解的方法
  83. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  84. for (MethodMetadata methodMetadata : beanMethods) {
  85. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  86. }
  87. // 处理普通方法
  88. processInterfaces(configClass, sourceClass);
  89. if (sourceClass.getMetadata().hasSuperClass()) {
  90. String superclass = sourceClass.getMetadata().getSuperClassName();
  91. if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
  92. this.knownSuperclasses.put(superclass, configClass);
  93. // Superclass found, return its annotation metadata and recurse
  94. return sourceClass.getSuperClass();
  95. }
  96. }
  97. return null;
  98. }

看到这里好像突然发现了新大陆呀,原来我们经常见的@Bean@ImportResource@Import@ComponentScan@PropertySource都是在这里处理的呀

咱们的重点还是放在@Import上,对其他几个注解感兴趣的同学可以自行研究一下

  1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  2. Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
  3. if (importCandidates.isEmpty()) {
  4. return;
  5. }
  6. if (checkForCircularImports && isChainedImportOnStack(configClass)) {
  7. this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  8. }
  9. else {
  10. this.importStack.push(configClass);
  11. try {
  12. for (SourceClass candidate : importCandidates) {
  13. //如果实现了ImportSelector接口
  14. if (candidate.isAssignable(ImportSelector.class)) {
  15. Class<?> candidateClass = candidate.loadClass();
  16. ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
  17. ParserStrategyUtils.invokeAwareMethods(
  18. selector, this.environment, this.resourceLoader, this.registry);
  19. if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
  20. this.deferredImportSelectors.add(
  21. new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
  22. }
  23. else {
  24. //这里调用了ImportSelector接口中的方法
  25. String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
  26. Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
  27. processImports(configClass, currentSourceClass, importSourceClasses, false);
  28. }
  29. }
  30. //如果实现了ImportBeanDefinitionRegistrar接口
  31. else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
  32. Class<?> candidateClass = candidate.loadClass();
  33. ImportBeanDefinitionRegistrar registrar =
  34. BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
  35. ParserStrategyUtils.invokeAwareMethods(
  36. registrar, this.environment, this.resourceLoader, this.registry);
  37. //这里将ImportBeanDefinitionRegistrar对应的类加入到了注册器,之后这个注册器会进行遍历调用接口中的方法
  38. configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
  39. }
  40. else {
  41. //将import当成Configuration来使用就是我们的第一种应用的方式
  42. this.importStack.registerImport(
  43. currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
  44. processConfigurationClass(candidate.asConfigClass(configClass));
  45. }
  46. }
  47. }
  48. catch (BeanDefinitionStoreException ex) {
  49. throw ex;
  50. }
  51. catch (Throwable ex) {
  52. throw new BeanDefinitionStoreException(
  53. "Failed to process import candidates for configuration class [" +
  54. configClass.getMetadata().getClassName() + "]", ex);
  55. }
  56. finally {
  57. this.importStack.pop();
  58. }
  59. }
  60. }