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 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 imports, Set 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 importCandidates, Predicate 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 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 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 5.3.4,如有错误,欢迎指正。
韦伯望远镜已完成部署那,地球外最大的天文台下一步将何去何从詹姆斯韦伯空间望远镜已完成部署。那么,地球外最大的天文台下一步将何去何从?这个新一代天文台正日渐接近观测点,并进入主镜和仪器测试准备期。美国宇航局(NASA)詹姆斯韦伯空间望远镜(
天上的小星星天上的小星星眨巴着你的小眼睛在看着蔚蓝色的地球还看着宇宙的星空地球上的人们探索着宇宙星空的无穷那浩瀚无垠的星空吸引着我们的眼镜小星星挥舞着小手代表着对地球人的欢迎人们探索宇宙的脚步
石油的来源有科学家提出石油用之不尽,本人赞同。以前西方人认为,石油是由动植物埋在地下,经地球运动形成的。所以总说节约能源,能源会有耗尽的一天。危言耸听的说二三年内耗尽,可事实是三十年又三十年
又创下世界纪录,中国人造太阳放电突破1000秒,技术领先美国图为中国人造太阳近年来中国在高科技领域内取得的突破越来越多,比如在可控核聚变领域内,最近中国又创下了一项世界纪录,中国托卡马克人造太阳核聚变放电时间突破了1000秒,刷新了此前由美
创世界纪录!中国科大实现半导体量子比特的超快操控来源中科院量子信息重点实验室中国科学技术大学郭光灿院士团队在硅基半导体自旋量子比特操控研究中取得重要进展。该团队郭国平教授李海欧研究员与中科院物理所张建军研究员等人,和美国澳大利亚
没有宇宙大爆炸通过星系红移现象证明了宇宙间的物质之间变远,从而得出宇宙膨胀结论。宇宙膨胀没有错。但是进行逆运算,就得出宇宙汇聚于一点。这一点就是奇点。从而得出奇点的密度无限大,奇点的时空无限扭曲
每天移动1毫米詹姆斯韦伯太空望远镜开始对齐主镜人类历史上具有里程碑意义的太空观测仪器詹姆斯韦伯太空望远镜(JamesWebbSpaceTelescope)发射升空已经过去了19天,那么,这台耗资接近100亿美元,被科学家寄予厚
维珍轨道公司成功发射七颗立方体卫星,其中四颗美国军方测试卫星北京时间1月14日5时39分,维珍轨道公司的一架名为宇宙女孩的747800飞机从美国加利福尼亚州莫哈韦航空航天港起飞,6时53分在太平洋上空发射了其LauncherOne运载火箭,
在太空中生活,女航天员的生理期怎么办?航天女英雄牺牲太大了太空是微重力环境,并且充斥着宇宙线和微流星体等危险,人类必须依靠航天服等设备才能顺利进入,否则会受到致命的伤害。太空危险重重,面临着无数艰难险阻,所以航天员要经历一系列难度极高的训
詹姆斯韦伯太空望远镜为啥能穿越到130亿年前?詹姆斯韦伯太空望远镜为啥能穿越到130亿年前?其主要原因之一就是詹姆斯韦伯太空望远镜采用了非常敏感的传感器,而该传感器专门通过中红外线波长(0。628。3微米)的长波可见光(红色)
定于3月前往国际空间站的俄罗斯宇航员将执行六至七次太空行走据俄罗斯卫星通讯社1月14日报道,新一期国际空间站考察组指令长奥列格阿尔捷米耶夫表示,定于3月前往国际空间站的俄罗斯宇航员计划在值守期间执行六至七次太空行走。俄罗斯宇航员奥列格阿尔
鱼腥草为什么叫折耳根?多年前,我和一个朋友去野外拍摄昆虫。拍着拍着,他却被脚边的一种小草吸引,开始蹲在地上挖起草来。我舅妈爱吃这个,挖点回去。他说。这是什么野菜?我问。他掐了一把叶子搓了搓,一下怼到我的
华为鸿蒙OS车机操作系统亮相上海车展日前,华为搭载于极狐阿尔法s的自动驾驶系统进行了首次公开试乘。近日,华为在这方面也是果断出手,动作不断。4月17日,北汽新能源旗下高端品牌极狐联合华为发布了极狐阿尔法S华为HI版,
中国反无人机更先进,为何俄罗斯却向土耳其求购?两个原因无人机在现代战场上的影响力,相信很多军迷都已经是看在眼里了,定点清除,执行各类战术任务,成为现代战争不可或缺的重要元素。当然,矛与盾从来都是相克相生的,随着人类无人机在军事领域作用
下一代航母曝光排水量75000吨,核动力,法国行吗?说到法国海军,我们很容易就会联想到其现役航母戴高乐号,这艘航母采用核动力技术。也是这艘航母,让法国海军成为了美国海军之后第二个使用核动力航母的国家,如此听来,法国海军真的很豪气,但
立陶宛真豪气?买直升机只选贵的!其实另有目的说到美国的黑鹰直升机,其实很多人并不陌生。因为这是我国唯一一款网友最为熟悉的美制装备,它出色的高原性能让无比挑剔的军迷都表示无可挑剔。所以美国这款直升机在国际市场上有真爱粉是不足为
法国尝到甜头不肯走了!再向印度兜售加油机,不过这次是二手的为了提升战斗机的远程作战能力,空中加油机就应运而生了。随着现代战争的形势变化,不少国家目前已把加油机发展成不可或缺的战略机型了。这就使得有能力的国家开始大力研发加油机,而没有能力的
永劫无间迦南进阶玩法网易云游戏无需配置带你快速上分最近在永劫无间的排位赛中,迦南这名角色的出场率十分高。迦南的高机动性既适合进攻也适合逃命,非常适合玩家们拿来上分。现在使用这名角色的人这么多,小伙伴们可以学习一些迦南的进阶玩法,上
它曾是法国幻影家族的一员,性能可与F15对抗,为何会被放弃?法国一直是一个军事和政治上独立自主的国家,在美苏争霸时,美苏两国都非常重视军事武器的研究发展,而法国也在构筑自己的国防体系,法国由于是自主独立国家,所以它得不到美国像支援他小弟同样
产业数字化语境下的计算人才培养产教融合的突围战文章来源Alter聊科技IDspnews每隔一段时间,华为就会发一次声明,表示不造车的决心。这家ICT大厂,在汽车领域的每一次动作,都会掀起热议。即便华为高管多次公开表示不造车,是
双鱼座有着怎么样的性格和个性?视频给你详细分析今天和大家谈一谈,处女座是个很明显的特点。如果你是处女座,你可以更好的了解自己如果你不是处女座,那么你可以更好的了解你的朋友或者恋人。准确的说,处女座的出生是在阳历的八月二十三号到
元宇宙里的后人类社会有学者认为,元宇宙指向具有历史意义的新时代,如航海时代工业革命时代航天时代,为人类社会最终数字化转型提出了新路径。这是一个与这个时代同频共鸣的后人类社会。后人类也被称为赛博格。当人