idea去掉无用import类_@Import注解的魅力
本篇主要介绍Spring
注解@Import
的魅力所在:它能让你高度自由的定义配置类装载规则与Bean注册逻辑。@Import是Spring
体系中的一个比较重要的注解,下面让我们一起看看它都有哪些神奇的魅力吧!
注册Bean
@Import
第一个功能就是注册Bean
到Spring
容器中,用法非常简单,将需要注册到Spring
的类通过@Import
直接导入即可,这时候注册的Bean
对应的名称为类完全限定名。如下所示:
package com.swj.mj.autoconfig.service;
@Slf4jpublic 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; }
}
创建一个启动类:
package com.swj.mj.web;
@SpringBootApplicationpublic class WebApplication {
public static void main(String[] args) throws Exception { SpringApplication.run(WebApplication.class, args); }
}
添加JVM
选项-ea
(激活断言功能)并启动容器后(启动类的所在包路径为com.swj.mj.web
,并且没有额外配置扫描包,所以默认扫描的包是com.swj.mj.web
),可以看到如下日志打印,说明@Import
可以用来注册Bean
。
同时,请求后可以看到两个断言都成立,说明通过@Import
注册的SpringBean
的beanName
为类对应的类完全限定名。
如果我们去掉了@Import(HelloService.class)
,那么应用启动后将直接失败并终止:
导入配置类
第二个功能是直接导入配置类。我们在原先的基础上新增一个配置类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)
直接导入配置类,由配置类注册HelloService
,这时候的beanName
就不是类完全限定名了,而是方法名helloService
。
实际上HelloServiceConfiguration
同样会被注册到Spring
容器中,通过appCtx.getBean(HelloServiceConfiguration.class)
可以得到配置类。所以可以说第一个功能与第二个功能是重合的,这点在源码上也可以得到解释,具体可以参考org.springframework.context.annotation.ConfigurationClassParser#processImports
。本篇不讲源码哈,只介绍如何使用。
选择性装载配置类
第三个功能是通过实现org.springframework.context.annotation.ImportSelector
接口完成动态开启配置类。
首先看一下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;
}
}
主要关注String[] selectImports(AnnotationMetadata importingClassMetadata)
方法,参数importingClassMetadata
表示使用了@Import(? extends ImportSelector)
注解的类的元数据。通过该参数可以实现获取一些组合注解的自定义属性等内容,从而实现选择性装载配置类。
「注意这个方法的返回值不能是null
,极端情况请返回空数组,否则运行将抛出NPE
。」
让我们先创建一个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; }
}
这个类主要用于通过静态持有Spring
的ApplicationContext
和BeanFactory
,后续可以直接通过该类访问Spring
容器。然后创建我们的ImportSelector
配置类装载选择器:
package com.swj.mj.autoconfig.configuration;
public class CustomImportSelector implements ImportSelector {
@Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // importingClassMetadata 指使用 @Import(HelloServiceImportSelector) 时 @Import 注解所在类的元数据 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() }; }}
然后创建我们的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} 工具类,默认为 {@literal true} */ boolean registerUtil() default true;
}
这里简单解释一下:首先@EnableCustomConfig
注解组合了@Import(CustomImportSelector.class)
,这样在CustomImportSelector#selectImports
方法中可以通过importingClassMetadata
获取得到@EnableCustomConfig
注解的属性配置。然后通过该配置实现添加装载配置。这里我们判断registerUtil
是否为true
(默认为true
),为true
时将装载SpringUtil
以便后续可以通过SpringUtil
访问Spring
容器。
接着调整HelloController
:
@RestController@AllArgsConstructor@RequestMapping("/hello")@EnableCustomConfigpublic 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
激活联动激活@Import(CustomImportSelector.class)
以注册SpringUtil
和HelloServiceConfiguration
,由于默认registerUtil
为true
,所以会注册SpringUtil
。启动应用后能访问http://localhost:8080/hello?name=Reka
表示断言均成立,容器中确实注册了对应的Bean
。
接着我们调整@EnableCustomConfig
为@EnableCustomConfig(registerUtil = false)
,再次启动容器访问http://localhost:8080/hello?name=Reka
,这时候会发现断言失败:
动态注册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)
,其中参数importingClassMetadata
与ImportSelector
里的参数效用一致,registry
参数简单理解是Bean
注册器,通过它可以往Spring
容器中注册Bean
。现在让我们通过动态注册Bean
的方式实现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} 工具类,默认为 {@literal true} */ boolean registerUtil() default true;
}
CustomImportBeanDefinitionRegistrar
的逻辑与CustomImportSelector
基本一致,只不过最后不是返回配置类,而是直接通过org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition
注册Bean
。最后调整一下HelloController
:
@RestController@AllArgsConstructor@RequestMapping("/hello")@EnableCustomBeanpublic 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
一致,请读者自行验证。
如果仅仅是这样,你是不是认为就没必要有ImportBeanDefinitionRegistrar
了。实际上通过该接口我们可以定义自己的容器Bean
注解。实现很多特殊的功能:比如在三维家美家技术中使用到了SpringCloud Stream
,但SCS
的@org.springframework.cloud.stream.annotation.EnableBinding
注解很麻烦,每次引入新的@Input
和@Output
都要将对应接口添加到@EnableBinding
中,三维家美家通过ImportBeanDefinitionRegistrar
和自定义注解实现新的Input/Ouput Bean
扫描注册流程。后续会计划将该功能提PR
到SpringCloud
项目中。
通过自定义注册和ImportBeanDefinitionRegistrar
可以更灵活地自定义Bean
注册逻辑,限于篇幅原因,我们往后有机会再讲。各位,今天关于@Import
的基本用法是否掌握了呢,建议可以自己实践一次以加深理解,如果对源码有兴趣,可以阅读org.springframework.context.annotation.ConfigurationClassParser
类。
idea去掉无用import类_@Import注解的魅力相关推荐
- serve注解是加在哪个类_@Service注解是放实现类上的而非接口上
今天用springboot写多模块项目,写完第一个Controller类,注入的Service接口是ok的,然后写第二个Controller类就一直报错.这把我给搞懵逼了,为什么我第一个Control ...
- [SpringBoot2]容器功能_底层注解配置绑定_@Configuration@Import@Conditional@ImportResource
@Configuration&@Bean 告诉SpringBoot这是一个配置类==配置文件 #############################Configuration使用示例### ...
- python中from import*的*什么意思_[Python]Python中的import和from import
在python用import或者from...import来导入相应的模块. 模块其实就是一些函数和类的集合文件,它能实现一些相应的功能,当我们需要使用这些功能的时候,直接把相应的模块导入到我们的程序 ...
- python import 类 继承_python学习之类的继承
面向对象中一个重要的特性就是继承,继承的好处就是提高代码的重用率,减少不必要的代码.继承是父类与子类的关系,当子类继承了父类后,就具有了父类的所有变量和方法.在python中定义继承的语法是:clas ...
- python from. import失败_彻底搞懂Python 中的 import 与 from import
以下文章来源&作者:青南(谢乾坤) 摄影:产品经理:kingname 的第一套乐高 你好,我是谢乾坤,前网易高级数据挖掘工程师.现任微软最有价值专家(Python 方向),有6年 Python ...
- python中import星_【已解决】Python中递归import导入:ImportError: cannot import name
折腾: 期间,从错误信息中可以看到:Traceback (most recent call last): File "/usr/lib64/python3.4/runpy.py", ...
- serve注解是加在哪个类_不会 Java 注解 ? 看这一篇文章!
对于Java注解,我之前的印象是很模糊的,总觉得这个东西经常听说,也经常用,但是具体是怎么回事,好像没有仔细学习过,说到注解,立马想到@Controller,仅此而已. 对于Java注解,我咨询过一些 ...
- java代码中出现黑色画线_从对象流中读取对象,请在画线处加入代码完成此程序【 】。 import java.util.*; import ja...
从对象流中读取对象,请在画线处加入代码完成此程序[ ]. import java.util.*; import ja 更多相关问题 [问答题,简答题] "三好一满意"是什么? [单 ...
- java data使用_@Data注解详解及使用方法-Fun言
@Data作用 去除Getter,Setter,equals,hashCode,toString方法,@Data注解在类上时,简化java代码编写,为该类提供读写属性.简单来说就是不用再手动添加get ...
最新文章
- 《OpenCV3编程入门》学习笔记6 图像处理(四)形态学滤波(2):开运算、闭运算、形态学梯度、顶帽、黑帽
- c#中volatile关键字的作用
- Azure进阶攻略丨如何驾驭罢工的Linux虚机网卡?
- 300英雄服务器维护多久,《300英雄》2021年5月20日6:00-9:00更新维护公告
- android 7.1 apk的systemuid相同导致问题[2]
- 【转载】vim常用命令总结
- Java计算两个经纬度间的距离
- OpenCV-Python实战(3)——OpenCV中绘制图形与文本
- 谁分配谁释放HEAP
- java面向对象是什么意思_java什么是面向对象
- VS2010 中文版本
- 租赁风控模型之决策树
- 按F12才能进入开机界面(戴尔游匣7559)
- 【图文】如何在学信网申请在线验证报告?包括-教育部学籍在线验证报告-教育部学历证书电子注册备案表 in 中国高等教育学生信息网
- 硬盘突然变raw格式_磁盘变成RAW格式的两种解决办法
- 基于Python的BP网络实现手写数字识别
- Codeforces Round #834 (Div. 3) E. The Humanoid
- Linux程序设计 读笔2 Shell脚本
- 安路FPSoC®产品SF1系列全新发布,高集成低功耗,助力实现多种应用场景
- 基于javaweb+JSP+Servlet简单购物车