Spring Boot 教程

@Import 注解

平时开发,我们很少直接使用 @Import 注解,如果你看过框架源码,则经常会看见 @Import 注解的身影。

该注解是用来导入的一个或多个组件类,通常是 @Configuration 类,也可以是普通类。提供与 Spring XML 中的 <import /> 元素相同的功能。允许导入 @Configuration 类,ImportSelector 和 ImportBeanDefinitionRegistrar 实现以及常规组件类(从4.2开始,类似于 AnnotationConfigApplicationContext.register(java.lang.Class<?>...))。

导入的 @Configuration 类中声明的 @Bean 定义可以使用 @Autowired 注解注入进行访问。可以对 bean 本身进行自动装配,也可以对声明 bean 的配置类实例进行自动装配。后一种方法允许在@Configuration 类方法之间进行显式的,IDE 友好的导航。

可以在类级别或作为元注释声明。

如果需要导入 XML 或其他非 @Configuration bean 定义资源,请改用 @ImportResource 注解。

@Import 注解支持三种使用方式:

  • 导入带有 @Configuration 注解的配置类,注意:4.2 版本之前只可以导入配置类,4.2 版本之后也可以导入普通类

  • ImportSelector 的实现

  • ImportBeanDefinitionRegistrar 的实现

要彻底了解该注解,我们还是应该看看它的源码:

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

   /**
    * {@link Configuration @Configuration}, {@link ImportSelector},
    * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
    */
   Class<?>[] value();

}

上面源码中的 @Target(ElementType.TYPE) 表示了该注解只能使用在类、接口(包括注释类型)或枚举声明上面。

注意:上面 value 属性是一个数组,因此我们可以一次加载多个类

示例代码

加载普通类

(1)定义一个 MyBean,代码如下:

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyBean {
    private String date;

    public MyBean init() {
        this.date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        return this;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

}

(2)在 Spring Boot 启动类上面使用 @Import 注解加载 MyBean 类,代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 加载普通类
 */
 // 这里可以一次加载多个类,例如:@Import({ MyBean.class, MyBean2.class, MyBean3.class })
@Import({ MyBean.class })
@RestController
@SpringBootApplication
public class Demo1Application {

    @Autowired
    private MyBean myBean;

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

    @RequestMapping("/")
    public String index() {
        return myBean.init().getDate();
    }

}

加载配置类

注意:配置类中的 MyBean 沿用上面实例定义的 MyBean 类

(1)定义自己的配置类 MyConfig,注意,该配置类上面没有添加 @Configuration 注解,因此不会自动被加载。代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import org.springframework.context.annotation.Bean;

public class MyConfig {

    @Bean
    public MyBean getMyBean() {
        System.out.println("Init MyBean");
        return new MyBean();
    }

}

(2)客户端代码,使用 @Import 注解手动加载 MyConfig 配置类。代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import com.huangx.springboot.springboot_import_demo1.config.MyConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 加载配置类
 */
@Import({ MyConfig.class })
@RestController
@SpringBootApplication
public class Demo1Application {

    @Autowired
    private MyBean myBean;

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

    @RequestMapping("/")
    public String index() {
        return myBean.init().getDate();
    }

}

实现 ImportSelector 接口

ImportSelector 接口由类型来实现,这些类型根据给定的选择标准(通常是一个或多个注释属性)来确定应导入哪个 @Configuration 类。ImportSelector 可以实现以下任何 Aware 接口,并且将在 selectImports(org.springframework.core.type.AnnotationMetadata) 之前调用它们各自的方法:

  • EnvironmentAware

  • BeanFactoryAware

  • BeanClassLoaderAware

  • ResourceLoaderAware

或者,类可以提供一个具有以下一种或多种支持参数类型的构造函数:

  • Environment

  • BeanFactory

  • ClassLoader

  • ResourceLoader

ImportSelector 实现通常以与常规 @Import 注解相同的方式处理,但是,也可以推迟导入的选择,直到所有 @Configuration 类都被处理。

ImportSelector 接口提供了如下两个方法:

  • default Predicate<String> getExclusionFilter():返回一个谓词,用于从导入候选项中排除类,该谓词将临时应用于通过此选择器的导入找到的所有类。如果这个谓词对给定的完全限定类名返回 true,则该类将不会被视为导入的配置类,从而绕过类文件加载和元数据内省。

  • String[] selectImports(AnnotationMetadata importingClassMetadata):根据正在导入的 @Configuration 类的 AnnotationMetadata,选择并返回应该导入的类的名称。

(1)实现自己的 ImportSelector 类 MyImportSelector,该类返回一个字符串数组(MyBean 和 TimeBean 类的全限定名);代码如下:

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回应该导入的类完全限定名称
        return new String[]{
                "com.huangx.springboot.springboot_import_demo1.bean.MyBean",
                "com.huangx.springboot.springboot_import_demo1.bean.TimeBean"
        };
    }

}

