目录
1、简介
Spring包
org.springframework.stereotype
下,除了@Component
、@Controller
、@Service
、@Repository
外,在5.0
版本中新增了@Indexed
注解。应用中使用
<context:component-scan />
或@ComponentScan
扫描的package
包含的类越来越多的时候,Spring启动时模式注解解析时间就会变得越长。@Indexed注解的引入正是为了解决这个问题,项目编译打包时,会在自动生成META-INF/spring.components文件,文件包含被@Indexed注释的类的模式解析结果。当Spring应用上下文进行组件扫描时,META-INF/spring.components会被org.springframework.context.index.CandidateComponentsIndexLoader读取并加载,转换为CandidateComponentsIndex对象,此时组件扫描会读取CandidateComponentsIndex,而不进行实际扫描,从而提高组件扫描效率,减少应用启动时间。
2、使用场景
在应用中有大量使用
@ComponentScan
扫描的package包含的类越多的时候,Spring模式注解解析耗时就越长。
3、使用方法
在项目中使用的时候需要导入一个
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 { }
4、原理说明
摘自官网:
简单说明一下:在项目中使用了@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);
}
}
}
5、使用需注意点
虽然这个@Indexed
注解能提升性能,但是在使用的时候也需要注意一一下。
假设Spring应用中存在一个包含META-INT/spring.components
资源的a.jar,b.jar仅存在模式注解,那么使用@ComponentScan
扫描这两个JAR中的package时,b.jar 中的模式注解不会被识别。
请务必注意这样的问题。
6、案例说明
使用时候存在上面的注意点,还是用一个简单的demo进行一下说明,能够更好的理解
6.1、DemoA项目(使用@Indexed注解
)
6.2、DemoB项目(不使用@Indexed注解
)
6.3、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,验证了上面提到的使用注意点。