Spring Boot中@Import注解的三种用法!

Spring Boot中@Import注解的三种用法!

需要注意的是:ImportSelector、ImportBeanDefinitionRegistrar这两个接口都必须依赖于@Import一起使用,而@Import可以单独使用。

@Import是一个非常有用的注解,它的长处在于你可以通过配置来控制是否注入该Bean,也可以通过条件来控制注入哪些Bean到 Spring容器中。

比如我们熟悉的:@EnableAsync@EnableCaching@EnableScheduling等等统一采用的都是借助@Import注解来实现的。

下面我们就通过示例来了解@Import三种用法!

一、引入普通类

有个用户类如下

@Data
public class UserConfig {  
    /** 用户名*/
    private String username;

    /**手机号*/
    private String phone;
}

那么如何通过@Import注入容器呢?

@Import(UserConfig.class)
@Configuration
public class UserConfiguration 

}

当在@Configuration标注的类上使用@Import引入了一个类后,就会把该类注入容器中。

当然除了@Configuration 比如@Component、@Service等一样也可以。

测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest 
{

    @Autowired
    private UserConfig userConfig;
    
    @Test
    public void getUser() {
        String name = userConfig.getClass().getName();
        System.out.println("name = " + name);
    }
}

控制台输出

name = com.jincou.importselector.model.UserConfig

如果@Import的功能仅仅是这样,那其实它并没什么特别的价值,我们可以通过其它方式实现?

@Configuration
public class UserConfiguration {

    @Bean
    public UserConfig userConfig() {
        return new UserConfig();
    }   
}

再比如直接添加@Configuration注解

@Configuration
public class UserConfig {
  // ......
}

确实如果注入静态的Bean到容器中,那完全可以用上面的方式代替,但如果需要动态的带有逻辑性的注入Bean,那才更能体现@Import的价值。


二、引入ImportSelector的实现类

说到ImportSelector这个接口就不得不说这里面最重要的一个方法:selectImports()

public interface ImportSelector {

 String[] selectImports(AnnotationMetadata importingClassMetadata);
}

这个方法的返回值是一个字符串数组,只要在配置类被引用了,这里返回的字符串数组中的类名就会被Spring容器new出来,然后再把这些对象注入IOC容器中。

所以这有啥用呢?我们还是用一个例子演示一下。

1、静态import场景(注入已知的类)

我们先将上面的示例改造下:

自定义MyImportSelector实现ImportSelector接口,重写selectImports方法

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //这里目的是将UserConfig 注入容器中
        return new String[]{"com.jincou.importselector.model.UserConfig"};
    }
}

然后在配置类引用

@Import(MyImportSelector.class)
@Configuration
public class UserConfiguration 
{

}

这样一来同样可以通过成功将UserConfig注入容器中。

如果看到这,你肯定会有疑问。我这又是新建MyImportSelector类,又是实现ImportSelector重写selectImports方法,然后我这么做有个卵用呢?

直接把类上加个@Component注入进去不香吗?这个ImportSelector把简单的功能搞这么复杂。

接下来就要说说如何动态注入Bean了。

2、动态import场景(注入指定条件的类)

我们来思考一种场景,就是你想通过开关来控制是否注入该Bean,或者说通过配置来控制注入哪些Bean,这个时候就有了ImportSelector的用武之地了。

我们来举个例子,通过ImportSelector的使用实现条件选择是注入本地缓存还是 Redis缓存

1)、定义缓存接口和实现类

顶层接口

public interface CacheService {
    
    void setData(String key);
}

本地缓存 实现类

public class LocalServicempl implements CacheService {
    
    @Override
    public void setData(String key) {
        System.out.println("本地存储存储数据成功 key= " + key); 
    }
}

redis缓存实现类

public class RedisServicempl implements CacheService {

    @Override
    public void setData(String key) {
        System.out.println("redis存储数据成功 key= " + key); 
    }
}

2)、定义ImportSelector实现类

以下代码中根据EnableMyCache注解中的不同值来切换缓存的实现类再spring中的注册。

public class MyCacheSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableMyCache.class.getName());
        //通过 不同type注入不同的缓存到容器中
        CacheType type = (CacheType) annotationAttributes.get("type");
        switch (type) {
            case LOCAL: {
                return new String[]{LocalServicempl.class.getName()};
            }
            case REDIS: {
                return new String[]{RedisServicempl.class.getName()};
            }
            default: {
                throw new RuntimeException(MessageFormat.format("unsupport cache type {0}", type.toString()));
            }
        }
    }
}

