Spring注解系列 - @Autowired注解

news2024/11/16 6:58:55

文章目录

  • 使用总结
  • 注入原理
  • @Autowired 注入过程
    • InjectionMetadata
    • InjectedElement
    • 依赖注入查找过程
      • findAutowireCandidates
    • 缓存注入信息
  • @Resource 注解

使用总结

@Autowired注解可以自动将所需的依赖对象注入到类的属性、构造方法或方法中,从而减少手动注入依赖的代码,并提高代码的可维护性和可测试性。它是Spring容器配置的一个重要注解,与@Required、@Primary、@Qualifier等注解同属容器配置范畴。

  1. @Autowired是按照类型注入依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照Bean的名称来装配,可以结合@Qualifier注解一起使用。如果通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下,会使用对象名作为最后的fallback匹配

使用场景

  1. 属性注入(Field Injection):
    在类的属性上使用@Autowired注解,Spring会自动注入一个相应类型的实例到该属性中。
    例如:@Autowired private MyRepository myRepository;
  2. 构造方法注入(Constructor Injection):
    在类的构造方法上使用@Autowired注解,Spring会在创建类实例时,通过构造方法注入相应的依赖。例如:
@Autowired 
public MyService(MyRepository myRepository) { 
	this.myRepository = myRepository; 
}

注意:从Spring 4.3开始,如果目标bean只有一个构造器,则无需再单独放置@Autowired注解,Spring会自动完成注入。但如果存在多个构造器,则需要选择一个并标注上@Autowired注解。

  1. 方法注入(Setter Injection):
    在类的setter方法上使用@Autowired注解,Spring会调用该setter方法并注入相应的依赖。
    例如:
@Autowired public void setMyRepository(MyRepository myRepository) { 
	this.myRepository = myRepository; 
}

高级用法

  1. 数组注入:
    如果需要将多个同类型的bean注入到一个数组中,可以在数组属性或方法上使用@Autowired注解。例如:T 为具体需要注入的类型
@Autowired 
private T[] autowiredElements;
  1. 集合注入:
    同样地,也可以将多个同类型的bean注入到一个集合(如Set、List、Collection等)中。例如:T 为具体需要注入的类型
@Autowired public void setMyRepositories(Set<T> set) { 
	this.autowiredSet = set; 
}
  1. Map注入:
    如果注入的Map中Key为String类型,并且代表每个bean的name,value为指定的bean类型,则可以直接注入。例如:T 为具体需要注入的类型
@Autowired public void setMyRepositoryMap(Map<String, T> map) { 
	this.autowiredMap = map; 
}

注意事项

  1. Bean的注册:
    被注入的依赖必须是Spring容器中的一个Bean。可以通过@Component@Service@Repository等注解将类注册为Spring容器中的Bean。

  2. 自动装配模式:
    默认情况下@Autowired按类型装配。如果有多个同类型的Bean,可以使用@Qualifier注解来指定具体的Bean。

  3. required属性:
    默认情况下@Autowired的required属性为true,这意味着Spring容器在启动时必须找到一个匹配的Bean。如果没有找到匹配的Bean,会抛出异常。可以通过将required属性设置为false来避免这种情况:

@Autowired(required=false) 
private MyRepository myRepository;

注入原理

实现@Autowired注解功能的是一个后置处理器org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
		MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
}

当一个Bean创建成功后,根据其生命周期,会进行Bean属性填充(populateBean)过程,源码位于org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
在这里插入图片描述

注意:这里@Autowired不是在postProcessAfterInstantiation里实现的
在这里插入图片描述
而是在postProcessProperties这个回调里实现字段的注入的
在这里插入图片描述

@Autowired 注入过程

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	// 获取要进行注入的信息
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
		metadata.inject(bean, beanName, pvs);
	} catch (XxxException ex) {
		// 省略异常处理
	}
	return pvs;
}

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
	// 从缓存中获取
	String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
	InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
	if (InjectionMetadata.needsRefresh(metadata, clazz)) {
		// 刷新缓存
		synchronized (this.injectionMetadataCache) {
			// Double-Check
			metadata = this.injectionMetadataCache.get(cacheKey);
			if (InjectionMetadata.needsRefresh(metadata, clazz)) {
				if (metadata != null) {
					metadata.clear(pvs);
				}
				// 构建缓存
				metadata = buildAutowiringMetadata(clazz);
				this.injectionMetadataCache.put(cacheKey, metadata);
			}
		}
	}
	return metadata;
}

InjectionMetadata

org.springframework.beans.factory.annotation.InjectionMetadata包含了一个Class的注入的元数据信息,哪些字段和哪些方法需要进行注入
在这里插入图片描述
InjectionMetadata#inject进行注入实际上是调用 InjectedElement#inject 方法进行注入

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		for (InjectedElement element : elementsToIterate) {
			element.inject(target, beanName, pvs);
		}
	}
}

