解读Spring的context:property-placeholder

news2024/11/14 20:56:59

        在spring中,如果要给程序定义一些参数,可以放在application.properties中,通过<context:property-placeholder>加载这个属性文件,然后就可以通过@value给我们的变量自动赋值,如果你们的程序可能运行在多个环境中(比如开发、测试和生产)你也可以定义多套属性文件。这些用法我们早已司空见惯,今天我们就来理一下来龙去脉。

context:property-placeholder

用法

<context:property-placeholder>的主要属性:

属性名        说明
location文件位置,多个之间通过如逗号/分号等分隔;
file-encoding文件编码
ignore-resource-not-found如果属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常 
ignore-unresolvable是否忽略解析不到的属性,如果不忽略,找不到将抛出异常
properties-ref本地java.util.Properties配置
local-override是否本地覆盖模式,即如果true,本地属性文件的优先级高于环境变量
system-properties-mode

系统属性模式

ENVIRONMENT(默认)

FALLBACK(环境变量兜底)

NEVER(不使用环境变量)

OVERRIDE(环境变量覆盖)                            

历史渊源

要说清楚这个标签,涉及spring3.1这个重要版本,从这个版本起,spring抽象了Environment接口,这个标签的处理器也从PropertyPlaceholderConfigurer被过渡到PropertySourcesPlaceholderConfigurer(下面细讲)。

关于system-properties-mode属性

在3.1之前,只有三个取值,当时的处理器还是PropertyPlaceholderConfigurer,这个处理现在已经不推荐使用,这个变量的作用是:声明对系统属性和环境变量的使用方式。

属性值说明
NEVER不使用系统属性和环境变量
OVERRIDE

使用系统属性和环境变量覆盖属性文件

即系统属性和环境变量优先

FALLBACK

使用系统属性和环境变量来做兜底

即属性文件优先

在3.1之后,多了一个新的取值Environment,spring从这个版本起引入了Environment接口,如果system-properties-mode属性被配置为Environment,则使用采用PropertySourcesPlaceholderConfigurer进行占位符处理,这也是3.1之后的默认处理方式。

见PropertyPlaceholderBeanDefinitionParser:

    @Override
	protected Class<?> getBeanClass(Element element) {
        //system-properties-mode设置为ENVIRONMENT,则走PropertySourcesPlaceholderConfigurer
		if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
			return PropertySourcesPlaceholderConfigurer.class;
		}
        //否则走PropertyPlaceholderConfigurer(兼容处理)
		return PropertyPlaceholderConfigurer.class;
	}

各种配置的优化级

由于system-properties-mode只推荐使用ENVIRONMENT,其它三种方式只是保持对老版本spring的兼容,这里主要分析在ENVIRONMENT模式下,属性文件,properties-ref,系统属性,环境变量的优先级

local-override属性值优先级
false

System.getProperty()>System.getenv()>

location属性文件>properties-ref

trueproperties-ref>location属性文件>System.getProperty()>System.getenv()

默认情况下,系统属性和环境变量一起构成StandardEnvironment,系统属性优先于环境变量。

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value}. */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


    //加入(定义)两个PropertySource:这个是系统属性,一个是环境变量
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

}

PropertySourcesPlaceholderConfigurer

PropertySourcesPlaceholderConfigurer维护一个MutablePropertySources,该对象放着两个PropertySource,一个对Environment做了一个包装,一个是合并本地的属性文件配置。并local-override的配置的不同,决定这两个PropertySource的优先级:

#postProcessBeanFatcory

