@Import 注解使用及原理解析

1:@Import的使用场景:

  • 通过 @Import 注解引入普通的 Java 类数组

        @Import({TestA.class}):这样就会把 TestA 注入进 IOC 容器,生成一个名字为 “com.demo.testA” 的 bean,同时也可以看到可以传入多个类,这样就可以在IOC容器里生成多个 bean。

  • 通过 @Import 注解引入实现了 ImportSelector 接口的类

        实现了ImportSelector接口,就必须重写 selectImports 方法,如下所示,就会把 TestB 注入IOC 容器,让 Spring 来管理名称为“com.demo.TestB” 的 bean。同理,该方法返回的是一个数组,如果返回多个,就会在 IOC 容器里生成多个 bean。

public class MySelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.demo.TestB"};
    }
}
  • 通过 @Import 引入实现了 ImportBeanDefinitionRegistrar 接口的类

        跟实现 ImportSelector 不同,实现 ImportBeanDefinitionRegistrar 不是必须重写ImportBeanDefinitionRegistrar 的方法,但是我们为了注入 bean定义, 还是需要重写 registerBeanDefinitions 方法,如下所示,此时就会注入一个名字为 testC 的 bean。同理也可以在该方法里使用多个 RootBeanDefinition 结合 registry 来注入多个 bean定义。

public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //构造 BeanDefinition
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class);
        //注册 bean, 并给其取个名字
        registry.registerBeanDefinition("testC",rootBeanDefinition);
    }
}

2:代码演示:

  1. 首先定义三个类,用来生成一些 Bean
    public class TestA {
        private void print() {
            System.out.println("TestAAAAAA");
        }
    }
    
    public class TestB {
        private void print() {
            System.out.println("TestBBBBBB");
        }
    }
    
    public class TestC {
        private void print() {
            System.out.println("TestCCCC");
        }
    }
  2. 定义实现 ImportSelector 的类,用来注入 TestB 的实例
    public class MySelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            return new String[]{"com.demo.TestB"};
        }
    }
  3. 定义实现 ImportBeanDefinitionRegistrar 接口的类,用来注入 TestC 的实例
    public class MyRegistrar implements ImportBeanDefinitionRegistrar {
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //构造 BeanDefinition
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class);
            //注册 bean, 并给其取个名字
            registry.registerBeanDefinition("testC",rootBeanDefinition);
        }
    }
  4. 定义配置类,通过 @Import 注解注入 bean 实例
    @Import({TestA.class, MySelector.class, MyRegistrar.class})
    public class ImportDemo {
    }
  5. 编写测试类
    public class ImportMain {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ImportDemo.class);
            String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
            //打印出 IOC 容器里所有的 bean
            for (String name : beanDefinitionNames) {
                System.out.println(name);
            }
        }
    }

  6. 测试结果:第 1 部分是 spring 内置的 bean,importDemo 为配置类 bean,第 2 部分的三个bean即为通过 @Import 注解注入的三个 Bean。由此可以看出,通过@Import 直接引入类名和实现ImportSelector的类的bean的名称为全类名,而通过实现ImportBeanDefinitionRegistrar 的类注入的 bean 的名称为自己取的名字。