3)、定义注解

@EnableMyCache注解就像一个开关,通过这个开关来是否将特定的Bean注入容器。

定义一个枚举

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyCacheSelector.class)
public @interface EnableMyCache 
{
    CacheType type() default CacheType.REDIS;
}
public enum CacheType {
    LOCAL, REDIS;
}

4)、测试

这里选择本地缓存。

@EnableMyCache(type = CacheType.LOCAL)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest 
{

    @Autowired
    private CacheService cacheService;

    @Test
    public void test() {
        cacheService.setData("key");
    }
}

控制台输出

本地存储存储数据成功 key= key

切换成redis缓存

@EnableMyCache(type = CacheType.REDIS)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest 
{

    @Autowired
    private CacheService cacheService;

    @Test
    public void test() {
        cacheService.setData("key");
    }
}

控制台输出

redis存储数据成功 key= key

这个示例不是就是Bean的动态注入了吗?

3、Spring如何使用ImportSelector的场景

SpringBoot有两个常用注解 @EnableAsync @EnableCaching 其实就是通过ImportSelector来动态注入Bean

看下@EnableAsync注解,它有通过@Import({AsyncConfigurationSelector.class})

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync 
{
    Class<? extends Annotation> annotation() default Annotation.class;

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default 2147483647;
}

「AsyncConfigurationSelector.class」

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync{
    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

    public AsyncConfigurationSelector() {
    }

    @Nullable
    public String[] selectImports(AdviceMode adviceMode) {
        switch(adviceMode) {
        case PROXY:
            return new String[]{ProxyAsyncConfiguration.class.getName()};
        case ASPECTJ:
            return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
        default:
            return null;
        }
    }
}

是不是和我上面写的示例一样。

总之,向这种还不能决定去注入哪个处理器(如果你能决定,那就直接@Import那个类好了,没必要实现接口了),就可以实现此接口,写出一些判断逻辑,不同的配置情况注入不同的处理类。


三、引入ImportBeanDefinitionRegister的实现类

当配置类实现了 ImportBeanDefinitionRegistrar 接口,你就可以自定义往容器中注册想注入的Bean。

这个接口相比与 ImportSelector 接口的主要区别就是,ImportSelector接口是返回一个类,你不能对这个类进行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加属性之类的。

public class MyImportBean implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               注册类,其registerBeanDefinition()可以注册bean
     */

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    }
}

1、举一个简单的示例

我们通过先通过一个简单的小示例,来理解它的基本使用

假设有个用户配置类如下

@Data
public class UserConfig {
    /** 用户名*/
    private String username;
    /**手机号*/
    private String phone;
}

我们通过实现ImportBeanDefinitionRegistrar的方式来完成注入。

public class MyImportBean implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               注册类,其registerBeanDefinition()可以注册bean
     */

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //构建一个 BeanDefinition , Bean的类型为 UserConfig,这个Bean的属性username的值为后端元宇宙
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserConfig.class)
                .addPropertyValue("username", "后端元宇宙")
                .getBeanDefinition()
;
        //把 UserConfig 这个Bean的定义注册到容器中
        registry.registerBeanDefinition("userConfig", beanDefinition);
    }
}

通过配置类 中引入MyImportBean对象。

@Import(MyImportBean.class)
@Configuration
public class UserImportConfiguration 
{

}

我们再来测试下

@EnableMyCache(type = CacheType.REDIS)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest 
{

    @Autowired
    private UserConfig userConfig;

    @Test
    public void test() {
        String username = userConfig.getUsername();
        System.out.println("username = " + username);
    }
}

控制台输出

username = 后端元宇宙

说明通过ImportBeanDefinitionRegistrar方式,已经把UserConfig注入容器成功,而且还为给bean设置了新属性。

然后我们再来思考一个问题,就比如我们在其它地方已经将UserConfig注入容器,这里会不会出现冲突,或者不冲突的情况下,属性能不能设置成功?

我们来试下

@Import(MyImportBean.class)
@Configuration
public class UserImportConfiguration 
{

    /**
     * 这里通过@Bean注解,将UserConfig注入Spring容器中,而且名称也叫userConfig
     */

    @Bean
    public UserConfig userConfig() {
        return new UserConfig();
    }
}