(2)在 Spring Boot 主类上面使用 @Import 注解加载自定义的 MyImportSelector 类,代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import com.huangx.springboot.springboot_import_demo1.bean.TimeBean;
import com.huangx.springboot.springboot_import_demo1.impl.MyImportSelector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 使用 MyImportSelector 加载类
 */
@Import({ MyImportSelector.class })
@RestController
@SpringBootApplication
public class Demo1Application {

    @Autowired
    private MyBean myBean;

    @Autowired
    private TimeBean timeBean;

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

    @RequestMapping("/")
    public String index() {
        System.out.println(timeBean.init().getTime());
        return myBean.init().getDate();
    }

}

实现 ImportBeanDefinitionRegistrar 接口

ImportBeanDefinitionRegistrar 接口由处理 @Configuration 类时注册附加 bean 定义的类型去实现。在 bean 定义级别(与@Bean方法/实例级别相反)进行操作时很有用,这是期望的或必需的。

与 @Configuration 和 ImportSelector 一起,这种类型的类可以提供给 @Import 注解()或者也可以从 ImportSelector 返回)。

ImportBeanDefinitionRegistrar 可以实现以下任何 Aware 接口,并且将在 registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.beans.factory.support.BeanNameGenerator) 之前调用它们的相应方法:

  • EnvironmentAware

  • BeanFactoryAware

  • BeanClassLoaderAware

  • ResourceLoaderAware

或者,类可以提供一个具有以下一种或多种支持参数类型的构造函数:

  • Environment

  • BeanFactory

  • ClassLoader

  • ResourceLoader

(1)实现 ImportBeanDefinitionRegistrar  接口,实现我们自己的 MyImportBeanDefinitionRegistrar 类,该类将注册两个 Bean 到 Spring 容器。代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import com.huangx.springboot.springboot_import_demo1.bean.TimeBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // myBean 注册为多例
        // 指定bean定义信息(包括bean的类型、作用域...)
        RootBeanDefinition myBeanDefinition = new RootBeanDefinition(MyBean.class);
        // 设置bean的目标范围,缺省状态是单例
        myBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
        registry.registerBeanDefinition("myBean", myBeanDefinition);

        // timeBean 注册为单例
        RootBeanDefinition timeBeanDefinition = new RootBeanDefinition(TimeBean.class);
        registry.registerBeanDefinition("timeBean", timeBeanDefinition);
    }

}

(2)在 Spring Boot 的主类上使用 @Import 注解声明自定义的 MyImportBeanDefinitionRegistrar 类,代码如下:

import com.huangx.springboot.springboot_import_demo1.bean.MyBean;
import com.huangx.springboot.springboot_import_demo1.bean.TimeBean;
import com.huangx.springboot.springboot_import_demo1.impl.MyImportBeanDefinitionRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 使用 ImportBeanDefinitionRegistrar 加载类
 */
@Import({ MyImportBeanDefinitionRegistrar.class })

@RestController
@SpringBootApplication
public class Demo1Application {

    @Autowired
    private ApplicationContext context;

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

    @RequestMapping("/")
    public String index() {
        MyBean myBean = context.getBean(MyBean.class);
        TimeBean timeBean = context.getBean(TimeBean.class);
        StringBuilder builder = new StringBuilder();

        builder.append("<p>");
        if(null == myBean) {
            builder.append("myBean is null");
        } else {
            System.out.println(myBean);
            builder.append("myBean = " + myBean.init().getDate());
        }
        builder.append("</p><p>");

        if(null == timeBean) {
            builder.append("timeBean is null");
        } else {
            System.out.println(timeBean);
            builder.append("timeBean = " + timeBean.init().getTime());
        }
        builder.append("</p>");

        return builder.toString();
    }

}

上面代码中,我们在 index() 方法中使用 ApplicationContext 的实例从 Ioc 容器中获取 MyBean 和 TimeBean 的实例。如果你访问两次 index() 方法,则会发现 MyBean 每次返回的是一个不同的实例,输出如下:

com.huangx.springboot.springboot_import_demo1.bean.MyBean@1b516608
com.huangx.springboot.springboot_import_demo1.bean.TimeBean@648ad20
com.huangx.springboot.springboot_import_demo1.bean.MyBean@6c684718
com.huangx.springboot.springboot_import_demo1.bean.TimeBean@648ad20

上面 1b516608 和 6c684718 是两个不同的 MyBean 实例。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号