简介
Spring 3.0之前,创建Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内。而在Spring 3.0之后提供了JavaConfig的方式,也就是将IOC容器里Bean的元信息以java代码的方式进行描述。我们可以通过@Configuration与@Bean这两个注解配合使用来将原来配置在xml文件里的bean通过java代码的方式进行描述
@Import注解提供了@Bean注解的功能,同时还有xml配置文件里 标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration
先看一下@Import注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
从源码里可以看出@Import可以配合 Configuration
, ImportSelector
, ImportBeanDefinitionRegistrar
来使用,下面的or表示也可以把Import当成普通的Bean使用@Import只允许放到类上面,不能放到方法上。下面我们来看具体的使用方式
普通使用方法
这种方式可以直接把类加入到Spring IOC容器
@Configuration
@Import(value={UserServiceImpl.class})
public class Config {
}
但是这种方式有一些问题,那就是只能使用类的无参构造方法来创建bean,对于有参数的构造方法就无能为力了
结合ImportBeanDefinitionRegistrar
接口
ImportBeanDefinitionRegistrar
接口的源码如下:
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
可以看到这个接口唯一的方法是有两个参数的
- AnnotationMetadata:通过这个参数可以拿到类的元数据信息
- BeanDefinitionRegistry:通过这个参数可以操作IOC容器
我们可以使用一个类来实现这个接口
public class UserServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
BeanDefinitionBuilder userService = BeanDefinitionBuilder.rootBeanDefinition(UserServiceImpl.class);
registry.registerBeanDefinition("userService", userService.getBeanDefinition());
}
}
可以看到我们在这个方法里面做一些特殊操作什么的都是可以的,相比较于普通的方式可是灵活了很多
接着我们在@Import注解引入的地方只需要修改为引入UserServiceBeanDefinitionRegistrar就可以了
@Configuration
@Import(value={UserServiceBeanDefinitionRegistrar.class})
public class Config {
}
结合ImportSelector
接口
相比较与实现ImportBeanDefinitionRegistrar
接口之后直接操作Bean容器来说,使用ImportSelector
会更加优雅一些,只需要返回需要注入类的全限定名就可以了
ImportSelector接口的源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
public class UserServiceImportSelect implements ImportSelector{
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{UserServiceImpl.class.getName()};
}
}
@Configuration()
@Import(value={UserServiceImportSelect.class})
public class Config {
}
相比较三种方式来说可以看到最后这种才是最优雅的方式
源码解析
首先我们就以第三种比较优雅的方式出发,使用Call Hierarchy看一下ImportSelector接口的selectImports方法调用链关系:
看过之前Spring源码解析文章的同学都知道,refresh
方法是用来初始化容器上下文的。跟着这个调用链走下来到中间有一个类是ConfigurationClassPostProcessor
,根据类名我们就可以猜到这个类应该是处理配置类(也就是标注@Configuration
)的。那么从这开始看吧
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " beanDef);
}
}
//查看是否是配置类
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
//如果没有配置类就直接返回
if (configCandidates.isEmpty()) {
return;
}
//对这些配置类根据Order排序
Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
@Override
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
&nb