作为一个BeanFactoryPostProcessor,该方法会在Bean实例化之前被spring容器调用

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if (this.propertySources == null) {
			this.propertySources = new MutablePropertySources();
			if (this.environment != null) {
				this.propertySources.addLast(
						//将Environment封装成一个新的PropertySource,后面的占位符处理将委托给Environment对象
					new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
						@Override
						@Nullable
						public String getProperty(String key) {
							return this.source.getProperty(key);
						}
					}
				);
			}
			try {
				//合并properties-ref和location的属性合并,并统称为localProperties
				PropertySource<?> localPropertySource =
						new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
				if (this.localOverride) {
					//如果本地覆盖,则将本地属性放在首位(优先)
					this.propertySources.addFirst(localPropertySource);
				}
				else {//否则将本地属性放在未位
					this.propertySources.addLast(localPropertySource);
				}
			}
			catch (IOException ex) {
				throw new BeanInitializationException("Could not load properties", ex);
			}
		}
		//处理属性占位符,这里只是将占位符处理器注入给到BeanFactory
		processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
		this.appliedPropertySources = this.propertySources;
	}

#mergeProperties

mergeProperties方法:合并location指向的属性文件和properties-ref指向的配置对象。如果全地覆盖,则使用properties-ref覆盖location的配置,否则相反。其中loadProperties是加载location指定的属性文件,并解析代析Properties对象,代码略

protected Properties mergeProperties() throws IOException {
		Properties result = new Properties();

		if (this.localOverride) {
			// 如果本地覆盖,则先加载location指定的属性文件
			loadProperties(result);
		}

		if (this.localProperties != null) {
            //如果定义了properties-ref属性,则进行属性合并
			for (Properties localProp : this.localProperties) {
				CollectionUtils.mergePropertiesIntoMap(localProp, result);
			}
		}

		if (!this.localOverride) {
			//如果非本地覆盖,则最后加载location指定的属性文件
			loadProperties(result);
		}

		return result;
	}

#processProperties

进入processProperties方法:ConfigurablePropertyResolver 最终被转换成StringValueResolver,然后调用doProcessProperties。

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			final ConfigurablePropertyResolver propertyResolver) throws BeansException {

		propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
		propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
		propertyResolver.setValueSeparator(this.valueSeparator);

		StringValueResolver valueResolver = strVal -> {
			String resolved = (this.ignoreUnresolvablePlaceholders ?
					propertyResolver.resolvePlaceholders(strVal) :
					propertyResolver.resolveRequiredPlaceholders(strVal));
			if (this.trimValues) {
				resolved = resolved.trim();
			}
			return (resolved.equals(this.nullValue) ? null : resolved);
		};

		doProcessProperties(beanFactoryToProcess, valueResolver);
	}

#doProcessProperties

进入doProcessProperties,该方法处理BeanDefinition中的各种占位符,最后把StringValueResolver注入给beanFactory,供属性代替使用。

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			StringValueResolver valueResolver) {

		BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

		String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
		for (String curName : beanNames) {
			if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
				BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
				try {
                    //处理BeanDefinition中的各种占位符(注意:不是bean本身)
					visitor.visitBeanDefinition(bd);
				}
				catch (Exception ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
				}
			}
		}

		beanFactoryToProcess.resolveAliases(valueResolver);
        //在这里把StringValueResolver注入给beanFactory,供属性代替使用
		beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
	}

相关接口说明

PropertyResolver接口

在PropertySourcesPlaceholderConfigurer中会创建PropertyResolver接口对象,该对象持有配置属性源的引用,并提供获取属性值,处理占位符等功能。详见:PropertySourcesPropertyResolver,这里只列出接口的定义:

public interface PropertyResolver {
	boolean containsProperty(String key);

	@Nullable
	String getProperty(String key);//获取属性值
	String getProperty(String key, String defaultValue);
	@Nullable
	<T> T getProperty(String key, Class<T> targetType);
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);

	String getRequiredProperty(String key) throws IllegalStateException;
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

	String resolvePlaceholders(String text);//处理占位符
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

Environment

Environment接口是Spring体系里一个既熟悉又陌生的接口。

Spring 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储、占位符处理和类型转换,支持更丰富的配置属性源(PropertySource)。

条件化 Spring Bean 装配管理:
通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。

@Profile

在spring应用中,通常会通过变量spring.profiles.active去指定当前环境,而注解@Profile可以加上Bean上,让Bean在某个环境中生效,其实现通过@Conditional(ProfileCondition.class),这里不展开。

