@Indexed 注解


本文轉載自:https://www.cnblogs.com/aflyun/p/11992101.html

最近在看 SpringBoot 核編程思想(核心篇),看到走向注解驅動編程這章,里面有講解到:在SpringFramework 5.0 引入了一個注解@Indexed ,它可以為 Spring 的模式注解添加索引,以提升應用啟動性能。

在往下閱讀的時候,請注意一些模式注解:

Spring注解 場景說明
@Repository 數據倉庫模式注解
@Component 通用組件模式注解
@Service 服務模式注解
@Controller Web控制器模式注解
@Configuration 配置類模式注解

使用場景

在應用中使用@ComponentScan掃描 package 時,如果 package 中包含很多的類,那么 Spring 啟動的時候就會變慢。

提升性能的一個方案就是提供一個 Component 的候選列表,Spring 啟動時直接掃描注入這些列表就行了,而不需要一個個類去掃描,再篩選出候選 Component。

需要注意的是:在這種模式下,所有組件掃描的目標模塊都必須使用這種機制——大白話將就所有的 Component 組件都必須生成到列表文件中去。

While classpath scanning is very fast, it is possible to improve the startup performance of large applications by creating a static list of candidates at compilation time. In this mode, all modules that are target of component scan must use this mechanism.

使用方式

在項目中使用的時候需要導入一個spring-context-indexer jar包。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>xxxx</version>
        <optional>true</optional>
    </dependency>
</dependencies>

然后在代碼中,對於使用了模式注解的類上加上@Indexed注解即可。如下:

@Indexed
@Controller
public class HelloController {

}

加了上面的依賴后,項目就會自動使用索引的方式啟動Spring。

原理說明

摘自官網:
在這里插入圖片描述

簡單說明一下:在項目中使用了@Indexed之后,編譯打包的時候會在項目中自動生成META-INT/spring.components文件。

當Spring應用上下文執行ComponentScan掃描時,META-INT/spring.components將會被CandidateComponentsIndexLoader 讀取並加載,轉換為CandidateComponentsIndex對象,這樣的話@ComponentScan不在掃描指定的package,而是讀取CandidateComponentsIndex對象,從而達到提升性能的目的。

知道上面的原理,可以看一下org.springframework.context.index.CandidateComponentsIndexLoader的源碼。

ublic final class CandidateComponentsIndexLoader {

	public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";

    public static final String IGNORE_INDEX = "spring.index.ignore";


	private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX);

	private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class);

	private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache =
			new ConcurrentReferenceHashMap<>();


	private CandidateComponentsIndexLoader() {
	}

    @Nullable
	public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader();
		}
		return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex);
	}

	@Nullable
	private static CandidateComponentsIndex doLoadIndex(ClassLoader classLoader) {
		if (shouldIgnoreIndex) {
			return null;
		}

		try {
			Enumeration<URL> urls = classLoader.getResources(COMPONENTS_RESOURCE_LOCATION);
			if (!urls.hasMoreElements()) {
				return null;
			}
			List<Properties> result = new ArrayList<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
				result.add(properties);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + result.size() + "] index(es)");
			}
			int totalCount = result.stream().mapToInt(Properties::size).sum();
			return (totalCount > 0 ? new CandidateComponentsIndex(result) : null);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Unable to load indexes from location [" +
					COMPONENTS_RESOURCE_LOCATION + "]", ex);
		}
	}

}

使用注意點

雖然這個@Indexed注解能提升性能,但是在使用的時候也需要注意下。

假設Spring應用中存在一個包含META-INT/spring.components資源的a.jar,但是 b.jar 僅存在模式注解,那么使用@ComponentScan掃描這兩個JAR中的package時,b.jar 中的模式注解不會被識別,請務必注意這樣的問題。

舉個列子說明下,能夠更好的理解。

  • DemoA項目(使用@Indexed注解
    在這里插入圖片描述

  • DemoB項目(不使用@Indexed注解)

    在這里插入圖片描述

  • SpringBootDemo項目
    在此項目中引入DemoA.jarDemoB.jar 。然后進行如下測試,測試代碼如下:

配置類,掃描模式注解

@Configuration
@ComponentScan(basePackages = "org.springboot.demo")
public class SpringIndexedConfiguration {
}

測試類:

@Test
public void testIndexedAnnotation(){

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringIndexedConfiguration.class);

    System.out.println("獲取DemoA Jar中【org.springboot.demo.controller.DemoAController】");
    DemoAController demoAController = context.getBean(DemoAController.class);
    System.out.println("DemoAController = " + demoAController.getClass());

    System.out.println("獲取DemoB Jar中【org.springboot.demo.controller.DemoBController】");
    DemoBController demoBController = context.getBean(DemoBController.class);
    System.out.println("DemoBController = " + demoBController.getClass());
}

結果:

beanDefinitionName = demoAController
獲取DemoA Jar中【org.springboot.demo.controller.DemoAController】
DemoAController = class org.springboot.demo.controller.DemoAController
獲取DemoB Jar中【org.springboot.demo.controller.DemoBController】

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springboot.demo.controller.DemoBController' available

找不到 DemoBController

通過這樣一個簡單的Demo,驗證了上面提到的使用注意點。

對於這種情況,Spring 官網提示了配置相關屬性,不再使用index方式啟動。要是這樣的話,我們完全可以不添加spring-context-indexer 依賴,這樣整體就不會使用index模式了。

The index is enabled automatically when a META-INF/spring.components is found on the classpath. If an index is partially available for some libraries (or use cases) but could not be built for the whole application, you can fallback to a regular classpath arrangement (as though no index was present at all) by setting spring.index.ignore to true, either as a system property or in a spring.properties file at the root of the classpath.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM