原创 程序员侠客行 2025-02-27 08:30 重庆
点击关注公众号,“技术干货”及时达!
@Import
注解是 Spring 框架中一个非常强大的工具,它允许你将普通类、组件类、ImportSelector
实现类和ImportBeanDefinitionRegistrar
实现类引入到容器中。通过@Import
,你可以实现配置的模块化,使得代码更加清晰和易于维护。
Spring 集成很多框架时,就是通过 @Import 来实现的。
❝本文中源码来自 Spring 5.3.x 分支,github 源码地址:github.com/spring-proj…[1]
❞
一 如何使用 @Import
该注解只有一个 value 属性,取值为需要导入的类的 class 对象。
有三种用法,介绍如下。
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.
「注意,通过 @Import 直接导入的 bean,名称是该类的全类名。」
因此应这样获取 context.getBean(OrderService.class.getName(), OrderService.class)
1.2 @Import 与 ImportSelector 接口
ImportSelector
接口,允许你自定义条件动态选择要导入的配置类,有两个方法:
selectImports:返回一个全类名的数组,这些类将被添加到 spring 容器中。注意,该方法可以返回空数组,但是不能返回 null!
getExclusionFilter:返回一个 Predicate,用于排除 selectImports 方法返回值中的某些类。
来看示例,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 时报错。
1.3 @Import 与 ImportBeanDefinitionRegistrar 接口
ImportBeanDefinitionRegistrar 接口允许通过编程方式动态注册 BeanDefinition。
来看示例,定义 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 {
}
二 @Import 的原理
Spring 中ConfigurationClassPostProcessor类
,会扫描所有的@Configuration
类,并处理其中的@Import
注解。
扫描配置类:Spring 容器启动时,会扫描所有的@Configuration
类。
解析 @Import 注解:对于每个配置类,Spring 会解析其中的@Import
注解,获取需要导入的类。
处理导入的类:根据导入的类的类型(普通类、@Configuration
类、ImportSelector
实现类、ImportBeanDefinitionRegistrar
实现类),Spring 会采取不同的处理方式。
注册 BeanDefinition:最终,Spring 会将所有导入的类注册为 BeanDefinition,让 Spring 容器管理。
源码中ConfigurationClassParser
类负责解析@Configuration
类,其中 doProcessConfigurationClass 方法逻辑如下:
先递归地收集配置类或某个注解上的 @Import 的 value 值;
然后调用processImports
方法来处理@Import
注解,分 3 种情况处理导入的类。
例如,对于 ImportBeanDefinitionRegistrar 接口实现,先收集到 Map 中缓存起来,然后遍历 Map,逐个调用 registerBeanDefinitions 方法
三 源码中使用
Spring 源码中,模块化、插拔式的功能选项,就是通过 @Import 实现的。如 aop、
3.1 @EnableAspectJAutoProxy 实现
当在配置类上添加 @EnableAspectJAutoProxy 时,就启用了 Spring aop 功能。 「源码中,EnableAspectJAutoProxy 注解上通过 Import 引入了 AspectJAutoProxyRegistrar」,该类 ImportBeanDefinitionRegistrar 接口的实现,在 registerBeanDefinitions 方法中向容器注册了 AnnotationAwareAspectJAutoProxyCreator。
AnnotationAwareAspectJAutoProxyCreator 类是 BeanPostProcessor 接口的一个实现,会解析容器中 @Aspect 注解的 bean,封装 @Before 等方法为 Advisor 对象;在 bean 的初始化后阶段,为切面命中的 bean 创建代理对象。
3.2 @EnableAsync 实现
@EnableAsync 注解提供了开箱即用的异步解决方案,我们只需在想要异步执行的方法上加 @Async 即可。 「源码中,EnableAsync 注解上通过 Import 引入了 AsyncConfigurationSelector 类,」 该类是 ImportSelector 接口实现,在 selectImports 方法中引入了 ProxyAsyncConfiguration 类(默认使用 JDK 动态代理)。
在 ProxyAsyncConfiguration 中,又声明了 AsyncAnnotationBeanPostProcessor 的 bean。后者是 BeanPostProcessor 接口实现,在 bean 的初始化后阶段,为使用了 @Async 的 bean 创建代理对象。
点击关注公众号,“技术干货” 及时达!