Environment接口继承自PropertyResolver,同时扩展了对profile的操作,通过profile来标识当前处于哪个环境(开发、测试或生产)

public interface Environment extends PropertyResolver {
    //获取当前profile(环境)
	String[] getActiveProfiles();
	String[] getDefaultProfiles();

	@Deprecated
	boolean acceptsProfiles(String... profiles);

    //判断当前环境是否与给定profiles一致
	boolean acceptsProfiles(Profiles profiles);
}

AbstractEnvironment

结合抽象类AbstractEnvironment,可以更好地理解Environment的行为

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	//维护当前活动的profile环境
    private final Set<String> activeProfiles = new LinkedHashSet<>();

	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
    //维护各种配置属性源
	private final MutablePropertySources propertySources = new MutablePropertySources();
    //持有占位符处理器
	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);
}

        AbstractEnvironment由于知道当前是哪个环境,就知道需要装载哪些配置文件,而MutablePropertySources是可变的PropertySources,它允许用户动态地添加各种PropertySource,比如来自配置中心的配置。最后由propertyResolver完成最后的占位符处理操作。

PropertySource

属性源就是对一类配置(可以是属性配置文件,系统属性,环境变量等)的封装,同时给这个配置一个名称

public abstract class PropertySource<T> {

	protected final String name;//名称

	protected final T source;//配置,存放键值对

    ...

}

MapPropertySource:一种基于Map实现的简单的PropertySource。

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {

	public MapPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}


	@Override
	@Nullable
	public Object getProperty(String name) {
		return this.source.get(name);
	}

	@Override
	public boolean containsProperty(String name) {
		return this.source.containsKey(name);
	}

	@Override
	public String[] getPropertyNames() {
		return StringUtils.toStringArray(this.source.keySet());
	}

}

只要你喜欢,你可以把Map当做你的配置源,也就是说你可以把创建一个Map作为应用程序的配置。一些主要的PropertySource:

PropertySource 类型说明
org.springframework.core.env.CommandLinePropertySource命令行配置属性源
org.springframework.jndi.JndiPropertySourceJDNI 配置属性源
org.springframework.core.env.MapPropertySource基于Map对象的配置属性源
org.springframework.core.env.PropertiesPropertySource

扩展自MapPropertySource,

Properties 配置属性源

org.springframework.web.context.support.ServletConfigPropertySourceServlet 配置属性源
org.springframework.web.context.support.ServletContextPropertySourceServletContext 配置属性源
org.springframework.core.env.SystemEnvironmentPropertySource环境变量配置属性源

其中PropertiesPropertySource扩展自MapPropertySource,我们的系统属性,以及我们属性配置文件,最终会被封装成PropertiesPropertySource,其它配置属性源请自行脑补。

PropertySources

PropertySources是PropertySource的集合,是一个继承自Iterable的接口,提供了一些方便操作属性源的方法:

  • addFirst():将属性源放在首位,即优先级最高
  • addLast():将属性源放在未位,即优先级最低

MutablePropertySources

PropertySources有一个唯一的实现:MutablePropertySources。该实现就是Environment对象里的可变属性源,基于该对象,可以实现PropertySource的动态地添加,并按需对属性源进行优先级排序。

注:在spring中有对应的注解,如@PropertySource和@PropertySources可以以注解的形成来配置属性源,随着applicationContext.xml慢慢被摈弃,正逐渐代替<context:property-placeholder>,这里不展开。

如何动态添加属性源

