本文轉載自: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.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 settingspring.index.ignore
totrue
, either as a system property or in aspring.properties
file at the root of the classpath.