广告
返回顶部
首页 > 资讯 > 后端开发 > Python >关于spring5的那些事:@Indexed 解密
  • 594
分享到

关于spring5的那些事:@Indexed 解密

2024-04-02 19:04:59 594人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录哪些资源会被索引?如何使用?原理随着云原生的发展,很多技术会被重新掂量,重新定义,历来技术的发展也是遵循天时地利,以其势尽享其利。再云原生下,jdk的最大的问题在于笨重(几百mb

随着云原生的发展,很多技术会被重新掂量,重新定义,历来技术的发展也是遵循天时地利,以其势尽享其利。再云原生下,jdk的最大的问题在于笨重(几百mb),启动慢,而像serverless架构nodejs技术栈可谓更完美。

其实在jdk9中倡导模块化本质在于减少JVM的体积,不需要资源(jar)不用再加载,而启动慢的问题其实也有解决方案GraalVM (一款类似于HotSpot VM),它的先进之处在于缩短运行的成本将.java文件直接编译成native code,而jvm则多了一个环节,首先将.java文件编译成字节码(.class),再借助JVM运行时JIT技术编译成native code。

spring5.0开始支持@Indexed来提升进应用启动速度,通过Annotation Processing Tools api在编译时来构建索引文件,本质是通过静态化来解决启动时Bean扫描加载的时间长的问题。

what is Annotation Processing Tools API?

不是什么黑科技,之前的系列也讲过,有点类似lombok。

哪些资源会被索引?

默认支持标记为Component及其派生注解(Controller、Repository、Service、Configuration等)的类,当然也可以是非spring bean(@Indexed修饰的类)。

注:如果已经是spring bean(Component修饰的类,并且Component已经被标记为@Indexed)了就没必要再标记@Indexed,否则索引文件会再追加一个相同的,感觉这是个bug

如何使用?

使用非常讲的,添加依赖就可以了,install后默认会生成一个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负责对符合条件的注解生成索引文件,整个源码也不是特别复杂,通过三个组件: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 {
	
	Set<String> getStereotypes(Element element);
}
//获取需要被索引的CandidateComponentsMetadata(元数据)
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;
	}    
}
//将上面的结果输出到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);
			}
		}
	}    
}    

原理

其实在Spring Boot项目中绝对存在ComponentScan(在SpringBootApplication中),而传统的spring项目中xml中对应<context:component-scan>,通过指定的 package(路径)来扫描注入spring bean,在扫描时通过读取spring.components文件来读取class(类全路径)从而达到提升速度的目的。

CandidateComponentsIndex存储了spring.components文件的内容


public class CandidateComponentsIndex {
	private static final AntPathMatcher pathMatcher = new AntPathMatcher(".");
	private final MultiValueMap<String, Entry> index;
	
	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时干最脏最累的活的终结者。而ClassPathScanninGCandidateComponentProvider非常重要可以视为scan的顶级实现类。

其中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;//这个之前讲过类元数据读取
	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你一定不陌生吧,这可是开发用户级的API,其实它的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);
	}
}   

其实关于@Indexed个人觉得实现上还是有一定局限性(只是针对当前Maven的一个module,换言之是基于jar的),要基于当前整个工程文件特别是org.springframework包(这个下面有很多待加载到ioc的bean的jar)工作量还是不少的,官方还没考虑吧。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: 关于spring5的那些事:@Indexed 解密

