稀土掘金技术社区

Spring中@Import原理和使用

Image点击关注公众号,“技术干货” 及时达!

@Import注解是 Spring 框架中一个非常强大的工具,它允许你将普通类、组件类、ImportSelector实现类和ImportBeanDefinitionRegistrar实现类引入到容器中。通过@Import,你可以实现配置的模块化,使得代码更加清晰和易于维护。

Spring 集成很多框架时,就是通过 @Import 来实现的。

❝

本文中源码来自 Spring 5.3.x 分支,github 源码地址:github.com/spring-proj…[1]

❞

一 如何使用 @Import

该注解只有一个 value 属性,取值为需要导入的类的 class 对象。

Image

有三种用法,介绍如下。

1.1 @Import 导入普通类

对于普通类(「没有被声明为 Component」),通过 @Import 也可以添加到 Spring 容器中。

例如,使用 @Import(OrderService.class) ,就能向容器中添加 OrderService 类型的 bean 「。」

import com.xiakexing.service.OrderService;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(OrderService.class)
public class AppConfig {
}

// 没有使用@Component的普通类
public class OrderService {

public void test() {
System.out.println("执行OrderService.test");
}
}

public class Test {

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean("orderService", OrderService.class);
orderService.test();
}
}

当执行上面代码时,会出现报错:No bean named 'orderService' available.Image

「注意,通过 @Import 直接导入的 bean,名称是该类的全类名。」 Image

因此应这样获取 context.getBean(OrderService.class.getName(), OrderService.class)

1.2 @Import 与 ImportSelector 接口

ImportSelector接口,允许你自定义条件动态选择要导入的配置类,有两个方法:

  1. selectImports:返回一个全类名的数组,这些类将被添加到 spring 容器中。注意,该方法可以返回空数组,但是不能返回 null!
  2. getExclusionFilter:返回一个 Predicate,用于排除 selectImports 方法返回值中的某些类。Image

来看示例,OrderService 与上面相同,增加 UserService 类。

public class UserService {
public void test() {
System.out.println("执行UserService.test");
}
}

SimpleImportSelector 只导入 UserService 类。

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

import java.util.function.Predicate;

public class SimpleImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{OrderService.class.getName(), UserService.class.getName()};
}

// 排除全类名中有Order的类
@Override
public Predicate<String> getExclusionFilter() {
return new Predicate<String>() {
@Override
public boolean test(String name) {
return name.contains("Order");
}
};
}
}

import com.xiakexing.service.SimpleImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(SimpleImportSelector.class)
public class AppConfig {
}

import com.xiakexing.service.OrderService;
import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class.getName(), UserService.class);
userService.test();
OrderService orderService = context.getBean(OrderService.class.getName(), OrderService.class);
orderService.test();
}
}

结果就是 userService.test() 执行成功,而获取 orderService 时报错。Image

1.3 @Import 与 ImportBeanDefinitionRegistrar 接口

ImportBeanDefinitionRegistrar 接口允许通过编程方式动态注册 BeanDefinition。Image

来看示例,定义 User 类。

public class User {

private String name;
private int age;

public User() {
}

public User(String name, int age) {
this.name = name;
this.age = age;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
" + name + ''' +
", age=" + age +
'}';
}
}

自定义 ImportBeanDefinitionRegistrar 接口实现,编程式向容器中注册 User 类的 beanDefinition,Spring 容器将创建对应的 bean。

import com.xiakexing.entity.User;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class SimpleBeanRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
definition.setBeanClass(User.class);
ConstructorArgumentValues values = new ConstructorArgumentValues();
values.addIndexedArgumentValue(0, "Tom");
values.addIndexedArgumentValue(1, 29);
definition.setConstructorArgumentValues(values);
definition.setScope("singleton");
registry.registerBeanDefinition("user", definition);
}
}

import com.xiakexing.service.SimpleBeanRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(SimpleBeanRegistrar.class)
public class AppConfig {
}

Image

二 @Import 的原理

Spring 中ConfigurationClassPostProcessor类,会扫描所有的@Configuration类,并处理其中的@Import注解。

  1. 扫描配置类:Spring 容器启动时,会扫描所有的@Configuration类。
  2. 解析 @Import 注解:对于每个配置类,Spring 会解析其中的@Import注解,获取需要导入的类。
  3. 处理导入的类:根据导入的类的类型(普通类、@Configuration类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类),Spring 会采取不同的处理方式。
  4. 注册 BeanDefinition:最终,Spring 会将所有导入的类注册为 BeanDefinition,让 Spring 容器管理。

源码中ConfigurationClassParser类负责解析@Configuration类,其中 doProcessConfigurationClass 方法逻辑如下:

  1. 先递归地收集配置类或某个注解上的 @Import 的 value 值;ImageImage
  2. 然后调用processImports方法来处理@Import注解,分 3 种情况处理导入的类。Image
  3. 例如,对于 ImportBeanDefinitionRegistrar 接口实现,先收集到 Map 中缓存起来,Image然后遍历 Map,逐个调用 registerBeanDefinitions 方法ImageImage

三 源码中使用

Spring 源码中,模块化、插拔式的功能选项,就是通过 @Import 实现的。如 aop、

3.1 @EnableAspectJAutoProxy 实现

当在配置类上添加 @EnableAspectJAutoProxy 时,就启用了 Spring aop 功能。Image 「源码中,EnableAspectJAutoProxy 注解上通过 Import 引入了 AspectJAutoProxyRegistrar」,该类 ImportBeanDefinitionRegistrar 接口的实现,在 registerBeanDefinitions 方法中向容器注册了 AnnotationAwareAspectJAutoProxyCreator。

AnnotationAwareAspectJAutoProxyCreator 类是 BeanPostProcessor 接口的一个实现,会解析容器中 @Aspect 注解的 bean,封装 @Before 等方法为 Advisor 对象;在 bean 的初始化后阶段,为切面命中的 bean 创建代理对象。

3.2 @EnableAsync 实现

@EnableAsync 注解提供了开箱即用的异步解决方案,我们只需在想要异步执行的方法上加 @Async 即可。Image 「源码中,EnableAsync 注解上通过 Import 引入了 AsyncConfigurationSelector 类,」 该类是 ImportSelector 接口实现,在 selectImports 方法中引入了 ProxyAsyncConfiguration 类(默认使用 JDK 动态代理)。Image在 ProxyAsyncConfiguration 中,又声明了 AsyncAnnotationBeanPostProcessor 的 bean。后者是 BeanPostProcessor 接口实现,在 bean 的初始化后阶段,为使用了 @Async 的 bean 创建代理对象。