@Import
注解是Spring框架中一个非常强大的工具,它允许你将普通类、组件类、ImportSelector
实现类和ImportBeanDefinitionRegistrar
实现类引入到容器中。通过@Import
,你可以实现配置的模块化,使得代码更加清晰和易于维护。
Spring集成很多框架时,就是通过@Import来实现的。
本文中源码来自Spring 5.3.x分支,github源码地址:GitHub - spring-projects/spring-framework: Spring Framework
一 如何使用@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 {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{OrderService.class.getName(), UserService.class.getName()};}// 排除全类名中有Order的类@Overridepublic Predicate<String> getExclusionFilter() {return new Predicate<String>() {@Overridepublic 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;}@Overridepublic String toString() {return "User{" +"name='" + 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 {@Overridepublic 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创建代理对象。