通过各种渠道获取到Environment的对象引用(比如通过ConfigurableApplicationContext#getEnvironment等),再把自己扩展的PropertySource添加到它的MutablePropertySources里面。

ConfigurableEnvironment environment = context.getEnvironment();
MapPropertySource mapPropertySource = new MapPropertySource("my-map-properties", new HashMap<>());
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(mapPropertySource);

@Value的实现

上面讲了Enviroment抽象以及属性源优先级,下面讲@Value注释如何实现属性值的替换。

AutowiredAnnotationBeanPostProcessor

多数人对@Value这一块并不陌生,主要的实现就是AutowiredAnnotationBeanPostProcessor,该BeanPostProcessor主要处理@Value和@Autowired注解。这里就不贴代码了,大概流程如下:

        spring容器在创建完Bean对象实现实例之后,进入属性注入阶段会回调postProcessPropertyValues方法(BeanPostProcessor的回调机制),进而会调用beanFactory.resolveDependency(AutowiredAnnotationBeanPostProcessor实现了BeanFactoryAware接口,持有beanFactory对象引用)

DefaultListableBeanFactory#doResolveDependency

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}

			Class<?> type = descriptor.getDependencyType();
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			if (value != null) {
				if (value instanceof String) {
                    //这里处理属性值
					String strVal = resolveEmbeddedValue((String) value);
					BeanDefinition bd = (beanName != null && containsBean(beanName) ?
							getMergedBeanDefinition(beanName) : null);
					value = evaluateBeanDefinitionString(strVal, bd);
				}
                //对属性值做类型转换
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				try {
					return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
				}
				catch (UnsupportedOperationException ex) {
					// A custom TypeConverter which does not support TypeDescriptor resolution...
					return (descriptor.getField() != null ?
							converter.convertIfNecessary(value, type, descriptor.getField()) :
							converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
				}
			}
...
}

2)beanFactory.resolveDependency的主要作用是处理属性依赖,可以处理对象的注入也可以处理属性值的注入。如果是@Value属性值注入,则进行占位符处理,而在BeanFactory里,处理占位符的对象是StringValueResolver,BeanFactory维护多个StringValueResolver:

public String resolveEmbeddedValue(@Nullable String value) {
		if (value == null) {
			return null;
		}
		String result = value;
        //这里的StringValueResolver有一个是PropertySourcesPlaceholderConfigurer注入的
		for (StringValueResolver resolver : this.embeddedValueResolvers) {
			result = resolver.resolveStringValue(result);
			if (result == null) {
				return null;
			}
		}
		return result;
	}

BeanFactory对属性值的处理就是交给多个StringValueResolver去循环处理,这里的StringValueResolver有一个是PropertySourcesPlaceholderConfigurer注入的,可回头看PlaceholderConfigurerSupport#doProcessProperties。

StringValueResolver接口

回到StringValueResolver接口,该接口只做一件事,那就是做占位符处理(将一个字符串转成另一个字符串)

@FunctionalInterface
public interface StringValueResolver {

	@Nullable
	String resolveStringValue(String strVal);

}

那问题来了,为何BeanFactory不直接持有PropertyResolver对象,而要使用新的接口呢?

这里要说明的是PropertyResolver接口是spring3.1才引入的,而StringValueResolver则在spring2.5就已经存在,一方面BeanFactory原本持有的就是StringValueResolver,另一个方面这突显了接口设计的单一原则,因为对于BeanFactory而言,就仅仅是想得到目标的属性值。因而也就没有必要换成PropertyResolver接口。

总结

回顾<context:property-placeholder>和@Value的整个过程:

1)自spring3.1起,采用PropertySourcesPlaceholderConfigurer进行配置源加载与占位符的处理,该Configurer维护一个MutablePropertySources,该对象放着两个PropertySource,一个对Environment做了一个包装,一个是合并本地的属性文件配置。并local-override的配置的不同,决定这两个PropertySource的优先级。

2)PropertySourcesPlaceholderConfigurer创建了PropertyResolver接口对象,并适配成StringValueResolver接口,传递给BeanFactory,由于PropertyResolver对象持有MutablePropertySources引用,因此也可以说这个MutablePropertySources对BeanFactory可见。

