想写Spring Boot SDK?先深入学习下@Import 注解吧

文章目录
背景
@Import 作用
作用对象
直接注入
注入 @Configuration 类
导入实现 ImportBeanDefinitionRegistrar接口的类
导入实现ImportSelector 接口的类
觉得文章不错欢迎关注公众号:小奏技术
背景
最近在写Spring Boot 相关的sdk,有这么一个需求,就是在某个Bean存在的时候才注入或者开启某个配置,最简单的例子就是我们需要项目配置了Redission才启动分布式锁相关的AOP切面配置,那么如何实现呢,最简单的方式使用使用@ConditionalOnBean 注解,但是发现@ConditionalOnBean 注解失效了,原因是@ConditionalOnBean 依赖的Bean后加载了,所以就打算使用@Import注解,在此之前就简单研究了下@Import 注解的用法

@Import 作用
将指定的类实例注入之Spring IOC 容器中

作用对象
直接注入
实现 ImportBeanDefinitionRegistrar 接口 注入
实现 ImportSelector 注入
直接注入
public class A {

    public A() {
        System.out.println("init A");
    }
}

public class B {

    public B() {
        System.out.println("init B");
    }
}

@Component
@Import({A.class,B.class})
public class TestImport {
}

这样就直接注入了,和这样写的方式一样

@Component
public class A {

    public A() {
        System.out.println("init A");
    }
}

@Component
public class B {

    public B() {
        System.out.println("init B");
    }
}

一般不会使用这种方式

注入 @Configuration 类
@Configuration
public class ConfigurationTest {

    @Bean
    public A a() {
        return new A();
    }
    
    @Bean
    public B b() {
        return new B();
    }

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ConfigurationTest.class)
public @interface EnableImport {
}

这种方式主要用于写三方sdk的时候开启某个功能,然后导入一些配置的Bean自动加载,比如EnableFeignClients注解

导入实现 ImportBeanDefinitionRegistrar接口的类
我们还是以注册BeanA.class B.class 为例

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        
        // 注册A
        BeanDefinitionBuilder beanDef_A = BeanDefinitionBuilder.rootBeanDefinition(A.class);
        registry.registerBeanDefinition("A", beanDef_A.getBeanDefinition());
        // 注册B
        BeanDefinitionBuilder beanDef_B = BeanDefinitionBuilder.rootBeanDefinition(B.class);
        registry.registerBeanDefinition("B", beanDef_B.getBeanDefinition());

    }
}

@Import(MyImportBeanDefinitionRegistrar.class)
@Configuration
public class TestImportBeanDefinitionRegistrar {
}
这样就注册了A B

或者基于注解的方式导入行

EnableTest
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableTest {
}

Application
@SpringBootApplication
@EnableTest
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

我们可以看看Spring Cloud Feign的自动装配就是用这种方式注入的

我们要开启Feign 的自动装配只需要在启动类添加@EnableFeignClients

我们看看@EnableFeignClients 源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

可以看到导入了配置类FeignClientsRegistrar.class,而FeignClientsRegistrar.class 就实现了ImportBeanDefinitionRegistrar 接口

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
  
  @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
  
  private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
        }
    }
  
  private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }
  
  
}

导入实现ImportSelector 接口的类
与上面类似

TestImportSelector.java 实现ImportSelector 接口
public class TestImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 获取 注解上 EnableTest 的Class 信息 并注册
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableTest.class.getName());
        assert annotationAttributes != null;
        Class<?>[] clazz = (Class<?>[])annotationAttributes.get("clazz");
        return Arrays.stream(clazz).map(Class::getName).toArray(String[]::new);

    }
}

注解导入

EnableTest.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(TestImportSelector.class)
public @interface EnableTest {

    Class[] clazz() default {};
}

然后在启动类添加注解

@SpringBootApplication
@EnableTest(clazz = {A.class, B.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

这样就注册了A.class B.class
————————————————
版权声明:本文为CSDN博主「weihubeats」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42651904/article/details/122118606

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值