Spring 中的@Import注解分析


       IOC(Inversion of Control)即控制反转,它是一种思想。 在这过程中,对象创建后通过在对象实例上设置属性来定义他们间的依赖关系,然后IOC容器在创建bean的时候注入这些依赖。在传统应用程序中, 可以在对象中通过new创建依赖的对象,这种方式属于直接获取依赖的对象, 而IOC意味着将设计好的对象交给容器控制,因此称为控制反转。


1.@Import 能做什么


      在Spring框架中,可以通过 XML定义Bean并加载到容器。一般情况下,每个单独的XML配置文件代表体系结构中的一个逻辑层或模块,让bean定义跨越多个XML文件会很有用,因此Spring支持通过一个或者多个<import/>元素 实现加载另一个XML文件中的bean定义。例如下面的例子

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

Spring提供的基于Java的配置允许编写注释,这可以降低配置的复杂性。像在Spring XML文件中使用< import/>元素来帮助模块化配置一样,@Import注释允许从另一个配置类加载@Bean定义。例如:

@Configuration
public class ConfigA {

    @Bean
    public User getUser() {
        return new User();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public Customer getCustomer() {
        return new Customer();
    }
}

用这种方式就不需要同时指定两个配置类ConfigA和ConfigB,初始化上下文的时候只需要显示提供ConfigB就可以了 ,例如下面的代码,运行后可以同时得到User和Customer对象

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
        User a = ctx.getBean(User.class);
        Customer b = ctx.getBean(Customer.class);
    }
}

由此可见,使用@Import 简化了容器实例化,因为只需要处理一个类,而不是要求您在构造期间记住大量的@Configuration类


2.@Import源码分析


1.@Import注解分析

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@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();

}

@Import注解的全类名是org.springframework.context.annotation.Import。其只有一个默认的value属性,该属性类型为Class<?>[],表示可以传入一个或多个Class对象。例如RedisAutoConfiguration对象

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    //省略
}

通过注释可以看出,该注解有如下作用:

  • 可以导入一个或多个组件类(通常是@Configuration配置类)
  • 该注解的功能与Spring XML中的<import/>元素相同。可以导入@Configuration配置类、ImportSelect和ImportBeanDefinitionRegistrar的实现类。
  • 从4.2版本开始,还可以引用常规组件类(普通类),该功能类似于AnnotationConfigApplicationContext.register方法。
  • 该注解可以在类中声明,也可以在元注解中声明。
  • 如果需要导入XML或其他非@Configuration定义的资源,可以使用@ImportResource注释。

2.@Import注解如何解析

Spring注册bean的过程中,ConfigurationClassParser对象主要用于解析配置类,其中ConfigurationClassParser #doProcessConfigurationClass方法通过读取源类中的注释、成员和方法,应用处理并构建完整的ConfigurationClass。@Import就是在此方法中被解析

部分源码如下:

//坐标:ConfigurationClassParser#doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

   //省略
   // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
   //省略
}


private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
		return;
	}

	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
	}else {
		this.importStack.push(configClass);
		try {
        //获取Import导入进来的类
		for (SourceClass candidate : importCandidates) {
             //1.判断该类是不是实现了ImportSelector接口
			if (candidate.isAssignable(ImportSelector.class)) {
				Class<?> candidateClass = candidate.loadClass();
                //实例化实现SelectImport接口的类
				ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
				this.environment, this.resourceLoader, this.registry);
				Predicate<String> selectorFilter = selector.getExclusionFilter();
				if (selectorFilter != null) {
					exclusionFilter = exclusionFilter.or(selectorFilter);
				}
                //如果实现了DeferredImportSelector
				if (selector instanceof DeferredImportSelector) {
					this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
				}else {
                    //普通的ImportSelector实现,调用selectImports方法,返回String数组,类的全类名称,并递归解析这些class
					String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
					Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
					processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
				}
			}
            //2.判断该类是否实现了ImportBeanDefinitionRegistrar接口
			else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
				Class<?> candidateClass = candidate.loadClass();
                //实例化类
				ImportBeanDefinitionRegistrar registrar =
					ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
							this.environment, this.resourceLoader, this.registry);
				configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
			}else {
            //3. 如果没有实现上面两个接口,例如@Configuration类
				this.importStack.registerImport(
				currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                //解析Configuration读取注解属性和方法
				processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
			}
	    	}
    	}
        //省略catch ..finally
       }
}

从源码上看processImports方法处理中对@Import的处理机制大致如下

1.如果Import的类实现了ImportSelector,一般情况下会调用selectImports方法,返回String数组,类的全类名称,并递归解析这些class

2.如果Import的类实现了ImportBeanDefinitionRegistrar 接口,会先实例化并把实例放入到configClass中名叫importBeanDefinitionRegistrars的Map集合中暂存

3.如果没有实现上述两个接口 (例如@Configuration),导入类将会解析并将定义的bean放入IOC容器中

  

前一篇:Spring中的事件与监听

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值