3)BeanFactory维护着一系列StringValueResolver对象,并提供处理对象依赖(包括属性值)的能力(#resolveDependency方法)。

4)属性值注入阶段,AutowiredAnnotationBeanPostProcessor通过调用BeanFactory的resolveDependency方法实现属性注入,内部调用StringValueResolver进行属性值处理,而本质上就是调用PropertySourcesPlaceholderConfigurer的PropertyResolver,最终使用的也是PropertySourcesPlaceholderConfigurer中的的MutablePropertySources。

5)得益于Environment的MutablePropertySources,应用可以更灵活地管理各种配置及其优先级。

最后献上一个类图:

by simple

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是头脑风暴法,有哪些原则?

1. 什么是头脑风暴法&#xff1f; 头脑风暴法&#xff08;Brainstorming&#xff09;是一种用于创造性思维和问题解决的方法。它旨在通过集体讨论和思维碰撞&#xff0c;激发团队成员的创造力和想象力&#xff0c;从而产生新的创意和解决方案。 在头脑风暴会议中&#xff…

数据结构 10-排序4 统计工龄 桶排序/计数排序(C语言)

给定公司名员工的工龄&#xff0c;要求按工龄增序输出每个工龄段有多少员工。 输入格式: 输入首先给出正整数&#xff08;≤&#xff09;&#xff0c;即员工总人数&#xff1b;随后给出个整数&#xff0c;即每个员工的工龄&#xff0c;范围在[0, 50]。 输出格式: 按工龄的递…

【福建事业单位-推理判断】01图形推理(位置,样式、属性、特殊)

【福建事业单位-推理判断】01图形推理 一、位置规律&#xff08;&#xff08;元素组成相同&#xff09;&#xff09;1.1平移旋转翻转1.1.1先判定方向&#xff0c;再确定路径1.1.2分内外圈走 1.2 旋转1.3翻转左右翻只有左右变&#xff0c;上下翻只有上下变&#xff0c;旋转180全…

真的不想知道如何进行语音翻译才简单吗

郑希&#xff1a;嘿&#xff0c;王浩&#xff01;我听说你最近去了日本旅游&#xff0c;怎么样&#xff1f;体验如何&#xff1f; 王浩&#xff1a;哈哈&#xff0c;太棒了&#xff01;日本真是一个充满魅力的国家。不过&#xff0c;要说令我惊喜的还是语音翻译技术&#xff0…

大同市副市长孟维君赴大同互联网职业技术学院指导建设工作

8月2日&#xff0c;大同市副市长孟维君一行莅临大同互联网职业技术学院&#xff08;以下简称&#xff1a;大同互联网学院&#xff09;&#xff0c;对学院的建设工作进行了重要指导。孟维君副市长深入施工现场&#xff0c;详细了解了项目施工的进展情况&#xff0c;并提出三点重…

【LeetCode-简单】剑指 Offer 18. 删除链表的节点(详解)

题目 定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&#xff1a;此题对比原题有改动 题目地址&#xff1a;剑指 Offer 18. 删除链表的节点 - 力扣&#xff08;LeetCode&#xff09; 方法 删除一个节点…

初始创建一个apex应用程序

在应用程序构建器 点击创建选择新建应用程序你只需要填写你应用程序的名称然后点击创建应用程序 在工作区页面&#xff0c;点开上方SQL工作室&#xff0c;点击对象浏览器然后你可以在右上角看到一个加号&#xff0c;点开&#xff0c;选“表”起好表名&#xff0c;并写好你需要的…

CPU缓存那些事儿

CPU缓存那些事儿 CPU高速缓存集成于CPU的内部&#xff0c;其是CPU可以高效运行的成分之一&#xff0c;本文围绕下面三个话题来讲解CPU缓存的作用&#xff1a; 为什么需要高速缓存&#xff1f;高速缓存的内部结构是怎样的&#xff1f;如何利用好cache&#xff0c;优化代码执行…

Nacos 抽取公共配置

文章目录 创建一个公共配置文件其他配置文件引用springboot配置文件 创建一个公共配置文件 其他配置文件引用 ${变量} springboot配置文件 spring:cloud:nacos:discovery:server-addr: current.ip:8848namespace: word_register_proconfig:server-addr: current.ip:8848auto-r…

