idea去掉无用import类_@Import注解的魅力
? ? 本篇主要介紹Spring注解@Import的魅力所在:它能讓你高度自由的定義配置類裝載規(guī)則與Bean注冊邏輯。@Import是Spring體系中的一個比較重要的注解,下面讓我們一起看看它都有哪些神奇的魅力吧!
注冊Bean
?? @Import第一個功能就是注冊Bean到Spring容器中,用法非常簡單,將需要注冊到Spring的類通過@Import直接導(dǎo)入即可,這時候注冊的Bean對應(yīng)的名稱為類完全限定名。如下所示:
package?com.swj.mj.autoconfig.service;@Slf4j
public?class?HelloService?{
????public?HelloService()?{
????????log.info("HelloService?Initialization...");
????}
????public?String?sayHello(String?name)?{
????????return?"Hello?"?+?name;
????}
}
? ??首先任意定義一個類,然后我們直接在某個Bean或者配置類上通過@Import(HelloService.class)方法注冊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容器中找不到名稱為"?+?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)建一個啟動類:
package?com.swj.mj.web;@SpringBootApplication
public?class?WebApplication?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????SpringApplication.run(WebApplication.class,?args);
????}
}
? ? 添加JVM選項-ea(激活斷言功能)并啟動容器后(啟動類的所在包路徑為com.swj.mj.web,并且沒有額外配置掃描包,所以默認(rèn)掃描的包是com.swj.mj.web),可以看到如下日志打印,說明@Import可以用來注冊Bean。
@Import注冊SpringBean? ??同時,請求后可以看到兩個斷言都成立,說明通過@Import注冊的SpringBean的beanName為類對應(yīng)的類完全限定名。
@Import注冊的SpringBean對應(yīng)名稱? ??如果我們?nèi)サ袅?#64;Import(HelloService.class),那么應(yīng)用啟動后將直接失敗并終止:
去掉@Import后將直接找不到類以致于啟動失敗導(dǎo)入配置類
? ??第二個功能是直接導(dǎo)入配置類。我們在原先的基礎(chǔ)上新增一個配置類HelloServiceConfiguration:
@Configurationpublic?class?HelloServiceConfiguration?{
????@Bean
????@ConditionalOnMissingBean
????public?HelloService?helloService()?{
????????return?new?HelloService();
????}
}
? ??該配置類會在容器中沒有helloService這個Bean時自動注冊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容器中找不到名稱為"?+?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;
????}
}
? ??這里通過@Import(HelloServiceConfiguration.class)直接導(dǎo)入配置類,由配置類注冊HelloService,這時候的beanName就不是類完全限定名了,而是方法名helloService。
? ??實際上HelloServiceConfiguration同樣會被注冊到Spring容器中,通過appCtx.getBean(HelloServiceConfiguration.class)可以得到配置類。所以可以說第一個功能與第二個功能是重合的,這點在源碼上也可以得到解釋,具體可以參考o(jì)rg.springframework.context.annotation.ConfigurationClassParser#processImports。本篇不講源碼哈,只介紹如何使用。
選擇性裝載配置類
? ??第三個功能是通過實現(xiàn)org.springframework.context.annotation.ImportSelector接口完成動態(tà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)注解的類的元數(shù)據(jù)。通過該參數(shù)可以實現(xiàn)獲取一些組合注解的自定義屬性等內(nèi)容,從而實現(xiàn)選擇性裝載配置類。
「注意這個方法的返回值不能是null,極端情況請返回空數(shù)組,否則運行將拋出NPE。」
? ??讓我們先創(chuàng)建一個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;
????}
}
? ??這個類主要用于通過靜態(tài)持有Spring的ApplicationContext和BeanFactory,后續(xù)可以直接通過該類訪問Spring容器。然后創(chuàng)建我們的ImportSelector配置類裝載選擇器:
package?com.swj.mj.autoconfig.configuration;public?class?CustomImportSelector?implements?ImportSelector?{
????@Override
????public?String[]?selectImports(AnnotationMetadata?importingClassMetadata)?{
????????//?importingClassMetadata?指使用?@Import(HelloServiceImportSelector)?時?@Import?注解所在類的元數(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?{
????/**
?????*?是否注冊?{@link?com.swj.mj.autoconfig.util.SpringUtil}?工具類,默認(rèn)為?{@literal?true}
?????*/
????boolean?registerUtil()?default?true;
}
? ??這里簡單解釋一下:首先@EnableCustomConfig注解組合了@Import(CustomImportSelector.class),這樣在CustomImportSelector#selectImports方法中可以通過importingClassMetadata獲取得到@EnableCustomConfig注解的屬性配置。然后通過該配置實現(xiàn)添加裝載配置。這里我們判斷registerUtil是否為true(默認(rèn)為true),為true時將裝載SpringUtil以便后續(xù)可以通過SpringUtil訪問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));
????}
}
? ??這里通過添加@EnableCustomConfig激活聯(lián)動激活@Import(CustomImportSelector.class)以注冊SpringUtil和HelloServiceConfiguration,由于默認(rèn)registerUtil為true,所以會注冊SpringUtil。啟動應(yīng)用后能訪問http://localhost:8080/hello?name=Reka表示斷言均成立,容器中確實注冊了對應(yīng)的Bean。
? ??接著我們調(diào)整@EnableCustomConfig為@EnableCustomConfig(registerUtil = false),再次啟動容器訪問http://localhost:8080/hello?name=Reka,這時候會發(fā)現(xiàn)斷言失敗:
通過注解屬性配置選擇性裝載配置類動態(tài)注冊Bean
? ??這是@Import的第四個功能,通過org.springframework.context.annotation.ImportBeanDefinitionRegistrar自由注冊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ù)簡單理解是Bean注冊器,通過它可以往Spring容器中注冊Bean。現(xiàn)在讓我們通過動態(tài)注冊Bean的方式實現(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?{
????/**
?????*?是否注冊?{@link?com.swj.mj.autoconfig.util.SpringUtil}?工具類,默認(rèn)為?{@literal?true}
?????*/
????boolean?registerUtil()?default?true;
}
? ? CustomImportBeanDefinitionRegistrar的邏輯與CustomImportSelector基本一致,只不過最后不是返回配置類,而是直接通過org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition注冊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));
????}
}
? ??實際運行效果與CustomImportSelect和@EnableCustomConfig一致,請讀者自行驗證。
? ??如果僅僅是這樣,你是不是認(rèn)為就沒必要有ImportBeanDefinitionRegistrar了。實際上通過該接口我們可以定義自己的容器Bean注解。實現(xiàn)很多特殊的功能:比如在三維家美家技術(shù)中使用到了SpringCloud Stream,但SCS的@org.springframework.cloud.stream.annotation.EnableBinding注解很麻煩,每次引入新的@Input和@Output都要將對應(yīng)接口添加到@EnableBinding中,三維家美家通過ImportBeanDefinitionRegistrar和自定義注解實現(xiàn)新的Input/Ouput Bean掃描注冊流程。后續(xù)會計劃將該功能提PR到SpringCloud項目中。
? ??通過自定義注冊和ImportBeanDefinitionRegistrar可以更靈活地自定義Bean注冊邏輯,限于篇幅原因,我們往后有機會再講。各位,今天關(guān)于@Import的基本用法是否掌握了呢,建議可以自己實踐一次以加深理解,如果對源碼有興趣,可以閱讀org.springframework.context.annotation.ConfigurationClassParser類。
總結(jié)
以上是生活随笔為你收集整理的idea去掉无用import类_@Import注解的魅力的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 仅完成部分的readprocessmem
- 下一篇: python定义一个类描述数字时钟_py