@Import、ImportSelector註解使用及源碼分析

1、@Import

在學習@Import這個註解時,小編在想一個問題,這個註解的做用是導入一個配置Configuration類,那到底什麼地方會用到它呢?想到咱們工程中也不會使用這個註解去導入配置呀,咱們都是新建一個類xxxxxxConfiguration.java,而後直接在類裏邊把全部的Bean組件啥的都給聲明瞭,下面的代碼咱們感受似曾相識,哈哈。java

/**
 * xx配置類,裏邊會有n個bean
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class CustomConfig {
    @Bean
    public Marker zuulProxyMarkerBean() {
    	return new Marker();
    }
    ......
}
複製代碼

但你有沒有想過一個問題,當配置類CustomConfig不在@SpringBootApplication所在包及其子包下時,它還能被裝配進去嗎?答案是不能。由於,它不在springboot默認掃描範圍內。詳情可查看SpringBoot封裝咱們本身的Starterredis

我講的到底有沒有道理呢?讓咱們來作個實驗。UserConfig用於配置User對象,它位於com.example包下,DemoApplication.java位於com.example.demo包下,此時SpringBoot是無法掃描到UserConfig並注入User對象的。spring

UserConfig.java

/**
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class UserConfig {
    @Bean
    public User getUser() {
    	return new User();
    }
}
複製代碼

使用下面代碼注入會報錯:瀏覽器

@Autowired
private User user;
複製代碼
The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)
複製代碼

怎麼辦呢?解決辦法有二種:緩存

  • 一、使用@ComponentScan("com.**")註解一句話搞定
  • 二、使用@Import註解引入

方法一簡單粗暴,看似沒啥毛病,但這是創建在你知道bean對象的大概包路徑的基礎上的,第三方的jar包中的bean可並非都是以com開頭命名的,這就尷尬了。 在上面的路徑結構基礎上,咱們在DemoApplication.java中加入@Import(UserConfig.class)這個註解便可解決問題。springboot

另外,@Import至關於Spring xml配置文件中的<import />標籤。bash

2、ImportSelector

@Import註釋是讓咱們導入一組指定的配置類--@Configuration修飾的類,類名一旦指定,將所有被解析。相反,ImportSelector將容許咱們根據條件動態選擇想導入的配置類,換句話說,它具備動態性。ImportSelector使用時,咱們要建立一個類實現ImportSelector接口,並重寫其中的String[] selectImports(AnnotationMetadata importingClassMetadata);方法。數據結構

假設咱們想實現這樣一個功能,咱們建立一個CustomImportSelector類,當使用CustomImportSelector的元素是類時,咱們返回UserConfig配置類,當使用CustomImportSelector的元素是類時,咱們返回StudentConfig配置類。app

注意目錄層次,要保證 UserConfigStudentConfigDemoApplication的外層,不然,這兩個配置類就會被spring默認解析到了。

/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class UserConfig {
	@Bean
	public User getUser() {
		return new User();
	}
}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class StudentConfig {

	@Bean
	public Student getStudent() {
		return new Student();
	}

}
@SpringBootApplication
// 一、很明顯,這裏CustomImportSelector修飾的是一個類,咱們將會返回UserConfig
@Import(CustomImportSelector.class)
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月19日
 * @Version 1.0
 */
@RestController
public class MyController {

	@Autowired(required = false)
	private Student student;

	@Autowired(required = false)
	private User user;

	@RequestMapping("/getStudent")
	private String getStudent() {
		return "student=[" + student + "],user=[" + user + "]";
	}

}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
public class CustomImportSelector implements ImportSelector {

	/**
	 * importingClassMetadata:被修飾的類註解信息
	 */
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {

		// 注意,自定義註解這裏是拿不到的
		System.out.println(importingClassMetadata.getAnnotationTypes());

		// 若是被CustomImportSelector導入的組件是類,那麼咱們就實例化UserConfig
		if (!importingClassMetadata.isInterface()) {
			return new String[] { "com.example.UserConfig" };
		}

		// 此處不要返回null
		return new String[] { "com.example.StudentConfig" };
	}
}
複製代碼