【Valgrind】如何使用Valgrind监控内存

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

FIFO不常规的应用

一、fifo的复位后的满标志位状态 IP中设置方法 1、Block Ram和Distributed Ram 通过设置上图中的值为1&#xff08;默认&#xff09;或者为0&#xff0c;设置full相关信号在复位后的状态。从下表中可以看出对应关系。 2、Bulitin FIFO 该类型下&#xff0c;读写FIFO的位宽一…

【程序崩溃的原因及处理方法】

程序的编写时&#xff0c;可能经常会遇到程序崩溃的现象。一般来说&#xff0c;程序崩溃是由于内存操作不当引发的。但是具体来讲&#xff0c;由哪些原因可以导致程序崩溃呢&#xff1f;以及当程序崩溃时该如何找到错误的位置呢&#xff1f; 1.资源不足&#xff1a;当应用程序…

应用案例|基于3D视觉的高反光金属管件识别系统解决方案

Part.1 项目背景 在现代制造业中&#xff0c;高反光金属管件的生产以及质量的把控是一个重要的挑战。传统的2D视觉系统常常难以准确地检测和识别高反光金属管件&#xff0c;因为它们的表面特征不够明显&#xff0c;容易受到光照和阴影的干扰。为了应对这个问题&#xff0c;基于…

TWS真无线蓝牙耳机哪家好?六款口碑好的TWS真无线蓝牙耳机分享

为了帮助大家在这个充满选择的世界中找到最理想的蓝牙耳机&#xff0c;我们特别为您精心挑选了几款备受赞誉的产品&#xff0c;它们在音质、舒适度、功能和性价比等方面都有出色的表现。在本文中&#xff0c;我们将深入探讨这些蓝牙耳机的特点和优势&#xff0c;帮助您更好地了…

python调用pytorch的clip模型时报错

使用python调用pytorch中的clip模型时报错&#xff1a;AttributeError: partially initialized module ‘clip’ has no attribute ‘load’ (most likely due to a circular import) 目录 现象解决方案一、查看项目中是否有为clip名的文件二、查看clip是否安装成功 现象 clip…

wpf画刷学习1

在这2篇博文有提到wpf画刷&#xff0c; https://blog.csdn.net/bcbobo21cn/article/details/109699703 https://blog.csdn.net/bcbobo21cn/article/details/107133703 下面单独学习一下画刷&#xff1b; wpf有五种画刷&#xff0c;也可以自定义画刷&#xff0c;画刷的基类都…

DDS中间件设计

OpenDDS、FastDDS数据分发服务中间件设计 软件架构 应用层DDS层RTPS层传输层 软件层次 FastDDS整体架构如下&#xff0c;这里可以看到DDS和RTPS的关系。另外缺少一部分IDL&#xff08;统一描述语言&#xff09;&#xff0c;其应该是Pub、Sub的反序列化、序列化工具。 在RT…

根据接口中的数据,循环出多个echarts图表

1.效果图 2.接口数据 3.开发如下 <div class"chart-list"><div v-for"(item,index) in chartData" :key"index" style"margin-top:5px;"><div>{{item.title}}</div><div :id"myChartindex" st…

分布式框架dubbo

1.分布式系统相关概念 1.1基本概念 1.2 集群和分布式 1.3 架构演进 A是一个微服务。ADB是一个组件。A可以java&#xff0c;B可以python实现。 2 dubbo 2.1 概述 2.2 dubbo代码 2.2.1 服务提供者的改造-将项目service层对外发布到dubbo 通过dubbo中的service注解&#xff…

Unity 3D WebView 插件之基础介绍(一)

序言&#xff1a; Unity 3D WebView&#xff1a;支持多个平台&#xff08;安卓、ios、Windows、WebGL、UWP&#xff09;页面渲染技术&#xff0c; WebView控件功能强大&#xff0c;除了具有一般View的属性和设置外&#xff0c;还可以对Url请求、页面加载、渲染、页面交互进行强…