Spring IOC - ConfigurationClassPostProcessor源码解析

        上文提到Spring在Bean扫描过程中,会手动将5个Processor类注册到beanDefinitionMap中,其中ConfigurationClassPostProcessor就是本文将要讲解的内容,该类会在refresh()方法中通过调用invokeBeanFactoryPosstProcessors(beanFactory)被调用。
        5个Processor类列表如下:
类名
是否BeanDefinitionRegistryPostProcessor
是否BeanFactoryPostProcessor
是否BeanPostProcessor
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
EventListenerMethodProcessor
DefaultEventListenerFactory
        ConfigurationClassPostProcessor中两个核心方法将被调用,其主要作用如下:

方法名

作用

postProcessBeanDefinitionRegistry()

(1)@Conditional注解条件解析

(2)被以下注解标注的类及内部类

@Component、@PropertySources、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean、接口中的@Bean

postProcessBeanFactory()

对full模式的配置类BeanDefinition进行CGLib增强:把CGLib生成的子类型设置到beanDefinition中

        postProcessBeanDefinitionRegistry()源码及注释如下:


	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);
		// 执行解析和扫描
		processConfigBeanDefinitions(registry);
	}
        进入processConfigBeanDefinitions(registry)方法,其主体逻辑流程图如下:
        
        其中标红步骤为两个关键步骤:

        1. 通过beanDefinition判断是否为配置类

        Spring专门提供了一个工具类:ConfigurationClassUtils来检查是否为配置类,并标记full、lite模式,逻辑如下:

        2. 解析配置类
        doProcessConfigurationClass即为真正的对个注解的解析方法,其逻辑流程图如下:

        具体的源码及详细注释如下:

        1. 主流程processConfigBeanDefinitions方法:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		// 候选配置类定义列表
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 获取容器中所有bean定义的名字
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			// 获取bean定义
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			// org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
			// Bean Definition是否已经被标记为配置类(full、lite模式)
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			// 查看bean是否是ConfigurationClass
			//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				// 是配置类,则加入到候选列表
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		// 如果为空,即找不到被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记的类,则立即返回
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		// 对需要处理的BeanDefinition排序,值越小越靠前
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		// 检查是否有自定义的beanName生成器
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				//获取自定义的beanName生成器
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					// 如果spring有自定义的beanName生成器,则重新赋值
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		//如果环境对象为空,则创建新的环境对象
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// 构造一个配置类解析器,用来把bean定义的重要信息提取转化为ConfigurationClass
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		// 候选配置类定义列表(查重)
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// 存储已解析的配置类列表
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 解析配置类
			parser.parse(candidates);
			// 配置类不能被申明为final(因为CGLIB的限制),除非它声明proxyBeanMethods=false
			// 在@Configuration类中,@Bean方法的实例必须是可重写的,以适应CGLIB
			parser.validate();

			// 获取解析器中解析出的配置类ConfigurationClass: parser.getConfigurationClasses()
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			// 过滤掉已解析的配置类
			configClasses.removeAll(alreadyParsed);

			// 构造一个bean定义读取器
			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// 读取ConfigurationClass获取衍生bean定义并注册到容器
			// 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器
			this.reader.loadBeanDefinitions(configClasses);
			// 加入已解析配置类
			alreadyParsed.addAll(configClasses);
			// 清空候选配置类定义列表
			candidates.clear();
			// 如果容器中bean定义有新增
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				// 查找出新增的配置类bean定义 start
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		// 如果想要在@Configuration类中使用ImportAware接口,需要将ImportRegistry注册为bean。这样,@Configuration类就可以
		// 使用ImportRegistry来获取导入的类信息
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			// 如果MetadataReaderFactory是由外部提供的,则需要清楚其缓存。但是,如果MetadataReaderFactory是由ApplicationContext共享的,则不需要进行清除,
			// 因为ApplicationContext会自动清楚共享缓存
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

        2. 其中检查是否为配置类方法ConfigurationClassUtils.checkConfigurationClassCandidate