3:原理解析

  •  如果不了解 ConfigurationClassPostProcessor 的同学,可以先去看我写的另一篇文章去了解一下Bean工厂后置处理器之 ConfigurationClassPostProcessor- Spring 到底怎么扫描到它所需要管理的bean的?,这篇文章比较详细的跟踪了 Spring 是如何扫描标注了 @Component 注解的 bean。
    • 我们在上一篇文章中跟踪到了 doProcessConfigurationClass 方法,并进入了处理 @ComponentScans 的逻辑,但是同时我们也意识到了,还有单独处理 @Import 注解的逻辑,接下来我们就跟进一下,看看 Spring 到底是怎么处理的。
      protected final SourceClass doProcessConfigurationClass(
      			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      			throws IOException {
      
      		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      			// Recursively process any member (nested) classes first
      			processMemberClasses(configClass, sourceClass, filter);
      		}
      
      		// Process any @PropertySource annotations
      		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
      				sourceClass.getMetadata(), PropertySources.class,
      				org.springframework.context.annotation.PropertySource.class)) {
      			if (this.environment instanceof ConfigurableEnvironment) {
      				processPropertySource(propertySource);
      			}
      			else {
      				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
      						"]. Reason: Environment must implement ConfigurableEnvironment");
      			}
      		}
      
      		// Process any @ComponentScan annotations
      		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
      				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
      		if (!componentScans.isEmpty() &&
      				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      			for (AnnotationAttributes componentScan : componentScans) {
      				// The config class is annotated with @ComponentScan -> perform the scan immediately
      				Set<BeanDefinitionHolder> scannedBeanDefinitions =
      						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
      				// Check the set of scanned definitions for any further config classes and parse recursively if needed
      				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
      					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      					if (bdCand == null) {
      						bdCand = holder.getBeanDefinition();
      					}
      					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
      						parse(bdCand.getBeanClassName(), holder.getBeanName());
      					}
      				}
      			}
      		}
      
      		// Process any @Import annotations
              接下来我们跟踪一下这个方法,看一下到底是怎么处理 @Import 注解的
      		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
      
      		// Process any @ImportResource annotations
      		AnnotationAttributes importResource =
      				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
      		if (importResource != null) {
      			String[] resources = importResource.getStringArray("locations");
      			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      			for (String resource : resources) {
      				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
      				configClass.addImportedResource(resolvedResource, readerClass);
      			}
      		}
      
      		// Process individual @Bean methods
      		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
      		for (MethodMetadata methodMetadata : beanMethods) {
      			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
      		}
      
      		// Process default methods on interfaces
      		processInterfaces(configClass, sourceClass);
      
      		// Process superclass, if any
      		if (sourceClass.getMetadata().hasSuperClass()) {
      			String superclass = sourceClass.getMetadata().getSuperClassName();
      			if (superclass != null && !superclass.startsWith("java") &&
      					!this.knownSuperclasses.containsKey(superclass)) {
      				this.knownSuperclasses.put(superclass, configClass);
      				// Superclass found, return its annotation metadata and recurse
      				return sourceClass.getSuperClass();
      			}
      		}
      
      		// No superclass -> processing is complete
      		return null;
      	}

      ​​​​​​​进入处理 @Import 的注解的方法如下

      private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      			boolean checkForCircularImports) {
      
      		if (importCandidates.isEmpty()) {
      			return;
      		}
      
      		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
      		}
      		else {
      			this.importStack.push(configClass);
      			try {
      				for (SourceClass candidate : importCandidates) {
      					if (candidate.isAssignable(ImportSelector.class)) {
      						// Candidate class is an ImportSelector -> delegate to it to determine imports
      						Class<?> candidateClass = candidate.loadClass();
      						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
      								this.environment, this.resourceLoader, this.registry);
      						Predicate<String> selectorFilter = selector.getExclusionFilter();
      						if (selectorFilter != null) {
      							exclusionFilter = exclusionFilter.or(selectorFilter);
      						}
      						if (selector instanceof DeferredImportSelector) {
      							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
      						}
      						else {
      							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
      							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
      							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
      						}
      					}
      					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
      						// Candidate class is an ImportBeanDefinitionRegistrar ->
      						// delegate to it to register additional bean definitions
      						Class<?> candidateClass = candidate.loadClass();
      						ImportBeanDefinitionRegistrar registrar =
      								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
      										this.environment, this.resourceLoader, this.registry);
      						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
      					}
      					else {
      						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
      						// process it as an @Configuration class
      						this.importStack.registerImport(
      								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
      						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
      					}
      				}
      			}
      			catch (BeanDefinitionStoreException ex) {
      				throw ex;
      			}
      			catch (Throwable ex) {
      				throw new BeanDefinitionStoreException(
      						"Failed to process import candidates for configuration class [" +
      						configClass.getMetadata().getClassName() + "]", ex);
      			}
      			finally {
      				this.importStack.pop();
      			}
      		}
      	}

      可以看到,上面有三个 if  else 判断,分别处理上面我们列举出来的三种场景,1:通过@Import 引入的实现了 ImportSelector 接口的类 2: 通过@Import引入的实现了 ImportBeanDefinitionRegistrar 的类 3:通过@Import引入的处理引入的普通的 java 类

      • 处理实现了 ImportSelector 的逻辑

        if (candidate.isAssignable(ImportSelector.class)) {
        						// Candidate class is an ImportSelector -> delegate to it to determine imports
        						Class<?> candidateClass = candidate.loadClass();
        						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
        								this.environment, this.resourceLoader, this.registry);
        						Predicate<String> selectorFilter = selector.getExclusionFilter();
        						if (selectorFilter != null) {
        							exclusionFilter = exclusionFilter.or(selectorFilter);
        						}
                                //实现了 DeferredImportSelector 的会暂时放到一个 list 中,最后再处理
        						if (selector instanceof DeferredImportSelector) {
        							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
        						}
        						else {
                                    //这里可以看到调用了selectImports方法,就是我们实现Selector接口重写的方法,我们返回了class数组, 在这里就取到了我们的返回值
        							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
        							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                                    //递归调用,因为导入的类可能又间接导入了其他类
        							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
        						}
        					}

        通过上面我们可以看到,这里调用了我们自己重写的方法,然后又将得到的class数组,继续递归调用,尝试解析其他 Import 的bean,但是我们目前却没有看到有关注册bean定义的逻辑,这里先不着急,我们接着看第三个 if else 逻辑,即没有实现ImportSelect 也没有实现 ImportBeanDefinitionRegistrar 的逻辑,因为随着我们的递归调用,最后一定会走到这一步

        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        						// process it as an @Configuration class
        						this.importStack.registerImport(
        								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

        可以看到,这里也是什么都没做,又重新递归调用解析配置类的逻辑,我们再回到解析配置类的逻辑看一下

        protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        			return;
        		}
        
        		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        		if (existingClass != null) {
        			if (configClass.isImported()) {
        				if (existingClass.isImported()) {
        					existingClass.mergeImportedBy(configClass);
        				}
        				// Otherwise ignore new imported config class; existing non-imported class overrides it.
        				return;
        			}
        			else {
        				// Explicit bean definition found, probably replacing an import.
        				// Let's remove the old one and go with the new one.
        				this.configurationClasses.remove(configClass);
        				this.knownSuperclasses.values().removeIf(configClass::equals);
        			}
        		}
        
        		// Recursively process the configuration class and its superclass hierarchy.
        		SourceClass sourceClass = asSourceClass(configClass, filter);
        		do {
                    //又回到刚刚解析配置类的方法,如解析 @ComponentScan, @Import 等
        			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
        		}
        		while (sourceClass != null);
                //最终会走到这里,把该 bean 放到一个 map 中保存
        		this.configurationClasses.put(configClass, configClass);
        	}

         可以看到,这里又要回到解析配置类的逻辑了,而假设我们Import的就是一个普通的类,就想要这个类的 bean, 它根本没有@Import, @ComponentScan之类的东西,可以预料到,再次进入解析配置类的逻辑也还是什么都没做的,即还是获取不到我们想要的 bean 定义,不过这是,我们留意到,最后一行会有一个 map 把这个类给存起来。那是不是有可能再解析完成之后的某个地方会调用这个map,来生成对应的bean定义呢?验证这一点的话,就得先回到解析配置类入口的地方。

ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
            //parse方法执行结束,代表解析结束
			parser.validate();
            
            //可以看到,这里就获取了我们刚刚保存起来的bean
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
            //执行这个加载bean定义的方法,看起来就像是我们想寻找的
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

         可以看到,解析完成之后,通过调用 getConfigurationClasses 方法获取到一些类