InjectedElement

InjectedElement包含以下几种类型:

  1. AutowiredFieldElement:@Autowire注解标注的字段
  2. AutowiredMethodElement:@Autowire注解标注的方法
  3. LookupElement:由CommonAnnotationBeanPostProcessor中定义的内部类LookupElement表示,这个CommonAnnotationBeanPostProcessor后面会提到

在这里插入图片描述

以字段注入为例

/**
 * Object bean: @Autowired所在类的bean实例
 * String beanName:@Autowired所在类的bean名称
 * PropertyValues pvs:属性值
 **/
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	if (this.cached) {
		try {
			value = resolveCachedArgument(beanName, this.cachedFieldValue);
		} catch (BeansException ex) {
			// 缓存的对象和想要注入的不兼容,因为是按名称注入
			// 可能多次注入过程中不同类型的对象因为使用了同一个名称而导致覆盖,从而不兼容
			// 兜底操作
			this.cached = false;
			value = resolveFieldValue(field, bean, beanName);
		}
	} else {
		// 获取该Field要注入的值
		value = resolveFieldValue(field, bean, beanName);
	}
	if (value != null) { // 反射进行赋值
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value);
	}
}

@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
	DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
	desc.setContainingClass(bean.getClass());
	Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
	Assert.state(beanFactory != null, "No BeanFactory available");
	TypeConverter typeConverter = beanFactory.getTypeConverter();
	Object value;
	try {
		value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
	} catch (BeansException ex) {
		throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
	}
	synchronized (this) {
		if (!this.cached) {
			if (value != null || this.required) {
				Object cachedFieldValue = desc;
				registerDependentBeans(beanName, autowiredBeanNames);
				if (value != null && autowiredBeanNames.size() == 1) {
					String autowiredBeanName = autowiredBeanNames.iterator().next();
					if (beanFactory.containsBean(autowiredBeanName) &&
							beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
						cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName);
					}
				}
				this.cachedFieldValue = cachedFieldValue;
				this.cached = true;
			} else {
				this.cachedFieldValue = null; // cached flag remains false
			}
		}
	}
	return value;
}

关键的就是beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)了,这里会去解析依赖,获取需要的对象实例

依赖注入查找过程

org.springframework.beans.factory.support.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;
		}
		// 例如 UserService 这个Class
		Class<?> type = descriptor.getDependencyType();
		// 通过AutowireCandidateResolver获取一次值
		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()));
			}
		}
		// 针对多个值一起注入的场景,比如@Autowired的是一个集合类型
		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
		if (multipleBeans != null) {
			return multipleBeans;
		}
		// 根据依赖描述符查找候选的Bean对象
		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) {
			if (isRequired(descriptor)) {
				// required 为 true,但是没找到,直接抛异常
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			return null;
		}
		String autowiredBeanName;
		Object instanceCandidate;
		if (matchingBeans.size() > 1) {
			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
			if (autowiredBeanName == null) {
				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
				}
				else {
					// In case of an optional Collection/Map, silently ignore a non-unique case:
					// possibly it was meant to be an empty collection of multiple regular beans
					// (before 4.3 in particular when we didn't even look for collection beans).
					return null;
				}
			}
			instanceCandidate = matchingBeans.get(autowiredBeanName);
		} else {
			// We have exactly one match.
			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
			autowiredBeanName = entry.getKey();
			instanceCandidate = entry.getValue();
		}

		if (autowiredBeanNames != null) {
			autowiredBeanNames.add(autowiredBeanName);
		}
		if (instanceCandidate instanceof Class) {
			instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
		}
		Object result = instanceCandidate;
		if (result instanceof NullBean) {
			if (isRequired(descriptor)) {
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			result = null;
		}
		if (!ClassUtils.isAssignableValue(type, result)) {
			throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
		}
		return result;
	} finally {
		ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
	}
}

findAutowireCandidates

根据类型查找所有候选的可注入的对象,下面就是@Autowired按类型注入的实现过程了

protected Map<String, Object> findAutowireCandidates(
		@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
	// 根据类型查找BeanFactory 中该类型对应的所有Bean名称
	String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
			this, requiredType, true, descriptor.isEager());
	Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
	// 首先从 this.resolvableDependencies 这个缓存中获取
	for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
		Class<?> autowiringType = classObjectEntry.getKey();
		if (autowiringType.isAssignableFrom(requiredType)) {
			Object autowiringValue = classObjectEntry.getValue();
			autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
			if (requiredType.isInstance(autowiringValue)) {
				result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
				break;
			}
		}
	}
	for (String candidate : candidateNames) {
		// isSelfReference: 判断是否@Autowired的是beanName
		if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
			addCandidateEntry(result, candidate, descriptor, requiredType);
		}
	}
	if (result.isEmpty()) {
		boolean multiple = indicatesMultipleBeans(requiredType);
		// Consider fallback matches if the first pass failed to find anything...
		DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
		for (String candidate : candidateNames) {
			if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
					(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
				addCandidateEntry(result, candidate, descriptor, requiredType);
			}
		}
		if (result.isEmpty() && !multiple) {
			// Consider self references as a final pass...
			// but in the case of a dependency collection, not the very same bean itself.
			for (String candidate : candidateNames) {
				if (isSelfReference(beanName, candidate) &&
						(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
						isAutowireCandidate(candidate, fallbackDescriptor)) {
					addCandidateEntry(result, candidate, descriptor, requiredType);
				}
			}
		}
	}
	return result;
}

BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class<?>, boolean, boolean)这个方法的作用是根据依赖的类型从BeanFactory中获取该类型所有的Bean名称,关键是String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);这句

public static String[] beanNamesForTypeIncludingAncestors(
		ListableBeanFactory lbf, Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {

	Assert.notNull(lbf, "ListableBeanFactory must not be null");
	String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
	if (lbf instanceof HierarchicalBeanFactory) {
		HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
		if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
			String[] parentResult = beanNamesForTypeIncludingAncestors(
					(ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit);
			result = mergeNamesWithParent(result, parentResult, hbf);
		}
	}
	return result;
}

缓存注入信息

当一个Bean创建时,populateBean这一步需要获取该类上有哪些元素是需要@Autowired的。AutowiredAnnotationBeanPostProcessor 同时实现了SmartInstantiationAwareBeanPostProcessor和MergedBeanDefinitionPostProcessor,解析注入信息这个过程就是在MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition实现的

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
	InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
	metadata.checkConfigMembers(beanDefinition);
}

@Resource 注解

@Resource默认按照名称自动注入,由J2EE提供,需要导入包javax.annotation.Resource(spring 6以后需要改成jakarta.annotation.Resource)。

@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用按名称注入的自动注入策略,而使用type属性时则使用按类型自动注入的策略。如果既不指定name也不指定type属性,这时将通过反射机制使用按名称自动注入策略进行注入。

@Resource装配顺序:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  2. 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。
  3. 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  4. 如果既没有指定name,又没有指定type,则自动按照按名称匹配方式进行装配;

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

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

相关文章

Android Compose的基本使用

前言: Compose这个东西呢,好处我没发现,坏处就是学习成本和低版本兼容. 不过,看在官方力推的份儿上,有空就学一下吧. 当初的kotlin,很多人说鸡肋(包括我)!现在不也咔咔用纯kotlin做项目吗?哈哈哈哈. 未来的事情,谁说得清呢? 首先创建一个专用的Compose项目 对没错!看到E…

体系结构论文(五十三):Featherweight Soft Error Resilience for GPUs 【22‘ MIRCO】

Featherweight Soft Error Resilience for GPUs 一、文章介绍 背景&#xff1a;软错误通常由高能粒子&#xff08;如宇宙射线和α粒子&#xff09;打击电路造成的位翻转&#xff0c;可能导致程序崩溃或产生错误输出。随着电子技术的进步&#xff0c;电路对这种辐射引发的软错…

Arduino UNO R3自学笔记14 之 Arduino使用HC-SR04模块如何测量距离?

注意&#xff1a;学习和写作过程中&#xff0c;部分资料搜集于互联网&#xff0c;如有侵权请联系删除。 前言&#xff1a;学习使用HC-SR04模块测距。 1.HC-SR04模块介绍 基本参数&#xff1a; ●使用电压&#xff1a;DC---5V ●静态电流&#xff1a;小于2mA ●电平输出&#…

【计算机网络】传输层UDP和TCP协议

目录 再谈端口号端口号范围划分认识知名端口号查看知名端口号两个问题 UDP协议UDP特点UDP的缓冲区基于UDP的应用层协议 TCP协议TCP协议格式确认应答机制超时重传机制连接管理机制&#xff08;三次握手与四次挥手&#xff09;理解TIME_WAIT状态理解CLOSE_WAIT状态滑动窗口快重传…

wsl(1) -- win11环境配置

1.前言 本专栏主要记录了我配置wsl的过程&#xff0c;以便日后回忆。 2. 开启WSL可选功能 打开设置&#xff0c;点击应用&#xff0c;点击可选功能&#xff0c;点击更多Windows功能&#xff0c;查看是否开启了【适用于Linux的Windows子系统】和【虚拟机平台】 3. 更新wsl …

【JavaEE初阶】深入理解多线程阻塞队列的原理,如何实现生产者-消费者模型,以及服务器崩掉原因!!!

前言&#xff1a; &#x1f308;上期博客&#xff1a;【JavaEE初阶】深入解析单例模式中的饿汉模式&#xff0c;懒汉模式的实现以及线程安全问题-CSDN博客 &#x1f525;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 ⭐️小编会在后端开发的学习中不断更新~~…

