Spring里面的Import注解到底是个啥?

Posted 栏观科技

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring里面的Import注解到底是个啥?相关的知识,希望对你有一定的参考价值。

 Spring提供了几种方式来注册Bean,日常开发中使用最多的是ComponentScan。得益于ComponentScan,注册bean非常的简单,只需要在被注册的类上声明@Component或者@Service等注解即可。

 除了ComponentScan,Spring还支持使用Configuration注解来注册Bean。在大型的项目中,模块化开发能极大地降低系统的复杂性,这时需要每个模块来定义本模块Bean注册情况,Configuration发挥着巨大的作用。

@Configuration
@ConditionalOnProperty(prefix = "module.wx-login", value = "enable", havingValue = "true")
@ComponentScan(basePackages = "com.lin.decorator.wxlogin")
public class WxLoginConfiguration 

 每个模块定义了Configuration之后,需要将多个模块的Configuration组合。Spring提供了Import注解来实现多个Configuration组合。

@Import(WxLoginConfiguration.class)
public class Application 
    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    

 Spring官方文档中关于Import的描述如下:

Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class<?>...)). @Bean definitions declared in imported @Configuration classes should be accessed by using @Autowired injection. Either the bean itself can be autowired, or the configuration class instance declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly navigation between @Configuration class methods.

 除了Configuration,Import还支持引入ImportSelector和ImportBeanDefinitionRegistrar。既然要全面了解Import机制,那另外两个也要一探究竟。

ImportSelector

 Spring官方文档中,对ImportSelector的描述如下:
Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.

 从字面上理解,ImportSelector可以根据注解里面的一个或多个属性来决定引入哪些Configuration。举个例子:

 小伙伴都用过Transactional注解,Transactional注解生效的前提是EnableTransactionManagement生效。看过EnableTransactionManagement源代码的小伙伴应该都知道,它通过Import引入了一个ImportSelector。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement 
	boolean proxyTargetClass() default false;
	AdviceMode mode() default AdviceMode.PROXY;
	int order() default Ordered.LOWEST_PRECEDENCE;

 而TransactionManagementConfigurationSelector会根据注解里面的AdviceMode不同,来确定引入不同的Configuration。

protected String[] selectImports(AdviceMode adviceMode) 
    switch (adviceMode) 
        case PROXY:
            return new String[] AutoProxyRegistrar.class.getName(),
					ProxyTransactionManagementConfiguration.class.getName();
		case ASPECTJ:
		    return new String[] determineTransactionAspectClass();
		default:
		    return null;
    

ImportBeanDefinitionRegistrar

 Spring官方文档中,对ImportBeanDefinitionRegistrar的描述如下:
Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary.

 字面意思是,通过继承这个接口可以额外定义Bean。举个例子:

 在使用Mybatis的时候,会使用到MapperScan这个注解,这个注解通过Import引入了ImportBeanDefinitionRegistrar,这也解释了为什么我们只在Interface上申明了一个Mapper,mybatis就帮我们生成好了Bean。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan 

 有小伙伴在编码过程中,并没有使用MapperScan,为什么也能正常使用呢?其实是Mybatis starter的功劳。在MybatisAutoConfiguration里面定义了ImportBeanDefinitionRegistrar,当MapperScan没有激活时,它就会生效。

@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean( MapperFactoryBean.class, MapperScannerConfigurer.class )
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean 
    @Override
    public void afterPropertiesSet() 
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan," +
           "MapperFactoryBean and MapperScannerConfigurer.");
    
  

Import执行流程

 了解了Import支持的三种不同类型的资源之后,接下来debug看一下import的执行过程。通过设置断点,发现在ConfigurationClassParser类中,通过深度遍历来处理Import。

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException 

    if (visited.add(sourceClass)) 
        for (SourceClass annotation : sourceClass.getAnnotations()) 
            String annName = annotation.getMetadata().getClassName();
            if (!annName.equals(Import.class.getName())) 
                collectImports(annotation, imports, visited);
            
        
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    

 而上面介绍的ImportSelector,需要调用selectImports方法进行解析。

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();
			
		
	
这样递归调用,就实现了资源的加载。

以上是关于Spring里面的Import注解到底是个啥?的主要内容,如果未能解决你的问题,请参考以下文章

Spring里面的Import注解到底是个啥?

Spring里面的Import注解到底是个啥?

Spring:IOC容器到底是咋肥事啊?

Spring:IOC容器到底是咋肥事啊?

话说Spring 5里的WebFlux到底是个啥?

Spring AOP Proxy创建出来的到底是个啥?