@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
ImportSelector
@Import
註釋是讓咱們導入一組指定的配置類--@Configuration
修飾的類,類名一旦指定,將所有被解析。相反,ImportSelector
將容許咱們根據條件動態選擇想導入的配置類,換句話說,它具備動態性。ImportSelector
使用時,咱們要建立一個類實現ImportSelector
接口,並重寫其中的String[] selectImports(AnnotationMetadata importingClassMetadata);
方法。數據結構
假設咱們想實現這樣一個功能,咱們建立一個CustomImportSelector
類,當使用CustomImportSelector
的元素是類時,咱們返回UserConfig
配置類,當使用CustomImportSelector
的元素是類時,咱們返回StudentConfig
配置類。app
UserConfig
和
StudentConfig
在
DemoApplication
的外層,不然,這兩個配置類就會被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
成功被注入框架
註解在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()
ConfigurationClassParser
是Spring
提供的用於解析@Configuration
的配置類,經過它將會獲得一個ConfigurationClass
對象列表。
其實通常在項目上,咱們實在是用不到上面的註解。有時候知識咱們學會了,可是咱們總想不出一種應用場景來將技術給用上,好煩。其實並非這樣的,瞭解技術的前因後果,長此以往會給咱們帶來不少能力,好比編寫更加優秀的代碼,更容易看懂框架源碼,框架上手快,bug解決速度快,牛逼吹起來會更有逼格。
可是,脫離需求,技術可能意義不是很大,接到一個需求,咱們能夠動動腦,看下這個需求能不能用上,就比如下面這張購物車實現圖:
像這些商品數量的操做,咱們徹底可使用redis的相關操做來實現,你卻非要給我建一張表來存儲,固然不是不能夠,只是緩存更簡單,更高效罷了。以用戶id爲key,商品id做爲field,使用redis哈希這種數據結構便可解決。
小編以爲先不急着實現需求,能夠先多動動腦筋,看看有什麼技術點能夠用到,再動手寫代碼。