【在Linux世界中追寻伟大的One Piece】System V共享内存

目录 1 -> System V共享内存 1.1 -> 共享内存数据结构 1.2 -> 共享内存函数 1.2.1 -> shmget函数 1.2.2 -> shmot函数 1.2.3 -> shmdt函数 1.2.4 -> shmctl函数 1.3 -> 实例代码 2 -> System V消息队列 3 -> System V信号量 1 -> Sy…

K8S部署流程

一、war打包镜像(survey,analytics,trac系统) 代码打包成war准备tomcat的server.xml文件&#xff0c;修改connector中8080端口为项目的端口 修改前&#xff1a; <Connector port"8080" protocol"HTTP/1.1"connectionTimeout"20000"redirect…

idea环境下vue2升级vue3

在IDEA环境下&#xff0c;Vue2升级Vue3是一个非常重要的主题。在本文中&#xff0c;我们将介绍Vue2和Vue3之间的主要区别&#xff0c;以及如何在IDEA中升级Vue2项目到Vue3。我们还将讨论Vue3的新特性&#xff0c;如Composition API和Teleport等&#xff0c;并提供一些实用的代码…

快速掌握-vue3

是什么 vue2 的升级版&#xff0c; 使用 ts 重构了代码&#xff0c; 带来了 Composition API RFC。 类似于 react hook 的写法。 ts 重构&#xff0c;代码可读性更强vue3.x 使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty实现了 TreeShaking (当 Javascript 项目达到一定…

自闭症寄宿学校:为孩子发掘多重才能

在教育的广阔天地里&#xff0c;每一片土壤都孕育着不同的生命&#xff0c;每一颗种子都蕴含着无限的可能。对于自闭症儿童而言&#xff0c;他们的世界或许更加独特与复杂&#xff0c;但同样充满了未被发掘的潜能与才华。在广州&#xff0c;星贝育园自闭症儿童寄宿制学校正以满…

计算机毕业设计 Java酷听音乐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

师生健康信息管理:SpringBoot技术突破

第4章 系统设计 4.1 系统体系结构 师生健康信息管理系统的结构图4-1所示&#xff1a; 图4-1 系统结构 登录系统结构图&#xff0c;如图4-2所示&#xff1a; 图4-2 登录结构图 师生健康信息管理系统结构图&#xff0c;如图4-3所示。 图4-3 师生健康信息管理系统结构图 4.2…

【Linux】用虚拟机配置Ubuntu环境

目录 1.虚拟机安装Ubuntu系统 2.Ubuntu系统的网络配置 3.特别声明 首先我们先要下载VMware软件&#xff0c;大家自己去下啊&#xff01; 1.虚拟机安装Ubuntu系统 我们进去之后点击创建新的虚拟机&#xff0c;然后选择自定义 接着点下一步 再点下一步 进入这个界面之后&…

element-ui 通过按钮式触发日期选择器

element ui 写在前面1. 自定义的日期时间组件CustomDatePicker.vue2. 页面效果总结写在最后 写在前面 需求&#xff1a;elementui中日期时间选择器&#xff0c;目前只能通过点击input输入框触发日期选择器&#xff0c;我希望能通过其他方式触发日期选择器同时把input输入框去掉…

Spring的IOC和DI入门案例分析和实现

前言 IOC和DI是spring的核心之一&#xff0c;那我们为什么要使用spring技术呢&#xff1f;spring技术的优点在哪里&#xff1f; spring的特点&#xff1a; 简化开发&#xff0c;降低企业级开发的复杂性框架整合&#xff0c;高效整合其他技术&#xff0c;提高企业级应用的开发与…

【Python报错已解决】TypeError: ‘NoneType‘ object is not callable

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

【常读常悟】《大数据之路-阿里巴巴大数据实践》一书读书摘要

【常读常悟】《大数据之路-阿里巴巴大数据实践》一书读书摘要 1、背景2、目录结构3、数据加工链路4、章节摘要4.1 第2章 日志采集4.1.1 日志采集方案4.1.2 采集指标 4.2 第3章 数据同步4.2.1 数据的特点4.2.2 数据同步的三种方式4.2.3 数据同步的最佳实践 4.3 第4章 离线数据开…

LabVIEW自动生成NI-DAQmx代码

在现代数据采集和控制系统中&#xff0c;LabVIEW被广泛应用于各种工业和科研领域。其中&#xff0c;NI-DAQmx是一个强大的驱动程序&#xff0c;可以帮助用户高效地管理和配置数据采集任务。本文将介绍如何在LabVIEW中通过DAQ Assistant Express VI和任务常量自动生成NI-DAQmx代…

VBA字典与数组第十九讲:VBA中动态数组的定义及创建

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…