//检查给定的BeanDefinition是否是一个配置类的候选者(或一个在配置/组件类中声明的嵌套组件类)
	//并对其进行相应的标记处理
	//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记
	public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

		//获取bean定义信息中的class类名
		String className = beanDef.getBeanClassName();
		//如果className为空,或者bean定义信息中的factoryMethod不等于空,那么直接返回
		if (className == null || beanDef.getFactoryMethodName() != null) {
			return false;
		}

		AnnotationMetadata metadata;
		//通过注解注入的BeanDefinition都是AnnotatedGenericBeanDefinition,实现了AnnotatedBeanDefinition
		//Spring内部的BeanDefinition都是RootBeanDefiniton,实现了AbstractBeanDefinition
		//此处主要用于判断是否是归属于AnnotatedBeanDefinition
		if (beanDef instanceof AnnotatedBeanDefinition &&
				className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
			// Can reuse the pre-parsed metadata from the given BeanDefinition...
			// 从当前bean的定义信息中获取元素
			metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
		}
		//判断是否是Spring中默认的BeanDefinition
		else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
			// Check already loaded Class if present...
			// since we possibly can't even load the class file for this Class.
			//获取当前bean对象的Class对象
			Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
			//如果class实例是下面4种类或者接口的子类,父接口等任何一种情况,直接返回
			if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
					BeanPostProcessor.class.isAssignableFrom(beanClass) ||
					AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
					EventListenerFactory.class.isAssignableFrom(beanClass)) {
				return false;
			}
			//为给定类创建新的AnnotationMetadata
			metadata = AnnotationMetadata.introspect(beanClass);
		}
		//如果上述两种情况都不符合
		else {
			try {
				//获取className的MetadataReader实例
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
				//读取底层类的完整注解元数据,包括带注解方法的元数据
				metadata = metadataReader.getAnnotationMetadata();
			}
			catch (IOException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not find class file for introspecting configuration annotations: " +
							className, ex);
				}
				return false;
			}
		}
		// 获取Bean Definition的元数据被@Configuration注解标注的属性字典值
		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		// 如果bean被@Configuration注解标注,并且proxyBeanMethods属性的值为true,则标注当前配置类为full,即需要代理增强,为一个代理类
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		// 如果存在,或者isConfigurationCandidate返回true,则标注当前配置类为lite,即不需要代理增强,为一个普通类
		// 标注了Configuration注解且proxyBeanMethods属性的值为false,则为lite模式
		// 没有标注Configuration注解,但是标注了Component、ComponentScan、Import、ImportResource、@Bean注解、则为lite模式
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		// 组件类不存在@Configuration注解,直接返回false
		else {
			return false;
		}

		// 获取配置类的排序顺序,设置到组件定义属性中
		// It's a full or lite configuration candidate... Let's determine the order value, if any.
		//Bean Definition是一个标记为full/lite的候选项,如果有order属性就设置order属性
		Integer order = getOrder(metadata);
		if (order != null) {
			//设置bean的order值
			beanDef.setAttribute(ORDER_ATTRIBUTE, order);
		}

		return true;
	}

        3. 真正干活的方法doProessConfigurationClass

// 通过读取源类的注释、成员和方法来构建完整的ConfigurationClass
	// 1.处理@Component
	// 2.处理每个@PropertySource
	// 3.处理每个@ComponentScan
	// 4.处理每个Impoit
	// 5.处理每个@ImportResource
	// 6.找配置类中@Bean注解的方法(找出Class中存在@Bean标注的方法,加入到ConfigurationClass的beanMethods属性)
	// 7.找接口中@Bean注解的方法
	// 8.存在父类则递归处理
	@Nullable
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			// 递归处理嵌套的成员类
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
		// 处理@PropertySources和@PropertySource注解
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
		// 处理@ComponentScans和@ComponentScan注解
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				// 配置类使用了注解@ComponentScan -> 立即执行扫描。
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
		// 处理@Import注解
		// getImports:递归获取@Import导入的类
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		// Process any @ImportResource annotations
		// 处理@ImportResource注解
		// 读取locations, reader属性,封装存放在importResources map集合中
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		// 处理@Bean方法
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		// 处理接口的默认方法实现,从jdk8开始,接口中的方法可以有自己的默认实现,因此如果这个接口的方法加了@Bean注解,也需要被解析
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/111680.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(11月4日)GBASE南大通用 x openGauss Meetup,欢迎报名

