亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

@ComponentScan注解用法之包路徑占位符的示例分析

發布時間:2021-08-17 09:02:58 來源:億速云 閱讀:118 作者:小新 欄目:開發技術

這篇文章將為大家詳細講解有關@ComponentScan注解用法之包路徑占位符的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

@ComponentScan注解的basePackages屬性支持占位符嗎?

答案是肯定的。

代碼測試

首先編寫一個屬性配置文件(Properties),名字隨意,放在resources目錄下。

在該文件中只需要定義一個屬性就可以,屬性名隨意,值必須是要掃描的包路徑。

basepackages=com.xxx.fame.placeholder

編寫一個Bean,空類即可。

package com.xxx.fame.placeholder;
import org.springframework.stereotype.Component;
@Component
public class ComponentBean {
}

編寫啟動類,在啟動類中通過@PropertySource注解來將外部配置文件加載到Spring應用上下文中,其次在@ComponentScan注解的value屬性值中使用占位符來指定要掃描的包路徑(占位符中指定的屬性名必須和前面在屬性文件中定義的屬性名一致)。

使用Spring 應用上下文來獲取前面編寫的Bean,執行main方法,那么是會報錯呢?還是正常返回實例呢?

package com.xxx.fame.placeholder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
@PropertySource("classpath:componentscan.properties")
@ComponentScan("${basepackages}")
public class ComponentScanPlaceholderDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ComponentScanPlaceholderDemo.class);
        context.refresh();
        ComponentBean componentBean = context.getBean(ComponentBean.class);
        System.out.println(componentBean);
    }
}

運行結果如下,可以看到正常返回ComponentBean在IoC容器中的實例了。

@ComponentScan注解用法之包路徑占位符的示例分析

這里我們是將@PropertySource注解添加到啟動類上,那如果將@ProperSource注解添加到ComponentBean類上,程序還可以正常運行嗎(將啟動類上的@PropertySource注解移除掉)?

@Component
@PropertySource("classpath:componentscan.properties")
public class ComponentBean {}

啟動應用程序。可以發現程序無法啟動,拋出以下異常。在這個錯誤里有一個關鍵信息:“Could not resolve placeholder ‘basepackages' in value " b a s e p a c k a g e s " ” , 即 無 法 解 析 @ C o m p o n e n t S c a n 注 解 中 指 定 的 “ {basepackages}"”,即無法解析@ComponentScan注解中指定的“ basepackages"”,即無法解析@ComponentScan注解中指定的“{basepackages}”。

@ComponentScan注解用法之包路徑占位符的示例分析

接下來我們就分析下,為什么在啟動類中添加@PropertySource注解,導入屬性資源,然后在@ComponentScan注解中使用占位符就沒有問題,而在非啟動類中添加@PropertySource導入外部配置資源,在@ComponentScan注解中使用占位符就會拋出異常。

上面說的啟動類其實也存在問題,更精準的描述應該是在調用Spring 應用上下文的refresh方法之前調用register方法時傳入的Class。

// ConfigurationClassParser#doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// 掃描到的 配置類是否添加了@Component注解,如果外部類添加了@Component注解,再處理內部類
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}
		// 處理所有的@PropertySource注解,由于@PropertySource被JDK1.8中的元注解@Repeatable所標注
		// 因此可以在一個類中添加多個
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			} else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		// 處理所有的@ComponentScan注解,@ComponentScan注解也被@Repeatable注解所標注
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		// 刪除其它與本次分析無關的代碼....
		// No superclass -> processing is complete
		return null;
	}

底層行為分析

要想完全搞明白這個問題,就必須清楚Spring 應用上下文關于Bean資源加載這一塊以及外部資源配置方面的邏輯,由于這一塊邏輯比較龐大,單篇文章很難說明白,這里就只分析產生以上錯誤的原因。

