Spring全解系列 - @Import注解(上)

简介: Spring全解系列 - @Import注解(上)
本文基于 Spring 5.2.x


@Import注解


@ImportSpring基于 Java 注解配置的主要组成部分。@Import注解提供了@Bean注解的功能,同时还有原来Spring基于 xml 配置文件里的<import>标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration的类。

下面将分别说明@Import注解的功能。


1. 引入其他的@Configuration


假设有如下接口和两个实现类:

package com.test
interface ServiceInterface {
    void test();
}
class ServiceA implements ServiceInterface {
    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}
class ServiceB implements ServiceInterface {
    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}

两个@Configuration,其中ConfigA``@Import``ConfigB:

package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceB() {
        return new ServiceB();
    }
}

通过ConfigA创建AnnotationConfigApplicationContext,获取ServiceInterface,看是哪种实现:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
    ServiceInterface bean = ctx.getBean(ServiceInterface.class);
    bean.test();
}

输出为:ServiceB.证明@Import的优先于本身的的类定义加载。


2. 直接初始化其他类的Bean


Spring 4.2之后,@Import可以直接指定实体类,加载这个类定义到context中。 例如把上面代码中的ConfigA@Import修改为@Import(ServiceB.class),就会生成ServiceBBean到容器上下文中,之后运行main方法,输出为:ServiceB.证明@Import的优先于本身的的类定义加载.


3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载


指定实现ImportSelector的类,通过AnnotationMetadata里面的属性,动态加载类。AnnotationMetadataImport注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。

需要实现selectImports方法,返回要加载的@Configuation或者具体Bean类的全限定名的String数组。

package com.test;
class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
        return new String[]{"com.test.ConfigB"};
    }
}
@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}


再次运行main方法,输出:ServiceB.证明@Import的优先于本身的的类定义加载。 一般的,框架中如果基于AnnotationMetadata的参数实现动态加载类,一般会写一个额外的Enable注解,配合使用。例如:


package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
    String name();
}
class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //这里的importingClassMetadata针对的是使用@EnableService的非注解类
        //因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
        Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}


之后,在ConfigA中增加注解@EnableService(name = "B")

package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

再次运行main方法,输出:ServiceB.

还可以实现DeferredImportSelector接口,这样selectImports返回的类就都是最后加载的,而不是像@Import注解那样,先加载。 例如:

package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}


修改EnableService注解:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}

这样ConfigA就优先于DefferredServiceImportSelector返回的ConfigB加载,执行main方法,输出:ServiceA


4. 指定实现ImportBeanDefinitionRegistrar的类,用于个性化加载


ImportSelector用法与用途类似,但是如果我们想重定义Bean,例如动态注入属性,改变Bean的类型和Scope等等,就需要通过指定实现ImportBeanDefinitionRegistrar的类实现。例如:

定义ServiceC

package com.test;
class ServiceC implements ServiceInterface {
    private final String name;
    ServiceC(String name) {
        this.name = name;
    }
    @Override
    public void test() {
        System.out.println(name);
    }
}

定义ServiceImportBeanDefinitionRegistrar动态注册ServiceC,修改EnableService

package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
    String name();
}
class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
                //增加构造参数
                .addConstructorArgValue(name);
        //注册Bean
        registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
    }
}


并且根据后面的源代码解析可以知道,ImportBeanDefinitionRegistrar@Bean 注解之后加载,所以要修改ConfigA去掉其中被@ConditionalOnMissingBean注解的Bean,否则一定会生成ConfigAServiceInterface


package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}

之后运行main,输出:TestServiceC

相关文章
|
17天前
|
缓存 Java Sentinel
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
|
6天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
2天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
4天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
17 2
|
6天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
16 2
|
12天前
|
Java Spring
springboot自带的@Scheduled注解开启定时任务
springboot自带的@Scheduled注解开启定时任务
|
15天前
|
XML JSON Java
【SpringBoot】springboot常用注解
【SpringBoot】springboot常用注解
|
16天前
|
Java 数据库 开发者
了解Spring Boot:重要注解详解
了解Spring Boot:重要注解详解
|
16天前
|
Java Spring 容器
SpringBoot自动装配原理之@Import注解解析
SpringBoot自动装配原理之@Import注解解析
53 0
|
17天前
|
存储 缓存 NoSQL
Springboot中使用redisson + 自定义注解优雅的实现消息的发布订阅
Springboot中使用redisson + 自定义注解优雅的实现消息的发布订阅