由openGauss社区、天津南大通用数据技术股份有限公司主办&#xff0c;伟仕佳杰科技有限公司、神州数码&#xff08;中国&#xff09;有限公司协办的“GBASE南大通用 x openGauss Meetup”活动将于2023年11月4日&#xff08;周六&#xff09;在合肥市高新区云飞路66号天源迪科科…

resource manager OCB structure(iofunc_ocb_t) 扩展实例

文章目录 前言一、OCB structure(iofunc_ocb_t) 是什么二、OCB structure(iofunc_ocb_t) 扩展实例1.OCB structure(iofunc_ocb_t) 扩展后的使用实例总结参考资料前言 本文主要介绍如何对qnx系统下的resource manager OCB structure(iofunc_ocb_t) 数据结构进行扩展 软件环境:…

ts 简易封装 axios,统一 API

文章目录 为什么要封装目标文件结构封装通用请求方法获得类型提示http 方法文件上传使用示例实例化post 请求类型提示文件上传 总结完整代码&#xff1a; 为什么要封装 axios 本身已经很好用了&#xff0c;看似多次一举的封装则是为了让 axios 与项目解耦。比如想要将网络请求…

JVM堆内存解析

一、JVM堆内存介绍 Java大多数对象都是存放在堆中&#xff0c;堆内存是完全自动化管理&#xff0c;根据垃圾回收机制不同&#xff0c;Java堆有不同的结构&#xff0c;下面是我们一台生产环境服务器JVM堆内存空间分配情况&#xff0c;JVM只设置了-Xms2048M -Xmx2048M。 1、JVM堆…

软件开发项目文档系列之八数据库设计说明书

数据库设计说明书是一个关键文档&#xff0c;它提供了有关数据库的详细信息&#xff0c;包括设计、结构、运行环境、数据安全、管理和维护等方面的内容。 1 引言 引言部分&#xff0c;简要介绍数据库设计说明书的目的和内容。这部分通常包括以下内容&#xff1a; 引言的目的…

基于通道的数据增强方法_使用随机量化的方式

前言本文提出了一种适用于任意数据模态的自监督学习数据增强技术 来源&#xff1a;机器之心 仅用于学术分享&#xff0c;若侵权请联系删除 自监督学习算法在自然语言处理、计算机视觉等领域取得了重大进展。这些自监督学习算法尽管在概念上是通用的&#xff0c;但是在具体操作…

免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

MyBatis实验(四)——关联查询

前言 多表关联查询是软件开发中最常见的应用场景&#xff0c;多表查询需要将数据实体之间的一对多、多对多、一对一的关系的转换为复杂的数据对象。mybaits提供的association和collection元素&#xff0c;通过映射文件构造复杂实体对象&#xff0c;在构造实体过程中&#xff0…

leetcode 155. 最小栈

2023.10.31 本题关键在于要求在能在常数时间内检索出最小元素。 其他四个方法都可以用普通的栈进行实现&#xff0c;最后一个方法“获取堆栈中最小元素” 可以借助一个新栈&#xff0c;专门用于存储栈中最小值的。具体细节看java代码&#xff1a; class MinStack {private De…

一百九十八、Java——IDEA项目中有参构造、无参构造等快捷键(持续梳理中)

一、目的 由于IDEA项目中有很多快捷键&#xff0c;可以很好的提高开发效率&#xff0c;因此整理一下 二、快捷键 &#xff08;一&#xff09;快捷键生成public static void main(String[] args) {} 快捷键&#xff1a;psvm &#xff08;二&#xff09;快捷键在test中创建cn…

Linux服务器使用GPU技巧