問題的根源就出在ConfigurationClassParser的doProcessConfigurationClass方法中,在該方法中,Spring是先處理@PropertySource注解,再處理@ComponentScan或者@ComponentScans注解,而Spring應用上下文最先處理的類是誰呢?

答案就是我們通過應用上下文的register方法注冊的類。當我們在register方法注冊的類上添加@PropertySource注解,那么沒問題,先解析@PropertySource注解導入的外部資源,接下來再解析@ComponentScan注解中的占位符,可以獲取到值。

當我們將@PropertySource注解添加到非register方法注冊的類時,由于是優先解析通過register方法注冊的類,再去解析@ComponentScan注解,發現需要處理占位符才能進行類路徑下的資源掃描,這時候就會使用Environment對象實例去解析。結果發現沒有Spring應用上下文中并沒有一個名為"basepackages"屬性,所以拋出異常。

// ConfigurationClassParser#doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// 掃描到的 配置類是否添加了@Component注解,如果外部類添加了@Component注解,再處理內部類
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}
		// 處理所有的@PropertySource注解,由于@PropertySource被JDK1.8中的元注解@Repeatable所標注
		// 因此可以在一個類中添加多個
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			} else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		// 處理所有的@ComponentScan注解,@ComponentScan注解也被@Repeatable注解所標注
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		// 刪除其它與本次分析無關的代碼....
		// No superclass -> processing is complete
		return null;
	}

那么除了將@PropertySource注解添加到通過register方法注冊類中(注意該方法的參數是可變參類型,即可以傳遞多個。如果傳遞多個,需要注意Spring 應用上下文解析它們的順序,如果順序不當也可能拋出異常),還有其它的辦法嗎?

答案是有的,這里先給出答案,通過-D參數來完成,或者編碼(設置到系統屬性中),以Idea為例,只需在VM options中添加-Dbasepackages=com.xxx.fame即可。

@ComponentScan注解用法之包路徑占位符的示例分析

或者在Spring應用上下文啟動之前(refresh方法執行之前),調用System.setProperty(String,String)方法手動將屬性以及屬性值設置進去。

package com.xxx.fame.placeholder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("${basepackages}")
public class ComponentScanPlaceholderDemo {
    public static void main(String[] args) {
    // 手動調用System#setProperty方法,設置 “basepackages”及其屬性值。
        System.setProperty("basepackages","com.xxx.fame");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ComponentScanPlaceholderDemo.class);
        context.refresh();
        ComponentBean componentBean = context.getBean(ComponentBean.class);
        System.out.println(componentBean);
    }
}

而這由延伸出來一個很有意思的問題,手動調用System的setProperty方法來設置“basepackages” 屬性,但同時也通過@PropertySource注解導入的外部資源中也指定相同的屬性名但不同值,接下來通過Environment對象實例來解析占位符“${basepackages}”,那么究竟是哪個配置生效呢?

@ComponentScan("${basepackages}")
@PropertySource("classpath:componentscan.properties")
public class ComponentScanPlaceholderDemo {
    public static void main(String[] args) {
        System.setProperty("basepackages","com.xxx.fame");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ComponentScanPlaceholderDemo.class);
        context.refresh();
        String value = context.getEnvironment().resolvePlaceholders("${basepackages}");
        System.out.println("${basepackages}占位符的值究竟是 com.xxx.fame 還是 com.unknow 呢? " + value);
    }
}
#在componentscan.properties指定相同的屬性名,但值不同
basepackages=com.unknow

接下來啟動應用上下文,執行結果如下:

@ComponentScan注解用法之包路徑占位符的示例分析

可以看到是通過System的setProperty方法設置的屬性值生效,具體原因這里就不再展開分析了,以后會詳細分析Spring中外部配置優先級問題,很有意思。

書歸正傳,Spring 應用上下文是如何解析占位符的呢?想要知道答案,只需要跟著ComponentScanParser的parse方法走即可,因為在Spring應用上下中是由該類來解析@ComponentScan注解并完成指定類路徑下的資源掃描和加載。