打開瀏覽器,調用接口,獲得以下返回,證實Student沒有被注入成爲bean,而User成功被注入框架

3、講講原理

註解在Spring啓動過程當中在哪裏被解析? Spring源碼版本:5.1.6.RELEASE

小編粗略debug了下源碼,這2個註解的解析過程統一在ConfigurationClassParser$DeferredImportSelectorGroupingHandler類中的processImports()方法實現的,該方法大體源碼以下:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    	Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    
    if (importCandidates.isEmpty()) {
    	return;
    }
    
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    	this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
    	this.importStack.push(configClass);
        try {
        for (SourceClass candidate : importCandidates) {
            // 一、若是該配置類被ImportSelector修飾,則當成ImportSelector進行處理
        	if (candidate.isAssignable(ImportSelector.class)) {
        		// Candidate class is an ImportSelector -> delegate to it to determine imports
        		Class<?> candidateClass = candidate.loadClass();
        		ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        		ParserStrategyUtils.invokeAwareMethods(
        				selector, this.environment, this.resourceLoader, this.registry);
        		if (selector instanceof DeferredImportSelector) {
        			this.deferredImportSelectorHandler.handle(
        					configClass, (DeferredImportSelector) selector);
        		}
        		else {
        			String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
        			Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
        			processImports(configClass, currentSourceClass, importSourceClasses, false);
        		}
        	}
        	// 二、若是該配置類被ImportBeanDefinitionRegistrar修飾,則當成ImportBeanDefinitionRegistrar進行處理
        	else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        		// Candidate class is an ImportBeanDefinitionRegistrar ->
        		// delegate to it to register additional bean definitions
        		Class<?> candidateClass = candidate.loadClass();
        		ImportBeanDefinitionRegistrar registrar =
        				BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        		ParserStrategyUtils.invokeAwareMethods(
        				registrar, this.environment, this.resourceLoader, this.registry);
        		configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        	}
        	// 三、若是該配置類被Import修飾,則當成Import進行處理
        	else {
        		// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        		// process it as an @Configuration class
        		this.importStack.registerImport(
        				currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        		processConfigurationClass(candidate.asConfigClass(configClass));
        	}
        }
        }
        catch (BeanDefinitionStoreException ex) {
        throw ex;
        }
        catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
        		"Failed to process import candidates for configuration class [" +
        		configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
        this.importStack.pop();
        }
    }
}
複製代碼

從Spring啓動開始,到執行註解解析,大體調用鏈路以下:

SpringApplication-refreshContext()->AbstractApplicationContext-refresh()-postProcessBeanFactory()->PostProcessorRegistrationDelegate-invokeBeanDefinitionRegistryPostProcessors()->ConfigurationClassPostProcessor-processConfigBeanDefinitions()->ConfigurationClassParser-parse()->ConfigurationClassParser-processImports()

ConfigurationClassParserSpring提供的用於解析@Configuration的配置類,經過它將會獲得一個ConfigurationClass對象列表。

4、總結

其實通常在項目上,咱們實在是用不到上面的註解。有時候知識咱們學會了,可是咱們總想不出一種應用場景來將技術給用上,好煩。其實並非這樣的,瞭解技術的前因後果,長此以往會給咱們帶來不少能力,好比編寫更加優秀的代碼,更容易看懂框架源碼,框架上手快,bug解決速度快,牛逼吹起來會更有逼格。

可是,脫離需求,技術可能意義不是很大,接到一個需求,咱們能夠動動腦,看下這個需求能不能用上,就比如下面這張購物車實現圖:

像這些商品數量的操做,咱們徹底可使用redis的相關操做來實現,你卻非要給我建一張表來存儲,固然不是不能夠,只是緩存更簡單,更高效罷了。以用戶id爲key,商品id做爲field,使用redis哈希這種數據結構便可解決。

小編以爲先不急着實現需求,能夠先多動動腦筋,看看有什麼技術點能夠用到,再動手寫代碼。

相關文章
相關標籤/搜索