进行深度学习实验的时候用pytorch-gpu&#xff0c;经常要与GPU打交道&#xff1b; 所以经常遇到奇奇怪怪的问题&#xff1b; 查看GPU占用情况 watch -n 10 nvidia-smi 使用技巧 torch.nn.DataParallel() CLASStorch.nn.DataParallel(module, device_idsNone, output_devic…

AntDB数据库荣获 “2023年信创物联网优秀服务商”

日前&#xff0c;在2023世界数字经济大会暨第十三届智博会 2023京甬信创物联网产融对接会上&#xff0c;AntDB数据库再获殊荣&#xff0c;获评“2023年信创物联网优秀服务商”。 图1&#xff1a;2023年信创物联网优秀服务商颁奖现场 信创物联网是信息技术应用创新与物联网的结…

mac录屏快捷键指南,轻松录制屏幕内容!

“大家知道mac电脑有录屏快捷键吗&#xff0c;现在录屏不太方便&#xff0c;每次都花很多时间&#xff0c;要是有录屏快捷键&#xff0c;应该会快速很多&#xff0c;可是哪里都找不到&#xff0c;有人知道吗&#xff1f;帮帮我&#xff01;” 苹果的mac电脑以其精美的设计和卓…

java商城免费搭建 VR全景商城 saas商城 b2b2c商城 o2o商城 积分商城 秒杀商城 拼团商城 分销商城 短视频商城

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

波浪理论第3波anzo capital昂首资本3个方法3秒确认

要想通过波浪理论在交易中赚取最大利润&#xff0c;确认第三波必不可少&#xff0c;因为第三波通常是趋势中最大和最强的一波&#xff0c;今天anzo capital昂首资本3个方法3秒确认。 首先&#xff0c;第一个确认方法—斜率。 通常&#xff0c;第三波的斜率会比第一波更陡峭&a…

钡铼技术助力ARM工控机在智慧交通中的创新应用

在交通运输领域&#xff0c;钡铼技术ARM工控机可以实现以下功能&#xff1a; 实时监控和管理&#xff1a;利用钡铼技术ARM工控机&#xff0c;可以对交通运输中的车辆、船只、飞机等进行实时监测和管理&#xff0c;帮助调度员提高车辆调度和路线规划的准确性和效率。 安全保障&…

查看局域网内另外一个电脑屏幕

查看局域网内另外一个电脑屏幕是一个相对简单但实用的技术。在局域网中&#xff0c;我们可以使用远程桌面、网络发现和共享、软件等技术来实现这一目标。 今天重点讲解一下&#xff0c;如何通过域之盾软件来查看另一个电脑屏幕&#xff1a; 1、部署软件&#xff0c;安装提示一…

软件测试优秀的测试工具,会用三款工作效率能提升一半

我们将常用的测试工具分为10类。 1. 测试管理工具 2. 接口测试工具 3. 性能测试工具 4. C/S自动化工具 5.白盒测试工具 6.代码扫描工具 7.持续集成工具 8.网络测试工具 9.app自动化工具 10.web安全测试工具 注&#xff1a;工具排名没有任何意义。 大多数初学者&…

众和策略:微软大动作

当地时间周二&#xff0c;美股首要指数全线收涨。但从月度数据来看&#xff0c;美股首要指数录得“三连跌”&#xff0c;10月份&#xff0c;道指跌1.36%&#xff0c;标普500指数跌2.2%&#xff0c;纳指跌2.78%。其间&#xff0c;标普和道指均为2020年3月以来初次呈现三个月连跌…

Unity 粒子特效-第四集-光球闪烁特效

一、特效预览 二、制作原理 光球素材资源 链接&#xff1a;https://pan.baidu.com/s/1XzWpQU2zX_wupMXSW7RxwA?pwdvu5r 提取码&#xff1a;vu5r 1.素材介绍 仔细看&#xff0c;我们的粒子贴图是&#xff08;如下&#xff09;&#xff0c;一颗球球 2.步骤介绍 1.光球动画的…