關(guān)于spring5的那些事:@Indexed 解密
隨著云原生的發(fā)展,很多技術(shù)會(huì)被重新掂量,重新定義,歷來(lái)技術(shù)的發(fā)展也是遵循天時(shí)地利,以其勢(shì)盡享其利。再云原生下,jdk的最大的問(wèn)題在于笨重(幾百mb),啟動(dòng)慢,而像Serverless架構(gòu),NodeJS技術(shù)??芍^更完美。
其實(shí)在jdk9中倡導(dǎo)模塊化本質(zhì)在于減少JVM的體積,不需要資源(Jar)不用再加載,而啟動(dòng)慢的問(wèn)題其實(shí)也有解決方案GraalVM (一款類似于HotSpot VM),它的先進(jìn)之處在于縮短運(yùn)行的成本將.java文件直接編譯成native code,而jvm則多了一個(gè)環(huán)節(jié),首先將.java文件編譯成字節(jié)碼(.class),再借助JVM運(yùn)行時(shí)JIT技術(shù)編譯成native code。
spring5.0開(kāi)始支持@Indexed來(lái)提升進(jìn)應(yīng)用啟動(dòng)速度,通過(guò)Annotation Processing Tools API在編譯時(shí)來(lái)構(gòu)建索引文件,本質(zhì)是通過(guò)靜態(tài)化來(lái)解決啟動(dòng)時(shí)Bean掃描加載的時(shí)間長(zhǎng)的問(wèn)題。
what is Annotation Processing Tools API?
不是什么黑科技,之前的系列也講過(guò),有點(diǎn)類似lombok。
哪些資源會(huì)被索引?
默認(rèn)支持標(biāo)記為Component及其派生注解(Controller、Repository、Service、Configuration等)的類,當(dāng)然也可以是非spring bean(@Indexed修飾的類)。
注:如果已經(jīng)是spring bean(Component修飾的類,并且Component已經(jīng)被標(biāo)記為@Indexed)了就沒(méi)必要再標(biāo)記@Indexed,否則索引文件會(huì)再追加一個(gè)相同的,感覺(jué)這是個(gè)bug
如何使用?
使用非常講的,添加依賴就可以了,install后默認(rèn)會(huì)生成一個(gè)META-INF/spring.components。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
#spring.components com.yh.rfe.lucky.day.service.impl.BasCostReportServiceImpl=org.springframework.stereotype.Component com.yh.rfe.lucky.day.service.impl.BasShopRuleDetailServiceImpl=org.springframework.stereotype.Component
而CandidateComponentsIndexer負(fù)責(zé)對(duì)符合條件的注解生成索引文件,整個(gè)源碼也不是特別復(fù)雜,通過(guò)三個(gè)組件:StereotypesProvider、MetadataCollector、MetadataStore來(lái)完成。
public class CandidateComponentsIndexer implements Processor {
@Override
public synchronized void init(ProcessingEnvironment env) {
this.stereotypesProviders = getStereotypesProviders(env);
this.typeHelper = new TypeHelper(env);
this.metadataStore = new MetadataStore(env);
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.metadataCollector.processing(roundEnv);
roundEnv.getRootElements().forEach(this::processElement);
if (roundEnv.processingOver()) {
writeMetaData();
}
return false;
}
}
//定義了哪些注解需要被索引
interface StereotypesProvider {
/**
* Return the stereotypes that are present on the given {@link Element}.
* @param element the element to handle
* @return the stereotypes or an empty set if none were found
*/
Set<String> getStereotypes(Element element);
}
//獲取需要被索引的CandidateComponentsMetadata(元數(shù)據(jù))
class MetadataCollector {
public CandidateComponentsMetadata getMetadata() {
CandidateComponentsMetadata metadata = new CandidateComponentsMetadata();
for (ItemMetadata item : this.metadataItems) {
metadata.add(item);
}
if (this.previousMetadata != null) {
List<ItemMetadata> items = this.previousMetadata.getItems();
for (ItemMetadata item : items) {
if (shouldBeMerged(item)) {
metadata.add(item);
}
}
}
return metadata;
}
}
//將上面的結(jié)果輸出到spring.components中
class MetadataStore {
static final String METADATA_PATH = "META-INF/spring.components";
public void writeMetadata(CandidateComponentsMetadata metadata) throws IOException {
if (!metadata.getItems().isEmpty()) {
try (OutputStream outputStream = createMetadataResource().openOutputStream()) {
PropertiesMarshaller.write(metadata, outputStream);
}
}
}
}
原理
其實(shí)在spring boot項(xiàng)目中絕對(duì)存在ComponentScan(在SpringBootApplication中),而傳統(tǒng)的spring項(xiàng)目中xml中對(duì)應(yīng)<context:component-scan>,通過(guò)指定的 package(路徑)來(lái)掃描注入spring bean,在掃描時(shí)通過(guò)讀取spring.components文件來(lái)讀取class(類全路徑)從而達(dá)到提升速度的目的。
CandidateComponentsIndex存儲(chǔ)了spring.components文件的內(nèi)容
public class CandidateComponentsIndex {
private static final AntPathMatcher pathMatcher = new AntPathMatcher(".");
private final MultiValueMap<String, Entry> index;
/*返回指定的注解類型和包路徑相關(guān)候選類型
* Set<String> candidates = index.getCandidateTypes("com.example", "org.springframework.stereotype.Component");
*/
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
List<Entry> candidates = this.index.get(stereotype);
if (candidates != null) {
return candidates.parallelStream()
.filter(t -> t.match(basePackage))
.map(t -> t.type)
.collect(Collectors.toSet());
}
return Collections.emptySet();
}
}
CandidateComponentsIndexLoader從classloader中讀取,可以從多個(gè)jar中讀取多個(gè)索引文件。
public final class CandidateComponentsIndexLoader {
public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache =
new ConcurrentReferenceHashMap<>();
@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);
}
}
}
ClassPathBeanDefinitionScanner非常重要,它就是spring 中scan時(shí)干最臟最累的活的終結(jié)者。而ClassPathScanningCandidateComponentProvider非常重要可以視為scan的頂級(jí)實(shí)現(xiàn)類。

