idea去掉无用import类_@Import注解的魅力
? ? 本篇主要介紹Spring注解@Import的魅力所在:它能讓你高度自由的定義配置類(lèi)裝載規(guī)則與Bean注冊(cè)邏輯。@Import是Spring體系中的一個(gè)比較重要的注解,下面讓我們一起看看它都有哪些神奇的魅力吧!
注冊(cè)Bean
?? @Import第一個(gè)功能就是注冊(cè)Bean到Spring容器中,用法非常簡(jiǎn)單,將需要注冊(cè)到Spring的類(lèi)通過(guò)@Import直接導(dǎo)入即可,這時(shí)候注冊(cè)的Bean對(duì)應(yīng)的名稱(chēng)為類(lèi)完全限定名。如下所示:
package?com.swj.mj.autoconfig.service;@Slf4j
public?class?HelloService?{
????public?HelloService()?{
????????log.info("HelloService?Initialization...");
????}
????public?String?sayHello(String?name)?{
????????return?"Hello?"?+?name;
????}
}
? ??首先任意定義一個(gè)類(lèi),然后我們直接在某個(gè)Bean或者配置類(lèi)上通過(guò)@Import(HelloService.class)方法注冊(cè)Bean,如下:
package?com.swj.mj.web.hello;@RestController
@AllArgsConstructor
@RequestMapping("/hello")
@Import(HelloService.class)
public?class?HelloController?implements?ApplicationContextAware?{
????private?static?ApplicationContext?appCtx;
????private?final?HelloService?helloService;
????@GetMapping
????public?Map?hello(String?name)?{
????????String?qualifiedBeanName?=?HelloService.class.getName();
????????assert?appCtx.containsBean(qualifiedBeanName)?:?"Spring容器中找不到名稱(chēng)為"?+?qualifiedBeanName?+?"的Bean";
????????String[]?beanNames?=?appCtx.getBeanNamesForType(HelloService.class);
????????assert?beanNames.length?==?1?&&?qualifiedBeanName.equals(beanNames[0]);
????????return?ImmutableMap.of("name",?name,?"helloResult",?helloService.sayHello(name));
????}
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????appCtx?=?applicationContext;
????}
}
? ??創(chuàng)建一個(gè)啟動(dòng)類(lèi):
package?com.swj.mj.web;@SpringBootApplication
public?class?WebApplication?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????SpringApplication.run(WebApplication.class,?args);
????}
}
? ? 添加JVM選項(xiàng)-ea(激活斷言功能)并啟動(dòng)容器后(啟動(dòng)類(lèi)的所在包路徑為com.swj.mj.web,并且沒(méi)有額外配置掃描包,所以默認(rèn)掃描的包是com.swj.mj.web),可以看到如下日志打印,說(shuō)明@Import可以用來(lái)注冊(cè)Bean。
@Import注冊(cè)SpringBean? ??同時(shí),請(qǐng)求后可以看到兩個(gè)斷言都成立,說(shuō)明通過(guò)@Import注冊(cè)的SpringBean的beanName為類(lèi)對(duì)應(yīng)的類(lèi)完全限定名。
@Import注冊(cè)的SpringBean對(duì)應(yīng)名稱(chēng)? ??如果我們?nèi)サ袅?#64;Import(HelloService.class),那么應(yīng)用啟動(dòng)后將直接失敗并終止:
去掉@Import后將直接找不到類(lèi)以致于啟動(dòng)失敗導(dǎo)入配置類(lèi)
? ??第二個(gè)功能是直接導(dǎo)入配置類(lèi)。我們?cè)谠鹊幕A(chǔ)上新增一個(gè)配置類(lèi)HelloServiceConfiguration:
@Configurationpublic?class?HelloServiceConfiguration?{
????@Bean
????@ConditionalOnMissingBean
????public?HelloService?helloService()?{
????????return?new?HelloService();
????}
}
? ??該配置類(lèi)會(huì)在容器中沒(méi)有helloService這個(gè)Bean時(shí)自動(dòng)注冊(cè)HelloService。然后修改HelloController:
@RestController@AllArgsConstructor
@RequestMapping("/hello")
@Import(HelloServiceConfiguration.class)
public?class?HelloController?implements?ApplicationContextAware?{
????private?static?ApplicationContext?appCtx;
????private?final?HelloService?helloService;
????@GetMapping
????public?Map?hello(String?name)?{
????????String?beanName?=?"helloService";
????????assert?appCtx.containsBean(beanName)?:?"Spring容器中找不到名稱(chēng)為"?+?beanName?+?"的Bean";
????????String[]?beanNames?=?appCtx.getBeanNamesForType(HelloService.class);
????????assert?beanNames.length?==?1?&&?beanName.equals(beanNames[0]);
????????return?ImmutableMap.of("name",?name,?"helloResult",?helloService.sayHello(name));
????}
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????appCtx?=?applicationContext;
????}
}
? ??這里通過(guò)@Import(HelloServiceConfiguration.class)直接導(dǎo)入配置類(lèi),由配置類(lèi)注冊(cè)HelloService,這時(shí)候的beanName就不是類(lèi)完全限定名了,而是方法名helloService。
? ??實(shí)際上HelloServiceConfiguration同樣會(huì)被注冊(cè)到Spring容器中,通過(guò)appCtx.getBean(HelloServiceConfiguration.class)可以得到配置類(lèi)。所以可以說(shuō)第一個(gè)功能與第二個(gè)功能是重合的,這點(diǎn)在源碼上也可以得到解釋,具體可以參考o(jì)rg.springframework.context.annotation.ConfigurationClassParser#processImports。本篇不講源碼哈,只介紹如何使用。
選擇性裝載配置類(lèi)
? ??第三個(gè)功能是通過(guò)實(shí)現(xiàn)org.springframework.context.annotation.ImportSelector接口完成動(dòng)態(tài)開(kāi)啟配置類(lèi)。
? ??首先看一下ImportSelector接口的定義:
public?interface?ImportSelector?{?/**
??*?Select?and?return?the?names?of?which?class(es)?should?be?imported?based?on
??*?the?{@link?AnnotationMetadata}?of?the?importing?@{@link?Configuration}?class.
??*?@return?the?class?names,?or?an?empty?array?if?none
??*/
?String[]?selectImports(AnnotationMetadata?importingClassMetadata);
?/**
??*?Return?a?predicate?for?excluding?classes?from?the?import?candidates,?to?be
??*?transitively?applied?to?all?classes?found?through?this?selector's?imports.
??*?
If?this?predicate?returns?{@code?true}?for?a?given?fully-qualified
??*?class?name,?said?class?will?not?be?considered?as?an?imported?configuration
??*?class,?bypassing?class?file?loading?as?well?as?metadata?introspection.
??*?@return?the?filter?predicate?for?fully-qualified?candidate?class?names
??*?of?transitively?imported?configuration?classes,?or?{@code?null}?if?none
??*?@since?5.2.4
??*/
?@Nullable
?default?Predicate?getExclusionFilter()?{
??return?null;
?}
}
? ??主要關(guān)注String[] selectImports(AnnotationMetadata importingClassMetadata)方法,參數(shù)importingClassMetadata表示使用了@Import(? extends ImportSelector)注解的類(lèi)的元數(shù)據(jù)。通過(guò)該參數(shù)可以實(shí)現(xiàn)獲取一些組合注解的自定義屬性等內(nèi)容,從而實(shí)現(xiàn)選擇性裝載配置類(lèi)。
「注意這個(gè)方法的返回值不能是null,極端情況請(qǐng)返回空數(shù)組,否則運(yùn)行將拋出NPE。」
? ??讓我們先創(chuàng)建一個(gè)SpringUtil:
package?com.swj.mj.autoconfig.util;public?class?SpringUtil?implements?BeanFactoryAware,?ApplicationContextAware,?Ordered?{
????private?static?ApplicationContext?appCtx;
????private?static?BeanFactory?beanFactory;
????public?static?ApplicationContext?getAppCtx()?{
????????return?appCtx;
????}
????public?static?BeanFactory?getBeanFactory()?{
????????return?beanFactory;
????}
????@Override
????public?void?setBeanFactory(BeanFactory?beanFactory)?throws?BeansException?{
????????SpringUtil.beanFactory?=?beanFactory;
????}
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????SpringUtil.appCtx?=?applicationContext;
????}
????@Override
????public?int?getOrder()?{
????????return?HIGHEST_PRECEDENCE?+?1;
????}
}
? ??這個(gè)類(lèi)主要用于通過(guò)靜態(tài)持有Spring的ApplicationContext和BeanFactory,后續(xù)可以直接通過(guò)該類(lèi)訪問(wèn)Spring容器。然后創(chuàng)建我們的ImportSelector配置類(lèi)裝載選擇器:
package?com.swj.mj.autoconfig.configuration;public?class?CustomImportSelector?implements?ImportSelector?{
????@Override
????public?String[]?selectImports(AnnotationMetadata?importingClassMetadata)?{
????????//?importingClassMetadata?指使用?@Import(HelloServiceImportSelector)?時(shí)?@Import?注解所在類(lèi)的元數(shù)據(jù)
????????String?enableCustomConfig?=?EnableCustomConfig.class.getName();
????????if?(importingClassMetadata.hasAnnotation(enableCustomConfig))?{
????????????Map?attrs?=?importingClassMetadata.getAnnotationAttributes(enableCustomConfig);if?(MapUtils.isNotEmpty(attrs))?{
????????????????String?registerUtil?=?Optional.ofNullable(attrs.get("registerUtil")).map(Object::toString).orElse("false");if?(Boolean.parseBoolean(registerUtil))?{return?new?String[]{
????????????????????????????HelloServiceConfiguration.class.getName(),
????????????????????????????SpringUtil.class.getName()
????????????????????};
????????????????}
????????????}
????????}return?new?String[]{
????????????????HelloServiceConfiguration.class.getName()
????????};
????}
}
? ??然后創(chuàng)建我們的EnableCustomConfig注解:
package?com.swj.mj.autoconfig.annotation;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomImportSelector.class)
public?@interface?EnableCustomConfig?{
????/**
?????*?是否注冊(cè)?{@link?com.swj.mj.autoconfig.util.SpringUtil}?工具類(lèi),默認(rèn)為?{@literal?true}
?????*/
????boolean?registerUtil()?default?true;
}
? ??這里簡(jiǎn)單解釋一下:首先@EnableCustomConfig注解組合了@Import(CustomImportSelector.class),這樣在CustomImportSelector#selectImports方法中可以通過(guò)importingClassMetadata獲取得到@EnableCustomConfig注解的屬性配置。然后通過(guò)該配置實(shí)現(xiàn)添加裝載配置。這里我們判斷registerUtil是否為true(默認(rèn)為true),為true時(shí)將裝載SpringUtil以便后續(xù)可以通過(guò)SpringUtil訪問(wèn)Spring容器。
? ??接著調(diào)整HelloController:
@RestController@AllArgsConstructor
@RequestMapping("/hello")
@EnableCustomConfig
public?class?HelloController?{
????private?final?HelloService?helloService;
????@GetMapping
????public?Map?hello(String?name)?{
????????ApplicationContext?appCtx?=?SpringUtil.getAppCtx();
????????assert?Objects.nonNull(appCtx)?:?"appCtx?為?null";
????????assert?appCtx.containsBean(SpringUtil.class.getName());
????????assert?appCtx.containsBean(HelloServiceConfiguration.class.getName());
????????assert?appCtx.getBean(HelloService.class)?==?helloService;
????????return?ImmutableMap.of("name",?name,?"helloResult",?helloService.sayHello(name));
????}
}
? ??這里通過(guò)添加@EnableCustomConfig激活聯(lián)動(dòng)激活@Import(CustomImportSelector.class)以注冊(cè)SpringUtil和HelloServiceConfiguration,由于默認(rèn)registerUtil為true,所以會(huì)注冊(cè)SpringUtil。啟動(dòng)應(yīng)用后能訪問(wèn)http://localhost:8080/hello?name=Reka表示斷言均成立,容器中確實(shí)注冊(cè)了對(duì)應(yīng)的Bean。
? ??接著我們調(diào)整@EnableCustomConfig為@EnableCustomConfig(registerUtil = false),再次啟動(dòng)容器訪問(wèn)http://localhost:8080/hello?name=Reka,這時(shí)候會(huì)發(fā)現(xiàn)斷言失敗:
通過(guò)注解屬性配置選擇性裝載配置類(lèi)動(dòng)態(tài)注冊(cè)Bean
? ??這是@Import的第四個(gè)功能,通過(guò)org.springframework.context.annotation.ImportBeanDefinitionRegistrar自由注冊(cè)SpringBean。該接口的定義如下:
public?interface?ImportBeanDefinitionRegistrar?{?default?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry,
???BeanNameGenerator?importBeanNameGenerator)?{
??registerBeanDefinitions(importingClassMetadata,?registry);
?}
?default?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry)?{
?}
}
? ??主要看void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry),其中參數(shù)importingClassMetadata與ImportSelector里的參數(shù)效用一致,registry參數(shù)簡(jiǎn)單理解是Bean注冊(cè)器,通過(guò)它可以往Spring容器中注冊(cè)Bean。現(xiàn)在讓我們通過(guò)動(dòng)態(tài)注冊(cè)Bean的方式實(shí)現(xiàn)CustomImportSelector的功能。
package?com.swj.mj.autoconfig.configuration;public?class?CustomImportBeanDefinitionRegistrar?implements?ImportBeanDefinitionRegistrar?{
????@Override
????public?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry,?BeanNameGenerator?importBeanNameGenerator)?{
????????AnnotationAttributes?attrs?=?AnnotatedElementUtils.getMergedAnnotationAttributes(
????????????????ClassUtils.resolveClassName(importingClassMetadata.getClassName(),?null),
????????????????EnableCustomBean.class);
????????if?(MapUtils.isNotEmpty(attrs)?&&?BooleanUtils.isTrue(attrs.getBoolean("registerUtil")))?{
????????????registry.registerBeanDefinition("springUtil",?new?RootBeanDefinition(SpringUtil.class));
????????}
????????registry.registerBeanDefinition("helloService",?new?RootBeanDefinition(HelloService.class));
????}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomImportBeanDefinitionRegistrar.class)
public?@interface?EnableCustomBean?{
????/**
?????*?是否注冊(cè)?{@link?com.swj.mj.autoconfig.util.SpringUtil}?工具類(lèi),默認(rèn)為?{@literal?true}
?????*/
????boolean?registerUtil()?default?true;
}
? ? CustomImportBeanDefinitionRegistrar的邏輯與CustomImportSelector基本一致,只不過(guò)最后不是返回配置類(lèi),而是直接通過(guò)org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition注冊(cè)Bean。最后調(diào)整一下HelloController:
@RestController@AllArgsConstructor
@RequestMapping("/hello")
@EnableCustomBean
public?class?HelloController?{
????private?final?HelloService?helloService;
????@GetMapping
????public?Map?hello(String?name)?{
????????ApplicationContext?appCtx?=?SpringUtil.getAppCtx();
????????assert?Objects.nonNull(appCtx)?:?"appCtx?為?null";
????????assert?appCtx.containsBean("springUtil");
????????assert?appCtx.getBean(HelloService.class)?==?helloService;
????????return?ImmutableMap.of("name",?name,?"helloResult",?helloService.sayHello(name));
????}
}
? ??實(shí)際運(yùn)行效果與CustomImportSelect和@EnableCustomConfig一致,請(qǐng)讀者自行驗(yàn)證。
? ??如果僅僅是這樣,你是不是認(rèn)為就沒(méi)必要有ImportBeanDefinitionRegistrar了。實(shí)際上通過(guò)該接口我們可以定義自己的容器Bean注解。實(shí)現(xiàn)很多特殊的功能:比如在三維家美家技術(shù)中使用到了SpringCloud Stream,但SCS的@org.springframework.cloud.stream.annotation.EnableBinding注解很麻煩,每次引入新的@Input和@Output都要將對(duì)應(yīng)接口添加到@EnableBinding中,三維家美家通過(guò)ImportBeanDefinitionRegistrar和自定義注解實(shí)現(xiàn)新的Input/Ouput Bean掃描注冊(cè)流程。后續(xù)會(huì)計(jì)劃將該功能提PR到SpringCloud項(xiàng)目中。
? ??通過(guò)自定義注冊(cè)和ImportBeanDefinitionRegistrar可以更靈活地自定義Bean注冊(cè)邏輯,限于篇幅原因,我們往后有機(jī)會(huì)再講。各位,今天關(guān)于@Import的基本用法是否掌握了呢,建議可以自己實(shí)踐一次以加深理解,如果對(duì)源碼有興趣,可以閱讀org.springframework.context.annotation.ConfigurationClassParser類(lèi)。
總結(jié)
以上是生活随笔為你收集整理的idea去掉无用import类_@Import注解的魅力的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 仅完成部分的readprocessmem
- 下一篇: python定义一个类描述数字时钟_py