紙上得來終覺淺 絕知此事要躬行 —陸游
最近在看SpringBoot核編程思想(核心篇),看到走向注解驅動編程這章,里面有講解到:
在SpringFramework5.0引入了一個注解@Indexed
,它可以為Spring的模式注解添加索引,以提升應用啟動性能。
在往下閱讀的時候,請注意一些模式注解:
Spring注解 | 場景說明 |
---|---|
@Repository | 數據倉庫模式注解 |
@Component | 通用組件模式注解 |
@Service | 服務模式注解 |
@Controller | Web控制器模式注解 |
@Configuration | 配置類模式注解 |
@
使用場景
在應用中有大量使用@ComponentScan
掃描的package包含的類越多的時候,Spring模式注解解析耗時就越長。
使用方法
在項目中使用的時候需要導入一個spring-context-indexer
jar包,有Maven和Gradle 兩種導入方式,具體可以看官網,我這里使用maven方式,引入jar配置如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.1.12.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
然后在代碼中,對於使用了模式注解的類上加上@Indexed
注解即可。如下:
@Indexed
@Controller
public class HelloController {
}
原理說明
摘自官網:
簡單說明一下:在項目中使用了@Indexed
之后,編譯打包的時候會在項目中自動生成META-INT/spring.components
文件。
當Spring應用上下文執行ComponentScan
掃描時,META-INT/spring.components
將會被CandidateComponentsIndexLoader
讀取並加載,轉換為CandidateComponentsIndex
對象,這樣的話@ComponentScan
不在掃描指定的package,而是讀取CandidateComponentsIndex
對象,從而達到提升性能的目的。
知道上面的原理,可以看一下org.springframework.context.index.CandidateComponentsIndexLoader
的源碼。
public class CandidateComponentsIndexLoader {
/**
* The location to look for components.
* <p>Can be present in multiple JAR files.
*/
public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
// 省略了的代碼......
@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();
// 轉換為CandidateComponentsIndex對象
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 中的模式注解不會被識別。
請務必注意這樣的問題。
案例說明
使用時候存在上面的注意點,還是用一個簡單的demo進行一下說明,能夠更好的理解。
-
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,驗證了上面提到的使用注意點。
參考資料
歡迎關注我的公眾號,技術路,一起成長。