關(guān)于spring5的那些事:@Indexed 解密
隨著云原生的發(fā)展,很多技術(shù)會被重新掂量,重新定義,歷來技術(shù)的發(fā)展也是遵循天時地利,以其勢盡享其利。再云原生下,jdk的最大的問題在于笨重(幾百mb),啟動慢,而像Serverless架構(gòu),NodeJS技術(shù)??芍^更完美。
其實(shí)在jdk9中倡導(dǎo)模塊化本質(zhì)在于減少JVM的體積,不需要資源(Jar)不用再加載,而啟動慢的問題其實(shí)也有解決方案GraalVM (一款類似于HotSpot VM),它的先進(jìn)之處在于縮短運(yùn)行的成本將.java文件直接編譯成native code,而jvm則多了一個環(huán)節(jié),首先將.java文件編譯成字節(jié)碼(.class),再借助JVM運(yùn)行時JIT技術(shù)編譯成native code。
spring5.0開始支持@Indexed來提升進(jìn)應(yīng)用啟動速度,通過Annotation Processing Tools API在編譯時來構(gòu)建索引文件,本質(zhì)是通過靜態(tài)化來解決啟動時Bean掃描加載的時間長的問題。
what is Annotation Processing Tools API?
不是什么黑科技,之前的系列也講過,有點(diǎn)類似lombok。
哪些資源會被索引?
默認(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)了就沒必要再標(biāo)記@Indexed,否則索引文件會再追加一個相同的,感覺這是個bug
如何使用?
使用非常講的,添加依賴就可以了,install后默認(rèn)會生成一個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é)對符合條件的注解生成索引文件,整個源碼也不是特別復(fù)雜,通過三個組件:StereotypesProvider、MetadataCollector、MetadataStore來完成。
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)目中絕對存在ComponentScan(在SpringBootApplication中),而傳統(tǒng)的spring項(xiàng)目中xml中對應(yīng)<context:component-scan>,通過指定的 package(路徑)來掃描注入spring bean,在掃描時通過讀取spring.components文件來讀取class(類全路徑)從而達(dá)到提升速度的目的。
CandidateComponentsIndex存儲了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中讀取,可以從多個jar中讀取多個索引文件。
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時干最臟最累的活的終結(jié)者。而ClassPathScanningCandidateComponentProvider非常重要可以視為scan的頂級實(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;//這個之前講過類元數(shù)據(jù)讀取 private CandidateComponentsIndex componentsIndex;//前面講過 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你一定不陌生吧,這可是開發(fā)用戶級的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個人覺得實(shí)現(xiàn)上還是有一定局限性(只是針對當(dāng)前maven的一個module,換言之是基于jar的),要基于當(dāng)前整個工程文件特別是org.springframework包(這個下面有很多待加載到ioc的bean的jar)工作量還是不少的,官方還沒考慮吧。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mybatis分頁查詢的實(shí)現(xiàn)(Rowbounds和PageHelper)
本文主要介紹了Mybatis分頁查詢的實(shí)現(xiàn)(Rowbounds和PageHelper),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01springboot 加載 META-INF/spring.factories方式
這篇文章主要介紹了springboot 加載 META-INF/spring.factories方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10springboot項(xiàng)目mapper無法自動裝配未找到?UserMapper?類型的Bean解決辦法
這篇文章給大家介紹了springboot項(xiàng)目mapper無法自動裝配,未找到?‘userMapper‘?類型的?Bean解決辦法(含報(bào)錯原因),文章通過圖文結(jié)合的方式介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2024-02-02JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(3)
這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08