@Import使用场景

一、注解说明

@Import注解可以将第三方包中的类对象注入到IOC容器中。使用Spring开发业务系统时,@Import注解的使用频率不及@Bean注解,@Import注解往往在一些中间件或者框架项目中使用的比较多。

在Spring底层,也大量使用了@Import注解来向IOC容器中注入Bean对象。当然,如果在开发业务系统时,也可以使用@Import注解向IOC容器中注入Bean对象。@Import注解相比于@Bean注解来讲,在使用上会更加灵活。

1. 注解源码

@Import注解只能标注到类或其他注解上,通常与配置类一起使用的,使用此注解引入的类上可以不再使用@Configuration,@Component等注解标注。本节,就对@Import注解的源码进行简单的剖析。

/**
 * Since: 3.0
 * @author Chris Beams
 * @author Juergen Hoeller
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
 Class<?>[] value();
}

从@Import源码的注释可以看出,@Import是Spring从3.0版本开始提供的注解,注解中只有一个Class数组类型的value属性。含义如下所示:

  • value:Class数组类型,用于指定其他配置类的字节码,支持指定多个配置类。另外,使用value属性指定的有一定的条件,必须是普通类、实现了ImportSelector接口的类和实现了ImportBeanDefinitionRegistrar接口的类。

注意:@Import注解只能标注到类上。

2. 注解使用场景

在使用Spring进行开发时,如果涉及到的配置项比较多,要是将所有的配置项都写到一个类里,则配置结构和配置内容将会变得非常杂乱,如果此时使用@Import注解,则可以将配置项进行分类管理。

另外,如果在项目中需要引入第三方的类,并且需要将这些类的对象注入到IOC容器中,也可以使用@Import注解。

二、使用案例

@Import注解可以引入三种类,分别如下所示:

  • 引入普通类,将Bean对象注入到IOC容器。
  • 引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器,但是实现了ImportSelector接口的类对象本身不会被注册到IOC容器中。
  • 引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中,但是实现了ImportBeanDefinitionRegistrar接口的类对象本身不会被注册到IOC容器中。

1. 引入普通类案例

本节,主要实现使用@Import注解实现引入普通类,并且将Bean对象注入到IOC容器中的案例。具体实现步骤如下所示:

(1)新建User类

public class User {
    private Long userId;
    private String userName;
    //#############省略getter/serrer方法############
}

可以看到,User类就是一个普通的类对象,后续会通过@Import注解引入User类,并且将User类的对象注入到IOC容器中。

(2)新建Spring配置类ImportConfig

@Import(value = {User.class})
@Configuration
public class ImportConfig {
}

可以看到,ImportConfig类主要是Spring的配置类,会在ImportConfig类上标注@Configuration注解和@Import注解,并且会通过@Import注解引入User类,将User类的对象注入到IOC容器中。

(3)新建ImportTest类

public class ImportTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
        String[] definitionNames = context.getBeanDefinitionNames();
        Arrays.stream(definitionNames).forEach((definitionName) -> System.out.println(definitionName));
    }
}

可以看到,ImportTest类主要是案例的测试类,在ImportTest类的main()方法中,主要打印了Bean定义的名称。

(4)运行ImportTest类

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User

其中,以org.springframework包命名的Bean是Spring内部的Bean。另外,可以看到,结果信息中也输出了ImportConfig类的Bean名称和User类的Bean名称(注意:此时bean的名称是全限定类名)。

说明:使用@Import注解可以引入普通的类,并且能够将类对象注入到Spring容器中。

2. 引入实现了ImportSelector接口的类案例

本节,主要实现使用@Import注解引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器中的案例。具体的实现步骤如下所示:

注意:本节实现的案例是在 1. 引入普通类案例 的基础上实现的。

(1)新建ImportSelectorBean类

public class ImportSelectorBean {
    private Long id;
    private String name;
    //########省略getter/setter方法#########
}

可以看到,ImportSelectorBean类是一个普通的类,ImportSelectorBean类的对象后续会通过ImportSelector接口的selectImports()注入到IOC容器中。

(2)新建MyImportSelector类

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ImportSelectorBean.class.getName()};
    }
}

可以看到,MyImportSelector类实现了ImportSelector接口,并实现了ImportSelector接口的selectImports()方法,在selectImports()中返回了包含ImportSelectorBean类的全类名的Spring数组。

(3)修改ImportConfig类

@Import(value = {User.class, MyImportSelector.class})
@Configuration
public class ImportConfig {
}

可以看到,在ImportConfig类上标注的@Import注解的value属性中,新增MyImportSelector类的Class对象。

(4)运行ImportTest类

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean

可以看到,在输出的结果信息中,除了有Spring内部的Bean对象的名称、ImportConfig类的Bean对象名称和User类的Bean对象名称外,还输出了ImportSelectorBean类的Bean对象名称。但是,并没有输出实现了ImportSelector接口的MyImportSelector类的Bean对象的名称。

说明:使用@Import注解可以引入实现了ImportSelector接口的类,将selectImports()方法返回的Bean数组注入到IOC容器中,但是实现了ImportSelector接口的类对象本身不会被注册到IOC容器中。

3. 引入实现了ImportBeanDefinitionRegistrar接口的类案例

本节,主要实现使用@Import注解引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中的案例。具体实现步骤如下所示:

(1)新增ImportBeanDefinitionRegistrarBean类

public class ImportBeanDefinitionRegistrarBean {
    private Long id;
    private String name;
 //#########省略getter/setter方法############
}

可以看到,ImportBeanDefinitionRegistrarBean类就是一个普通的类,后续会通过ImportBeanDefinitionRegistrar接口的实现类实现的registerBeanDefinitions()方法将ImportBeanDefinitionRegistrarBean类的Bean对象注入到IOC容器中。

(2)新增MyImportBeanDefinitionRegistrar类

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
        BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
}

可以看到,MyImportBeanDefinitionRegistrar类实现了ImportBeanDefinitionRegistrar接口,并实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法。在registerBeanDefinitions()方法中,获取ImportBeanDefinitionRegistrarBean类的全类名作为注入到IOC容器中的Bean名称。接下来,调用RootBeanDefinition类的构造方法传入ImportBeanDefinitionRegistrarBean类的Class对象创建BeanDefinition对象。最终,调用registry的registerBeanDefinition()方法将创建的BeanDefinition对象注入到IOC容器中。

(3)修改ImportConfig类

@Import(value = {User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}

可以看到,在ImportConfig类上标注的@Import注解的value属性中,新增了实现了ImportBeanDefinitionRegistrar接口的MyImportBeanDefinitionRegistrar类的Class对象。

(4)运行ImportTest类

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean

可以看到,在输出的结果信息中,除了Spring内部的Bean名称、ImportConfig类的Bean名称、User类的Bean名称和ImportSelectorBean类的Bean名称外,还输出了ImportBeanDefinitionRegistrarBean类的名称。但是并没有输出实现了ImportBeanDefinitionRegistrar接口的MyImportBeanDefinitionRegistrar类的Bean名称。

说明:使用@Import注解能够引入实现了ImportBeanDefinitionRegistrar接口的类,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry对象注入BeanDefinition对象到IOC容器中,但是实现了ImportBeanDefinitionRegistrar接口的类对象本身不会被注册到IOC容器中。

三、ImportSelector和ImportBeanDefinitionRegistrar介绍

特别说明:
我们在注入bean对象时,可选的方式有很多种。
例如:
我们自己写的类,可以使用@Component,@Service,@Repository,@Controller等等。
我们导入的第三方库中的类,可以使用@Bean(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。
但是当我们的类很多时,在每个类上加注解会很繁琐,同时使用@Bean或者@Import写起来也很麻烦。此时我们就可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。顺便说一句,在SpringBoot中,@EnableXXX这样的注解,绝大多数都借助了ImportSelector或者ImportBeanDefinitionRegistrar。在spring中,@EnableTransactionManagement就是借助了ImportSelector,而@EnableAspectJAutoPorxy就是借助了ImportBeanDefinitionRegistrar。
共同点:
他们都是用于动态注册bean对象到容器中的。并且支持大批量的bean导入。
区别:
ImportSelector是一个接口,我们在使用时需要自己提供实现类。实现类中返回要注册的bean的全限定类名数组,然后执行ConfigurationClassParser类中的processImports方法注册bean对象。
ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册bean到容器中。
注意事项:
实现了ImportSelector接口或者ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。
同时,在注册到容器中时bean的唯一标识是全限定类名,而非短类名。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值