然后我们再来跑下上面的测试用例,发现 报错了。

Spring Boot中@Import注解的三种用法!

2、举一个复杂点的例子

Mybatis的@MapperScan就是用这种方式实现的,@MapperScan注解,指定basePackages,扫描Mybatis Mapper接口类注入到容器中。

这里我们自定义一个注解@MyMapperScan来扫描包路径下所以带@MapperBean注解的类,并将它们注入到IOC容器中。

1)、先定义一个@MapperBean注解,就相当于我们的@Mapper注解

/**
 * 定义包路径。(指定包下所有添加了MapperBean注解的类作为bean)
 * 注意这里 @Import(MyMapperScanImportBean.class) 的使用
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MapperBean {
}

2)、一个需要注入的bean,这里加上@MapperBean注解。

package com.jincou.importselector.mapperScan;
import com.jincou.importselector.config.MapperBean;

@MapperBean
public class User {
}

3)、再定一个扫描包路径的注解@MyMapperScan 就相当于mybatis的@MapperScan注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyMapperScanImportBean.class)
public @interface MyMapperScan 
{

   /**
    * 扫描包路径
    */

    String[] basePackages() default {};
}

4)、MyMapperScanImportBean实现ImportBeanDefinitionRegistrar接口

public class MyMapperScanImportBean implements ImportBeanDefinitionRegistrarResourceLoaderAware {

    private final static String PACKAGE_NAME_KEY = "basePackages";
    private ResourceLoader resourceLoader;
    
    /**
     * 搜索指定包下所有添加了MapperBean注解的类,并且把这些类添加到ioc容器里面去
     * 
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               注册类,其registerBeanDefinition()可以注册bean
     */

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1. 从BeanIocScan注解获取到我们要搜索的包路径
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
        if (annoAttrs == null || annoAttrs.isEmpty()) {
            return;
        }
        String[] basePackages = (String[]) annoAttrs.get(PACKAGE_NAME_KEY);
        // 2. 找到指定包路径下所有添加了MapperBean注解的类,并且把这些类添加到IOC容器里面去
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        //路径包含MapperBean的注解的bean
        scanner.addIncludeFilter(new AnnotationTypeFilter(MapperBean.class));
        //扫描包下路径
        scanner.scan(basePackages);
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

5)测试

这里扫描的路径就是上面User实体的位置

@RunWith(SpringRunner.class)
@SpringBootTest
@MyMapperScan(basePackages 
= {"com.jincou.importselector.mapperScan"})
public class UserServiceTest {

    @Autowired
    private User user;

    @Test
    public void test() {
        System.out.println("username = " + user.getClass().getName());
    }
}

运行结果

username = com.jincou.importselector.mapperScan.User

完美,成功!

实现它的基本思想是:当自己需要操作BeanFactory里面的Bean的时候,那就必须只有它才能做到了。而且它还有个方便的地方,那就是做包扫描的时候,比如@MapperScan类似这种的时候,用它处理更为方便(因为扫描到了直接注册即可)

求一键三连:点赞、转发、在看。

原文始发于微信公众号(后端元宇宙): Spring Boot中@Import注解的三种用法!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/115283.html

(0)
小半的头像小半
0 0

