平时开发,我们很少直接使用 @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 接口由类型来实现,这些类型根据给定的选择标准(通常是一个或多个注释属性)来确定应导入哪个 @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 接口由处理 @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 实例。