public Set<ConfigurationClass> getConfigurationClasses() {
        //可以看到,这就是我们刚刚保存时用到的 map, 这里将所有的key返回
		return this.configurationClasses.keySet();
	}

        拿到类信息之后,首先去除已经被解析过的,然后就调用 this.reader.loadBeanDefinitions(configClasses);

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

        这里会循环刚刚拿到的所有类信息,然后调用一个方法,我们继续跟进

private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
        //解析通过Import导入的类
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		
        //解析通过 Registra 引入的类
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

                这里我们不仅看到了处理@Import导入的普通类的逻辑,还看到了最后一行解析 Registra 引入的类,我们先看一下@Import导入的普通类的逻辑

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
		AnnotationMetadata metadata = configClass.getMetadata();
		AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

		ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
		configBeanDef.setScope(scopeMetadata.getScopeName());
		String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
		AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
		configClass.setBeanName(configBeanName);

		if (logger.isTraceEnabled()) {
			logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
		}
	}

        这里就简单了,这就是我们想要寻找的,首先实例化bean定义,然后注册bean定义。

        这里总结一下,目前通过导入Selector引入的类和@Import导入的普通类的逻辑已经清楚了,即selector会调用我们重写的方法,然后获取到我们重写方法返回的class数组,然后递归调用解析,最终会进入处理@Import普通类的逻辑,然后解析过程中没有进行 beanDefinition 的注册,而是在一个map中暂存起来。解析配置类完成之后,才把刚刚暂存起来的类信息再拿出来,进行bean定义的实例化和注册。

        刚刚我们还留意到了,处理Registra的方法,这个很像我们@Import导入的另外一种方式,而正是唯一一个我们还没有理清逻辑的地方,我们点进去跟进一下

loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());

        这个方法跟刚刚解析类数组类似,只不过改成了批量处理 registra

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
	}

        我们看到这里就会很眼熟,因为,我们在实现 ImportBeanDefinitionRegistrar 接口的时候,会重写这个方法,并在其进行了bean定义的注册,由此,我们知道了,我们重写的这个方法到底什么时候被调用了。此时的问题就是,这么多registra是从哪里来的,难道是跟刚刚的逻辑一样,也是解析配置类的时候,并没有真正注册bean定义,而是在某个地方暂存起来吗?看起来很可能是这样的。而且这里便利 registrars 的时候是直接遍历 map,所以很可能也是解析配置类的时候把这些 registrars 暂存到 map 里了。下面我们验证一下。

public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //构造 BeanDefinition
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestC.class);
        //注册 bean, 并给其取个名字
        registry.registerBeanDefinition("testC",rootBeanDefinition);
    }
}

        我们再次回到解析 @Import 的逻辑 

else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
                       //这里实例化了一个registrar
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
//难道这个方法就是把刚刚实例化的registrar给暂存起来?不是吧不是吧
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        //果然,这里跟刚刚处理普通java类的逻辑一样,也是在一个map里暂存起来了
		this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
	}

        而这个 map 就是刚刚我们 for 循环遍历的map

       至此为止,三种 @Import 引入 Java Bean 的逻辑全部都理清了,当然这里只是扫描到了所有的 beanDefinitions,后面才会循环所有的 beanDefinitions 再调用 getBean,来创建所有的bean。这里暂时就先不展开了。

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
@Import注解是Spring框架中的一个注解,它用于将其他类或配置导入到当前类中。@Import可以单独使用,也可以和其他注解一起使用,例如@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口。 当@Import注解单独使用时,它可以直接将其他普通的类导入到当前类中,以便在当前类中可以使用被导入的类。 当@Import注解结合@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口使用时,在Spring Boot中是最常见的用法之一。举个例子,如果我们在一个类上使用@EnableAutoConfiguration注解,那么在该注解的源码中会使用@Import注解来导入AutoConfigurationImportSelector类。这样,通过@EnableAutoConfiguration注解,我们可以自动配置应用程序的一些默认设置。 总结来说,@Import注解是用来将其他类或配置导入到当前类中的注解,可以单独使用,也可以和其他注解一起使用,常见的用法是结合@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口在Spring Boot中实现自动配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [@Import注解的四种使用方式](https://blog.csdn.net/bluemysky/article/details/128827769)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [@Import注解使用](https://blog.csdn.net/m0_55806905/article/details/127967036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值