相关推荐

  • Redis常见面试题(第一季) 后端开发

    Redis常见面试题(第一季)

    0 0111
    小半的头像 小半
    2022年10月5日
  • 【88期】面试官问:你能说说 Spring 中,接口的bean是如何注入的吗? 面试题

    【88期】面试官问:你能说说 Spring 中,接口的bean是如何注入的吗?

    0 0193
    小半的头像 小半
    2022年5月17日
  • Nacos 配置中心实战 微信精选

    Nacos 配置中心实战

    0 0183
    小半的头像 小半
    2022年11月28日
  • 搭建SpringBoot和Mysql Demo 硬件开发

    搭建SpringBoot和Mysql Demo

    0 0211
    小半的头像 小半
    2023年7月2日
  • 还傻傻的分不清Redis Stack和Redis关系吗?全新体验:全文搜索、向量数据库、文档数据库,真的很香。 技术漫谈

    还傻傻的分不清Redis Stack和Redis关系吗?全新体验:全文搜索、向量数据库、文档数据库,真的很香。

    0 0186
    小半的头像 小半
    2024年3月14日
  • Python教程:如何破解密码解压ZIP、7z和读取PDF文件 Python

    Python教程:如何破解密码解压ZIP、7z和读取PDF文件

    0 0205
    飞熊的头像 飞熊
    2024年4月7日
  • 深入研究下Spring Boot Actuator 在kubernetes中探针的应用 后端开发

    深入研究下Spring Boot Actuator 在kubernetes中探针的应用

    0 0427
    小半的头像 小半
    2022年9月23日
  • Kafka系列二消息队列的选择 后端开发

    Kafka系列二消息队列的选择

    0 0186
    小半的头像 小半
    2022年9月20日
  • 放弃 console.log 吧!用 Debugger 你能读懂各种源码 前端开发

    放弃 console.log 吧!用 Debugger 你能读懂各种源码

    0 0151
    神光的编程秘籍的头像 神光的编程秘籍
    2023年2月17日
  • 如何从0开发一个Vue组件库并发布到npm 前端开发

    如何从0开发一个Vue组件库并发布到npm

    0 0254
    小半的头像 小半
    2024年3月8日
  • JWT认证就是这么简单 微信精选

    JWT认证就是这么简单

    0 0347
    小半的头像 小半
    2023年7月21日
  • 代码级异常体系建设演进之路 架构设计

    代码级异常体系建设演进之路

    0 0259
    小半的头像 小半
    2024年3月17日

发表回复

登录后才能评论

扫我!扫我!扫码!

Spring Boot中@Import注解的三种用法!

站长精选

  • 谈一谈大厂都怎么防止重复下单?

    谈一谈大厂都怎么防止重复下单?

    2023年4月27日

  • 一个 SpringBoot 配置顺序问题,让我直接回滚了代码。。。

    一个 SpringBoot 配置顺序问题,让我直接回滚了代码。。。

    2023年6月18日

  • Dubbo-聊聊通信模块设计

    Dubbo-聊聊通信模块设计

    2022年11月21日

  • 本地缓存之王——Caffeine 组件最强讲解!

    本地缓存之王——Caffeine 组件最强讲解!

    2022年12月1日

  • 腾讯一面:如何正确停止一个线程?

    腾讯一面:如何正确停止一个线程?

    2023年2月14日

  • 简单、好用,无学习成本:一个轻量级的 SpringBoot 项目性能分析工具开源了!

    简单、好用,无学习成本:一个轻量级的 SpringBoot 项目性能分析工具开源了!

    2024年2月1日

  • 高颜值的一站式开源的数据中台

    高颜值的一站式开源的数据中台

    2024年1月3日

  • 大厂是如何防止订单重复支付的?三分钟彻底搞懂!

    大厂是如何防止订单重复支付的?三分钟彻底搞懂!

    2023年12月13日

  • 微软出品,鲜为人知却无比实用的逆天小工具,来了解一下?

    微软出品,鲜为人知却无比实用的逆天小工具,来了解一下?

    2023年4月6日

  • 分页慢查询导致的事故,太坑了!

    分页慢查询导致的事故,太坑了!

    2023年7月12日

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!

玻璃钢生产厂家福建玻璃钢花盆研究徐州玻璃钢古代人物雕塑设计武汉多彩玻璃钢雕塑哪家便宜河北哪有玻璃钢雕塑肇庆玻璃钢卡通雕塑尺寸深圳欧式人物玻璃钢雕塑晋城附近玻璃钢仿铜雕塑乐山玻璃钢牛雕塑云南环保玻璃钢雕塑上海进口玻璃钢雕塑厂家报价呼和浩特动物玻璃钢雕塑价格玻璃钢剪影人物雕塑商场美陈最新信息成品玻璃钢蘑菇造型雕塑商场美陈蝴蝶绿叶的寓意组合式玻璃钢花盆定制余杭区玻璃钢金属雕塑私人定做黄山环保玻璃钢雕塑丽江玻璃钢雕塑生产商济南玻璃钢小猪佩奇雕塑六安玻璃钢雕塑供应商徐州设计玻璃钢雕塑江苏欧式玻璃钢雕塑订做价格北京仿铜玻璃钢雕塑供应商河北玻璃钢骆驼雕塑工艺玻璃钢雕塑图片乐清市玻璃钢雕塑订做玻璃钢花盆叠一起怎么打开生肖玻璃钢雕塑厂家电话辽宁广场玻璃钢雕塑制作香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化