本篇主要介绍Spring注解@Import的魅力所在:它能让你高度自由的定义配置类装载规则与Bean注册逻辑。@Import是Spring体系中的一个比较重要的注解,下面让我们一起看看它都有哪些神奇的魅力吧!

注册Bean

@Import第一个功能就是注册BeanSpring容器中,用法非常简单,将需要注册到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

同时,请求后可以看到两个断言都成立,说明通过@Import注册的SpringBeanbeanName为类对应的类完全限定名。

@Import注册的SpringBean对应名称

如果我们去掉了@Import(HelloService.class),那么应用启动后将直接失败并终止:

去掉@Import后将直接找不到类以致于启动失败

导入配置类

第二个功能是直接导入配置类。我们在原先的基础上新增一个配置类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;    }

}

这个类主要用于通过静态持有SpringApplicationContextBeanFactory,后续可以直接通过该类访问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)以注册SpringUtilHelloServiceConfiguration,由于默认registerUtiltrue,所以会注册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),其中参数importingClassMetadataImportSelector里的参数效用一致,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扫描注册流程。后续会计划将该功能提PRSpringCloud项目中。

通过自定义注册和ImportBeanDefinitionRegistrar可以更灵活地自定义Bean注册逻辑,限于篇幅原因,我们往后有机会再讲。各位,今天关于@Import的基本用法是否掌握了呢,建议可以自己实践一次以加深理解,如果对源码有兴趣,可以阅读org.springframework.context.annotation.ConfigurationClassParser类。

idea去掉无用import类_@Import注解的魅力相关推荐

  1. serve注解是加在哪个类_@Service注解是放实现类上的而非接口上

    今天用springboot写多模块项目,写完第一个Controller类,注入的Service接口是ok的,然后写第二个Controller类就一直报错.这把我给搞懵逼了,为什么我第一个Control ...

  2. [SpringBoot2]容器功能_底层注解配置绑定_@Configuration@Import@Conditional@ImportResource

    @Configuration&@Bean 告诉SpringBoot这是一个配置类==配置文件 #############################Configuration使用示例### ...

  3. python中from import*的*什么意思_[Python]Python中的import和from import

    在python用import或者from...import来导入相应的模块. 模块其实就是一些函数和类的集合文件,它能实现一些相应的功能,当我们需要使用这些功能的时候,直接把相应的模块导入到我们的程序 ...

  4. python import 类 继承_python学习之类的继承

    面向对象中一个重要的特性就是继承,继承的好处就是提高代码的重用率,减少不必要的代码.继承是父类与子类的关系,当子类继承了父类后,就具有了父类的所有变量和方法.在python中定义继承的语法是:clas ...

  5. python from. import失败_彻底搞懂Python 中的 import 与 from import

    以下文章来源&作者:青南(谢乾坤) 摄影:产品经理:kingname 的第一套乐高 你好,我是谢乾坤,前网易高级数据挖掘工程师.现任微软最有价值专家(Python 方向),有6年 Python ...

  6. python中import星_【已解决】Python中递归import导入:ImportError: cannot import name

    折腾: 期间,从错误信息中可以看到:Traceback (most recent call last): File "/usr/lib64/python3.4/runpy.py", ...

  7. serve注解是加在哪个类_不会 Java 注解 ? 看这一篇文章!

    对于Java注解,我之前的印象是很模糊的,总觉得这个东西经常听说,也经常用,但是具体是怎么回事,好像没有仔细学习过,说到注解,立马想到@Controller,仅此而已. 对于Java注解,我咨询过一些 ...

  8. java代码中出现黑色画线_从对象流中读取对象,请在画线处加入代码完成此程序【 】。 import java.util.*; import ja...

    从对象流中读取对象,请在画线处加入代码完成此程序[ ]. import java.util.*; import ja 更多相关问题 [问答题,简答题] "三好一满意"是什么? [单 ...

  9. java data使用_@Data注解详解及使用方法-Fun言

    @Data作用 去除Getter,Setter,equals,hashCode,toString方法,@Data注解在类上时,简化java代码编写,为该类提供读写属性.简单来说就是不用再手动添加get ...

最新文章

  1. 《OpenCV3编程入门》学习笔记6 图像处理(四)形态学滤波(2):开运算、闭运算、形态学梯度、顶帽、黑帽
  2. c#中volatile关键字的作用
  3. Azure进阶攻略丨如何驾驭罢工的Linux虚机网卡?
  4. 300英雄服务器维护多久,《300英雄》2021年5月20日6:00-9:00更新维护公告
  5. android 7.1 apk的systemuid相同导致问题[2]
  6. 【转载】vim常用命令总结
  7. Java计算两个经纬度间的距离
  8. OpenCV-Python实战(3)——OpenCV中绘制图形与文本
  9. 谁分配谁释放HEAP
  10. java面向对象是什么意思_java什么是面向对象
  11. VS2010 中文版本
  12. 租赁风控模型之决策树
  13. 按F12才能进入开机界面(戴尔游匣7559)
  14. 【图文】如何在学信网申请在线验证报告?包括-教育部学籍在线验证报告-教育部学历证书电子注册备案表 in 中国高等教育学生信息网
  15. 硬盘突然变raw格式_磁盘变成RAW格式的两种解决办法
  16. 基于Python的BP网络实现手写数字识别
  17. Codeforces Round #834 (Div. 3) E. The Humanoid
  18. Linux程序设计 读笔2 Shell脚本
  19. 安路FPSoC®产品SF1系列全新发布,高集成低功耗,助力实现多种应用场景
  20. 基于javaweb+JSP+Servlet简单购物车

热门文章

  1. LeetCode_数组_中等题
  2. 玩转可视化--来聊聊地图投影的学问
  3. 软件工程实践2017第一次作业-051502125
  4. 微软在Skype推出LGBT骄傲月表情与贴纸
  5. Swift の 函数式编程
  6. PHP验证码无法显示的原因
  7. jQuery用面向对象的思想来编写验证表单的插件
  8. 学习资源之4:Linux
  9. 【博客话题】我的linux心路历程
  10. 被惊艳到了!适用 Win、Mac、Linux,酷炫的极客界面 eDEX-UI