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注解到底是个啥?的主要内容,如果未能解决你的问题,请参考以下文章