其實前面已經給出了答案,就是通過Environment實例的resolvePlaceHolders方法,但這里稍有不同的是resolvePlaceholders方法對于不能解析的占位符不會拋出異常,只會原樣返回,那么為什么前面拋出異常呢?答案是使用了另一個方法-resolveRequiredPalceholders方法。

在ComponentScanAnnotationParser方法中,首先創建了ClassPathBeanDefinitionScanner對象,顧名思義該類就是用來處理類路徑下的BeanDefinition資源掃描的(在MyBatis和Spring整合中,MyBatis就通過繼承該類來完成@MapperScan注解中指定包路徑下的資源掃描)。

由于@ComponentScan注解中的basepackages方法的返回值是一個數組,因此這里使用String類型的數組來接受,遍歷該數組,沒有每一個獲取到每一個包路徑,調用Environment對象的resolvePlaceholder方法來解析可能存在的占位符。由于resolvePlaceholders方法對于不能解析的占位符不會拋出異常,因此顯然問題不是出自這里(之所以要提出這一部分代碼,是想告訴大家,在@ComponentScan注解中可以使用占位符的,Spring是有處理的)。

在該方法的最后調用了ClassPathBeanDefinitionScanner的doScan方法,那么我們繼續往下追查,看哪里調用了Environment對象的resolveRequiredPlaceholders方法。

// ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
   ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
         componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
   // 刪除與本次分析無關的代碼.....
   Set<String> basePackages = new LinkedHashSet<>();
   String[] basePackagesArray = componentScan.getStringArray("basePackages");
   for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
   }
  // 刪除與本次分析無關的代碼.....
   return scanner.doScan(StringUtils.toStringArray(basePackages));
}

在ClassPathBeanDefinitionScanner的doScan方法中,遍歷傳入的basePackages(即用戶在@ComponentScan注解的basepackes方法中指定的資源路徑,也許有小伙伴疑惑為什么我明明沒有指定,只指定了其value方法,這涉及到Spring注解中的顯式引用,有興趣的小伙伴可以查看Spring 在Github項目上的Wiki),對于遍歷到的每一個basepackge,都調用findCandidateComponents方法來處理,由于該方法的返回值是集合,泛型是BeanDefinitionHolder,這意味著已經根據資源路徑完成了資源的掃描和加載,所以需要繼續往下追查。

// ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      // ...省略其它代碼
   }
   return beanDefinitions;
}

ClassPathBeanDefinitionScanner并沒有該方法,而是由其父類ClassPathScanningCandidateComponentProvider定義并實現。在findCandidateComponents方法中,首先判斷是否使用了索引,如果使用了索引則調用addCandidateComponentsFromIndex方法,否則調用scanCandidateComponents方法(索引是Spring為了減少應用程序的啟動時間,通過編譯器來在編譯期就確定那些類是需要IoC容器來進行管理的)。

// ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
   } else {
      return scanCandidateComponents(basePackage);
   }
}