本文链接: https://www.lsjlt.com/news/156785.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 关于spring5的那些事:@Indexed 解密
    目录哪些资源会被索引?如何使用?原理随着云原生的发展,很多技术会被重新掂量,重新定义,历来技术的发展也是遵循天时地利,以其势尽享其利。再云原生下,jdk的最大的问题在于笨重(几百mb...
    99+
    2022-11-12
  • 关于 sudo 的那些事儿
    觉得你已经了解了 sudo 的所有知识了吗?再想想。大家都知道 sudo,对吗?默认情况下,该工具已安装在大多数 Linux 系统上,并且可用于大多数 BSD 和商业 Unix 变体。不过,在与数百名 ...
    99+
    2023-06-05
  • 详解关于spring bean名称命名的那些事
    目录前言02源码查看01从main方法直接调试断点02带着问题查看,靠猜加验证的方式03源码验证04总结前言 用了多年spring,一直想当然把spring默认的beanName当成...
    99+
    2022-11-12
  • 浅谈React Router关于history的那些事
    如果你想理解React Router,那么应该先理解history。更确切地说,是history这个为React Router提供核心功能的包。它能轻松地在客户端为项目添加基于loc...
    99+
    2022-11-12
  • 关于索引我能说的那些事儿
    本文是自己对MySQL的InnoDB索引的理解,如有错误,还望不吝指出。 1 索引   索引两个大字往那里一摆,刚接触不久的朋友可能对这个概念有点陌生,不好理解。没有关系,先用一个简单的例子入手,比方说现在我们要从一本字典中查...
    99+
    2015-01-11
    关于索引我能说的那些事儿
  • 关于pt-archiver和自增主键的那些事
    目录前言分析解析结论本文Percona Blog 的译文,原文移步文章末尾的 阅读原文。 前言 pt-archiver 是一款常见的 表清理或者归档工具。 MySQL 中删除大表之前...
    99+
    2022-11-13
  • 关于vue中api统一管理的那些事
    目录前情提要针对小项目而言(没有单独二次封装axios)无需管理,直接干。仅限于接口数量在20-30的统一api.js文件管理针对非小型项目而言(进行axios的二次封装)api统一...
    99+
    2022-11-13
  • 关于iOS自适应cell行高的那些事儿
    前言 其实早就准备写这篇文章了,但是一直没有系统去整理一下相关的demo,加上最近离职了,各种事情忙的有点郁闷,所以一直拖沓了下来。回家休息了一段时间想起来写了一半的demo,在还...
    99+
    2022-05-31
    cell 自适应 行高
  • Java基础7:关于Java类和包的那些事
    更多内容请关注微信公众号【Java技术江湖】这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、EL...
    99+
    2023-06-02
  • 关于C语言操作符的那些事(超级全)
    目录前言操作符的分类移位操作符位操作符赋值操作符单目操作符关系操作符逻辑操作符条件操作符逗号表达式下标引用符函数调用符结构体调用操作符总结前言 C语言中操作符不多,但是有些相同的操作...
    99+
    2022-11-12
  • 详细聊聊关于Mysql联合查询的那些事儿
    目录联合查询之union 1. 查询中国各省的ID以及省份名称 2. 湖南省所有地级市ID、名字 3. 用union将他们合并 联合查询之union a...
    99+
    2022-11-12
  • Tungsten Fabric入门宝典丨关于安装的那些事(下)
    作者:Tatsuya Naganawa  译者:TF编译组Tungsten Fabric入门宝典系列文章,来自技术大牛倾囊相授的实践经验,由TF中文社区为您编译呈现,旨在帮助新手深入理解TF的运行、安装、集成、调试等全流程。如果您...
    99+
    2023-06-03
  • 关于mybatis的一级缓存和二级缓存的那些事儿
    目录一、缓存是什么二、为什么需要缓存三、哪些数据会放到缓存四、mybatis一级缓存五、二级缓存六、注意事项一、缓存是什么 缓存其实就是存储在内存中的临时数据,这里的数据量会比较小,...
    99+
    2022-11-12
  • 关于Spring中一级缓存、二级缓存和三级缓存的那些事
    目录题记缓存作用分析一级缓存、二级缓存、三级缓存区别是什么总结题记 常常听到别人提起:“一级缓存、二级缓存、三级缓存”。那么它们是什么呢?有什么作用呢? 缓存...
    99+
    2022-11-13
  • 关于Redis你可能不了解的一些事
    引子 Redis 是一个高性能分布式的key-value数据库。它支持多种数据结构,并可应用于缓存、队列等多种场景下。使用过Redis的小伙伴们可能对这些已经非常熟知了,下面我想来谈谈Redis也许并不被...
    99+
    2022-10-18
  • 这些关于Go中interface{}的注意事项你都了解吗
    目录interface{} 注意断言用于反射的注意点我们一起来回顾一下上一次说到的 interface{} 可以用来做多态接口类型分为空接口类型和非空接口类型,他们的底层数...
    99+
    2023-03-08
    Go interface{}注意事项 Go interface{} Go interface
  • iOS自动化测试的那些干货:关于appium启动报错问题的解决办法
    我们在进行iOS Appium自动化测试的时候,会遇到环境配置、兼容使用问题,这里做个总结,以避免后续踩着这些坑。问题1. 提示no module “appium”解决:第1步,在终端输入命令cd /usr/local/binpip3 in...
    99+
    2023-06-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作