其中ClassPathMapperScanner是mybatis的mapper掃描類。
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//看這里吧
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
private MetadataReaderFactory metadataReaderFactory;//這個(gè)之前講過(guò)類元數(shù)據(jù)讀取
private CandidateComponentsIndex componentsIndex;//前面講過(guò)
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
Set<String> types = new HashSet<>();
for (TypeFilter filter : this.includeFilters) {
String stereotype = extractStereotype(filter);
if (stereotype == null) {
throw new IllegalArgumentException("Failed to extract stereotype from "+ filter);
}
types.addAll(index.getCandidateTypes(basePackage, stereotype));
}
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (String type : types) {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
if (isCandidateComponent(metadataReader)) {
AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition(
metadataReader.getAnnotationMetadata());
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Using candidate component class from index: " + type);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + type);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because matching an exclude filter: " + type);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
}
AnnotationConfigApplicationContext#scan你一定不陌生吧,這可是開(kāi)發(fā)用戶級(jí)的API,其實(shí)它的scanner就是ClassPathBeanDefinitionScanner
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
public void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
this.reader.register(annotatedClasses);
}
}
其實(shí)關(guān)于@Indexed個(gè)人覺(jué)得實(shí)現(xiàn)上還是有一定局限性(只是針對(duì)當(dāng)前maven的一個(gè)module,換言之是基于jar的),要基于當(dāng)前整個(gè)工程文件特別是org.springframework包(這個(gè)下面有很多待加載到ioc的bean的jar)工作量還是不少的,官方還沒(méi)考慮吧。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mybatis分頁(yè)查詢的實(shí)現(xiàn)(Rowbounds和PageHelper)
本文主要介紹了Mybatis分頁(yè)查詢的實(shí)現(xiàn)(Rowbounds和PageHelper),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
springboot 加載 META-INF/spring.factories方式
這篇文章主要介紹了springboot 加載 META-INF/spring.factories方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot項(xiàng)目mapper無(wú)法自動(dòng)裝配未找到?UserMapper?類型的Bean解決辦法
這篇文章給大家介紹了springboot項(xiàng)目mapper無(wú)法自動(dòng)裝配,未找到?‘userMapper‘?類型的?Bean解決辦法(含報(bào)錯(cuò)原因),文章通過(guò)圖文結(jié)合的方式介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-02-02
JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(3)
這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
又一波Java專業(yè)人士必備書(shū)籍來(lái)襲
又一波Java專業(yè)人士必備書(shū)籍來(lái)襲,這篇文章主要向大家推薦了Java專業(yè)人士必讀的書(shū),感興趣的小伙伴們不要錯(cuò)過(guò)2016-09-09