由于并未使用索引,所以執行scanCandidateComponents方法。在該方法中,首先創建了一個Set集合用來存放BeanDefinition,重點接下來的資源路徑拼接,首先在資源路徑前面拼接上“classpath*:”,然后調用resolveBasePackage方法,傳入basePackage,最后拼接上“**/*.class”,看起來值的懷疑的地方只有這個resolveBasePackage方法了。

// ClassPathScanningCandidateComponentProvider#DEFAULT_RESOURCE_PATTERN
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
//ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      // 刪除與本次分析無關的代碼......
   } catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}

果不其然,在resolveBasePackage方法中,調用Environment實現類對象的resolveRequiredPlacehol-ders方法。

// ClassPathScanningCandidateComponentProvider#resolveBasePackage
protected String resolveBasePackage(String basePackage) {
   return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage));
}

最后我們分析下為什么resolvePlaceholders方法對于不能處理的占位符只會原樣返回,而resolveReq-uiredPlaceholders方法對于不能處理的占位符卻會拋出異常呢?

Environment實現類并不具備占位符解析能力,其只具有存儲外部配置以及查詢外部配置的能力,雖然也實現了PropertyResolver接口,這也是典型的裝飾者模式實現。

// AbstractEnvironment#resolvePlaceholders
public String resolvePlaceholders(String text) {
	return this.propertyResolver.resolvePlaceholders(text);
}
// AbstractEnvironment#resolveRequiredPlaceholders
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	return this.propertyResolver.resolveRequiredPlaceholders(text);
}

@ComponentScan注解用法之包路徑占位符的示例分析

AbstractProperyResolver實現了resolvePlaceholders方法以及resolveRequiredPlaceholders方法,不過能明顯看到都是委派給PropertyPlaceholderHelper類來完成占位符的解析。不過重點是在通過cr-eatePlaceholders方法創建PropertyPlaceholderHelper實例傳入的布爾值。

在resolvePlaceholders方法中調用createPlaceholderHelper方法時,傳入的布爾值為true,而在resolveRequiredPlaceholders方法中調用createPlaceholders方式時傳入的布爾值為false。

該值決定了PropertyPlaceholderHelper在面對無法解析的占位符時的行為。

@Nullable
private PropertyPlaceholderHelper nonStrictHelper;
	@Nullable
private PropertyPlaceholderHelper strictHelper;
// AbstractPropertyResolver#resolvePlaceholders
public String resolvePlaceholders(String text) {
   if (this.nonStrictHelper == null) {
      this.nonStrictHelper = createPlaceholderHelper(true);
   }
   return doResolvePlaceholders(text, this.nonStrictHelper);
}
// AbstractPropertyResolver#resolveRequiredPlaceholders
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
   if (this.strictHelper == null) {
      this.strictHelper = createPlaceholderHelper(false);
   }
   return doResolvePlaceholders(text, this.strictHelper);
}

在PropertyPlaceholderHelper的parseStringValue方法中(該方法就是Spring 應用上下文中解析占位符的地方(例如@Value注解中配置的占位符))。

在該方法中,對于無法解析的占位符,首先會判斷ignoreUnresolvablePlaceholders屬性是否為true,如果為true,則繼續嘗試解析,否則(else分支)就是拋出我們前面的看到的異常。

ingnoreUresolvablePlaceholder屬性的含義是代表是否需要忽略不能解析的占位符。

// PropertyPlaceholderHelper#parseStringValue
protected String parseStringValue(
      String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
   // 省略與本次分析無關代碼......
   StringBuilder result = new StringBuilder(value);
   while (startIndex != -1) {
      int endIndex = findPlaceholderEndIndex(result, startIndex);
      if (endIndex != -1) {
          // 省略與本次分析無關代碼......
         } else if (this.ignoreUnresolvablePlaceholders) {
            // Proceed with unprocessed value.
            startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
         } else {
            throw new IllegalArgumentException("Could not resolve placeholder '" +
                  placeholder + "'" + " in value \"" + value + "\"");
         }
         visitedPlaceholders.remove(originalPlaceholder);
      } else {
         startIndex = -1;
      }
   }
   return result.toString();
}

關于“@ComponentScan注解用法之包路徑占位符的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

革吉县| 灵石县| 宜阳县| 普安县| 且末县| 江油市| 光山县| 井冈山市| 乌兰浩特市| 咸宁市| 莱西市| 芜湖市| 高台县| 平湖市| 昌江| 和平县| 沙田区| 偃师市| 甘德县| 徐州市| 绿春县| 前郭尔| 南和县| 祁门县| 岱山县| 福安市| 衢州市| 巩义市| 昌乐县| 白水县| 砚山县| 东乌珠穆沁旗| 平阴县| 颍上县| 肃宁县| 高清| 宣化县| 收藏| 英德市| 农安县| 密云县|