本文轉載自: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.jar和DemoB.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.componentsis 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 settingspring.index.ignoretotrue, either as a system property or in aspring.propertiesfile at the root of the classpath.
