当前位置: 首页 > news >正文

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor

Spring源码系列文章

Spring源码解析(一):环境搭建

Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean

Spring源码解析(三):bean容器的刷新

Spring源码解析(四):单例bean的创建流程

Spring源码解析(五):循环依赖

Spring源码解析(六):bean定义后置处理器ConfigurationClassPostProcessor

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor


目录

  • 一、AutowiredAnnotationBeanPostProcessor简介
  • 二、determineCandidateConstructors(筛选候选构造函数)
  • 三、postProcessMergedBeanDefinition(查询@Autowired @Value属性)
    • 1、 findAutowiringMetadata
    • 2、checkConfigMembers
  • 四、postProcessProperties(属性填充)
    • 1、inject
    • 2、字段的属性注入
    • 3、方法的属性注入
    • 4、查询匹配的bean对象
      • 4.1、搜索类型匹配的bean的Map
      • 4.2、出现多个bean,如何筛选最后的bean
    • 5、冷知识:使用@Value进行依赖注入

一、AutowiredAnnotationBeanPostProcessor简介

类图如下:
在这里插入图片描述

  • SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
    • 过滤出可以作为构造注入的构造函数列表
  • MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
    • 查找bean的@Autowired @Value属性方法并缓存起来
  • InstantiationAwareBeanPostProcessor#postProcessProperties
    • @Autowired @Resource注解属性填充

AutowiredAnnotationBeanPostProcessor构造函数

  • autowiredAnnotationTypes 集合中保存了该类会处理的注解
  • autowiredAnnotationTypes 中添加了三个注解@Autowired@Value、和通过反射得到的javax.inject.Inject
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);...public AutowiredAnnotationBeanPostProcessor() {this.autowiredAnnotationTypes.add(Autowired.class);this.autowiredAnnotationTypes.add(Value.class);try {this.autowiredAnnotationTypes.add((Class<? extends Annotation>)ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}
}

二、determineCandidateConstructors(筛选候选构造函数)

  • 触发时机:bean实例化时候,获取bean的构造函数
  • 其作用是从注入bean的所有构造函数中过滤出可以作为构造注入的构造函数列表
@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)throws BeanCreationException {// 在这里首先处理了@Lookup注解// 判断是否已经解析过 。lookupMethodsChecked 作为一个缓存集合,保存已经处理过的beanif (!this.lookupMethodsChecked.contains(beanName)) {if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {try {Class<?> targetClass = beanClass;do {// 遍历bean中的每一个方法ReflectionUtils.doWithLocalMethods(targetClass, method -> {// 判断 方法是否被 @Lookup 修饰Lookup lookup = method.getAnnotation(Lookup.class);if (lookup != null) {Assert.state(this.beanFactory != null, "No BeanFactory available");// 如果被@Lookup 修饰,则封装后保存到RootBeanDefinition 的methodOverrides 属性中,在 SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory) 进行了cglib的动态代理。LookupOverride override = new LookupOverride(method, lookup.value());try {RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName);mbd.getMethodOverrides().addOverride(override);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(beanName,"Cannot apply @Lookup to beans without corresponding bean definition");}}});targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);}}// 将已经解析好的beanName 添加到缓存中this.lookupMethodsChecked.add(beanName);}// 这里开始处理构造函数// 获取bean的所有候选构造函数Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);if (candidateConstructors == null) {synchronized (this.candidateConstructorsCache) {candidateConstructors = this.candidateConstructorsCache.get(beanClass);// 如果候选构造构造函数为空if (candidateConstructors == null) {Constructor<?>[] rawCandidates;try {// 获取所有访问权限的构造函数rawCandidates = beanClass.getDeclaredConstructors();}catch (Throwable ex) {throw new BeanCreationException(beanName,"Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);}// 根据所有构造函数数量创建候选集合List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);// @Autowired(required = true) 的构造函数,有且只能有一个Constructor<?> requiredConstructor = null;// 默认的无参构造函数Constructor<?> defaultConstructor = null;// 针对 Kotlin 语言的构造函数,不太明白,一般为nullConstructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);int nonSyntheticConstructors = 0;for (Constructor<?> candidate : rawCandidates) {// 构造函数是否是非合成// 一般我们自己创建的都是非合成的// Java在编译过程中可能会出现一些合成的构造函数if (!candidate.isSynthetic()) {nonSyntheticConstructors++;}else if (primaryConstructor != null) {continue;}// 遍历autowiredAnnotationTypes集合// 判断当前构造函数是否被autowiredAnnotationTypes集合中的注解修饰,若未被修饰,则返回null// autowiredAnnotationTypes 集合中的注解在一开始就说了是 @Autowired、@Value 和 @Inject 三个。 MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);if (ann == null) {// 如果未被修饰,这里判断是否是 Cglib 的代理类,如果是则获取原始类,否则直接返回beanClassClass<?> userClass = ClassUtils.getUserClass(beanClass);// 如果这里不相等,肯定是通过 cglib的代理类,这里的userClass 就是原始类// 再次判断构造函数是否包含指定注解if (userClass != beanClass) {try {Constructor<?> superCtor =userClass.getDeclaredConstructor(candidate.getParameterTypes());ann = findAutowiredAnnotation(superCtor);}catch (NoSuchMethodException ex) {// Simply proceed, no equivalent superclass constructor found...}}}if (ann != null) {// 如果已经找到了必须装配的构造函数(requiredConstructor  != null)// 那么当前这个就是多余的,则抛出异常if (requiredConstructor != null) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' Autowired annotation already: " +requiredConstructor);}// 确定是否是必须的,@Autowired 和 @Inject 默认为true// @Autowired 可以通过 required 修改boolean required = determineRequiredStatus(ann);if (required) {// 如果当前构造函数为必须注入,但是候选列表不为空,则说明已经有构造函数适配,则抛出异常// 就是只要有required = true的构造函数就不允许存在其他可注入的构造函数if (!candidates.isEmpty()) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with 'required' Autowired annotation: " +candidate);}// 到这一步,说明当前构造函数是必须的,且目前没有其他构造函数候选// 直接将当前构造函数作为必须构造函数requiredConstructor = candidate;}// 添加到候选列表candidates.add(candidate);}// 如果 构造函数参数数量为0,则是默认构造函数,使用默认构造函数else if (candidate.getParameterCount() == 0) {defaultConstructor = candidate;}}// 如果候选构造函数不为空if (!candidates.isEmpty()) {// 将默认构造函数添加到可选构造函数列表中,作为回退if (requiredConstructor == null) {if (defaultConstructor != null) {candidates.add(defaultConstructor);}else if (candidates.size() == 1 && logger.isInfoEnabled()) {logger.info("Inconsistent constructor declaration on bean with name '" + beanName +"': single autowire-marked constructor flagged as optional - " +"this constructor is effectively required since there is no " +"default constructor to fall back to: " + candidates.get(0));}}candidateConstructors = candidates.toArray(new Constructor<?>[0]);}// 如果 当前bean只有一个有参构造函数,那么将此构造函数作为候选列表返回// (这就代表,如果bean中只有一个有参构造函数并不需要使用特殊注解,也会作为构造函数进行注入)else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {candidateConstructors = new Constructor<?>[] {rawCandidates[0]};}//下面这一段判断不是太理解else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};}else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {candidateConstructors = new Constructor<?>[] {primaryConstructor};}else {candidateConstructors = new Constructor<?>[0];}this.candidateConstructorsCache.put(beanClass, candidateConstructors);}}}return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

总结

  • 解析@Lookup 注解的方法,保存到 RootBeanDefinition 中
  • 从缓存中获取筛选好的构造函数列表,若有直接返回,没有则进行下一步
  • 通过反射获取bean 的所有构造函数,并进行构造函数遍历
    • 筛选每个构造函数是否被 @Autowired @Inject注解修饰
  • 当前构造函数没有被修饰,则判断当前bean是否Cglib动态代理类
    • 如果是,则获取原始类的构造函数
    • 再判断 构造函数是否被 @Autowired、@Inject 注解修饰
  • 如果筛选出候选构造函数
    • 如果有一个必须注入的构造函数(@Autowired(required =true)或者 @Inject )
      • 则不允许有其他候选构造函数出现
      • 有且只能筛选出一个必须注入的构造函数
    • 如果不存在必须注入的构造含函数 (@Autowired(required =false) 或者 @Inject)
      • 则允许多个候选注入构造函数出现(@Autowired(required = false) 修饰的构造函数)
      • 并且将这个几个候选构造函数返回
    • 如果bean有且只有一个构造函数
      • 即使没有被注解修饰,也会调用该构造函数作为bean创建的构造函使用

三、postProcessMergedBeanDefinition(查询@Autowired @Value属性)

  • 实例化之后会调用所有MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法
  • 找到所有的注入点,其实就是被@Autowired注解修饰的方法以及字段,同时静态的方法以及字段也会被排除
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}

1、 findAutowiringMetadata

  • 解析@Autowired @Value注解的信息,生成元数据
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {// 1.设置cacheKey的值(beanName 或者 className)String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// 2.检查beanName对应的InjectionMetadata是否已经存在于缓存中InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);// 3.检查InjectionMetadata是否需要刷新(为空或者class变了)if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {// 4.加锁后,再次从缓存中获取beanName对应的InjectionMetadatametadata = this.injectionMetadataCache.get(cacheKey);// 5.加锁后,再次检查InjectionMetadata是否需要刷新if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {// 6.如果需要刷新,并且metadata不为空,则先移除metadata.clear(pvs);}try {// 7.解析@Autowired注解的信息,生成元数据(包含clazz和clazz里解析到的注入的元素,// 这里的元素包括AutowiredFieldElement和AutowiredMethodElement)metadata = buildAutowiringMetadata(clazz);// 8.将解析的元数据放到injectionMetadataCache缓存,以备复用,每一个类只解析一次this.injectionMetadataCache.put(cacheKey, metadata);} catch (NoClassDefFoundError err) {throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() +"] for autowiring metadata: could not find class that it depends on", err);}}}}return metadata;
}

buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {// 1.用于存放所有解析到的注入的元素的变量LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();Class<?> targetClass = clazz;// 2.循环遍历do {// 2.1 定义存放当前循环的Class注入的元素(有序)final LinkedList<InjectionMetadata.InjectedElement> currElements =new LinkedList<InjectionMetadata.InjectedElement>();// 2.2 如果targetClass的属性上有@Autowired @Value注解,则用工具类获取注解信息ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {@Overridepublic void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {// 2.2.1 获取field上的@Autowired注解信息AnnotationAttributes ann = findAutowiredAnnotation(field);if (ann != null) {// 2.2.2 校验field是否被static修饰// 如果是则直接返回,因为@Autowired注解不支持static修饰的fieldif (Modifier.isStatic(field.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static fields: " + field);}return;}// 2.2.3 获取@Autowired注解的required的属性值//(required:值为true时,如果没有找到bean时,自动装配应该失败;false则不会)boolean required = determineRequiredStatus(ann);// 2.2.4 将field、required封装成AutowiredFieldElement,添加到currElementscurrElements.add(new AutowiredFieldElement(field, required));}}});// 2.3 如果targetClass的方法上有@Autowired注解,则用工具类获取注解信息ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {@Overridepublic void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {// 2.3.1 找到桥接方法Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);// 2.3.2 判断方法的可见性,如果不可见则直接返回if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}// 2.3.3 获取method上的@Autowired注解信息AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {// 2.3.4 校验method是否被static修饰,如果是则直接返回// 因为@Autowired注解不支持static修饰的methodif (Modifier.isStatic(method.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static methods: " + method);}return;}// 2.3.5 @Autowired注解标识在方法上的目的就是将容器内的Bean注入到方法的参数中,没有参数就违背了初衷if (method.getParameterTypes().length == 0) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation should only be used on methods with parameters: " +method);}}// 2.3.6 获取@Autowired注解的required的属性值boolean required = determineRequiredStatus(ann);// 2.3.7  获取method的属性描述器PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);// 2.3.8 将method、required、pd封装成AutowiredMethodElement,添加到currElementscurrElements.add(new AutowiredMethodElement(method, required, pd));}}});// 2.4 将本次循环获取到的注解信息添加到elementselements.addAll(0, currElements);// 2.5 在解析完targetClass之后,递归解析父类// 将所有的@Autowired的属性和方法收集起来,且类的层级越高其属性会被越优先注入targetClass = targetClass.getSuperclass();}// 2.6 递归解析targetClass父类(直至父类为Object结束)while (targetClass != null && targetClass != Object.class); // 2.7 将clazz和解析到的注入的元素封装成InjectionMetadatareturn new InjectionMetadata(clazz, elements);
}

总结

  • 遍历当前bean中的所有属性方法,过滤静态属性和方法
  • 属性:将field、required封装成AutowiredFieldElement
  • 方法:将method、required、pd(获取method的属性描述器)封装成AutowiredMethodElement
  • 将解析的元数据放到injectionMetadataCache缓存,以后统一处理

2、checkConfigMembers

  • 将所有需要注入的属性和方法添加到集合中,后面会使用
  • Member是Field和method的父类
public void checkConfigMembers(RootBeanDefinition beanDefinition) {Set<InjectedElement> checkedElements = new LinkedHashSet<InjectedElement>(this.injectedElements.size());// 1.遍历检查所有要注入的元素for (InjectedElement element : this.injectedElements) {Member member = element.getMember();// 2.如果beanDefinition的externallyManagedConfigMembers属性不包含该memberif (!beanDefinition.isExternallyManagedConfigMember(member)) {// 3.将该member添加到beanDefinition的externallyManagedConfigMembers属性beanDefinition.registerExternallyManagedConfigMember(member);// 4.并将element添加到checkedElementscheckedElements.add(element);}}// 5.赋值给checkedElements(检查过的元素)this.checkedElements = checkedElements;
}

四、postProcessProperties(属性填充)

  • 在postProcessProperties 方法中完成了Bean 中@Autowired@Inject@Value注解的属性填充
  • 上一步postProcessMergedBeanDefinition已经筛选出需要注入的属性放入injectionMetadataCache中
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 筛选出需要注入的属性类型InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {// 进行属性注入// 存在两种InjectionMetadata// 1.AutowiredFieldElement// 2.AutowiredMethodElement// 分别对应字段的属性注入以及方法的属性注入metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}

1、inject

  • inject 的实现很简单,遍历所有元素,调用元素的 inject 方法
  • 属性调用的是AutowiredFieldElement.inject
  • 方法调用的是 AutowiredMethodElement.inject
public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {// 如果checkedElements存在,则使用checkedElements,否则使用injectedElementsCollection<InjectedElement> elementsToIterate =(this.checkedElements != null ? this.checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {boolean debug = logger.isDebugEnabled();for (InjectedElement element : elementsToIterate) {if (debug) {logger.debug("Processing injected element of bean '" + beanName + "': " + element);}// 解析@Autowired注解生成的元数据类:AutowiredFieldElement、AutowiredMethodElement,// 这两个类继承InjectionMetadata.InjectedElement,各自重写了inject方法。element.inject(target, beanName, pvs);}}
}
  • findAutowiringMetadata中添加的注入元素的顺序先添加属性元素,再添加方法元素
  • 那么在 InjectionMetadata#inject 的遍历中也是先遍历属性元素,再遍历方法元素
  • 方法注入的优先级要高于属性注入,因为方法注入在属性注入后,会将属性注入的结果覆盖掉

2、字段的属性注入

  1. 获取属性field
  2. beanFactory.resolveDependency找到当前字段所匹配的Bean对象
  3. 将找的的Bean对象封装成ShortcutDependencyDescriptor对象作为缓存
    • 如果当前Bean是原型Bean,那么下次再来创建该Bean时
    • 就可以直接拿缓存的结果对象,不需要再次进行查找
  4. 利用反射将结果对象赋值给字段
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {// 1.拿到该元数据的属性值Field field = (Field) this.member;Object value;// 2.如果缓存中已经存在,则直接从缓存中解析属性(原型Bean)if (this.cached) {// 对于原型Bean,第一次创建的时候,也找注入点,然后进行注入,此时cached为false,注入完了之后cached为true// 第二次创建的时候,先找注入点(此时会拿到缓存好的注入点),也就是AutowiredFieldElement对象,此时cache为true,也就进到此处了// 注入点内并没有缓存被注入的具体Bean对象,而是beanName,这样就能保证注入到不同的原型Bean对象value = resolvedCachedArgument(beanName, this.cachedFieldValue);} else {// 3.把field和required属性,包装成desc描述类DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);TypeConverter typeConverter = beanFactory.getTypeConverter();try {// 4.核心逻辑: 进行依赖查找,找到当前字段所匹配的Bean对象value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);} catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {// 5.value不为空或者required为trueif (value != null || this.required) {// 6.如果属性依赖注入的bean不止一个(Array,Collection,Map),缓存cachedFieldValue放的是DependencyDescriptorthis.cachedFieldValue = desc;// 7.注册依赖关系到缓存(beanName 依赖 autowiredBeanNames)registerDependentBeans(beanName, autowiredBeanNames);// 8.如果属性依赖注入的bean只有一个(正常都是一个)if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName)) {// @Autowired标识属性类型和Bean的类型要匹配// 因此Array,Collection,Map类型的属性不支持缓存属性Bean名称// 9.检查autowiredBeanName对应的bean的类型是否为field的类型if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 10.将该属性解析到的bean的信息封装成ShortcutDependencyDescriptor,// 以便之后可以通过getBean方法来快速拿到bean实例this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}} else {this.cachedFieldValue = null;}// 11.缓存标识设为truethis.cached = true;}}}if (value != null) {// 12.设置字段访问性ReflectionUtils.makeAccessible(field);// 13.通过反射为属性赋值,将解析出来的bean实例赋值给fieldfield.set(bean, value);}
}

3、方法的属性注入

  • 逻辑几乎与字段注入方式一样
// 代码看着很长,实际上逻辑跟字段注入基本一样
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 判断XML中是否配置了这个属性,如果配置了直接跳过// 换而言之,XML配置的属性优先级高于@Autowired注解if (checkPropertySkipping(pvs)) {return;}Method method = (Method) this.member;Object[] arguments;if (this.cached) {arguments = resolveCachedArguments(beanName);} else {// 通过方法参数类型构造依赖描述符// 逻辑基本一样的,最终也是调用beanFactory.resolveDependency方法Class<?>[] paramTypes = method.getParameterTypes();arguments = new Object[paramTypes.length];DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();// 遍历方法的每个参数for (int i = 0; i < arguments.length; i++) {MethodParameter methodParam = new MethodParameter(method, i);DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);currDesc.setContainingClass(bean.getClass());descriptors[i] = currDesc;try {// 还是要调用这个方法Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);if (arg == null && !this.required) {arguments = null;break;}arguments[i] = arg;} catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);}}synchronized (this) {if (!this.cached) {if (arguments != null) {Object[] cachedMethodArguments = new Object[paramTypes.length];System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length);  // 注册bean之间的依赖关系registerDependentBeans(beanName, autowiredBeans);// 跟字段注入差不多,存在@Value注解,不进行缓存if (autowiredBeans.size() == paramTypes.length) {Iterator<String> it = autowiredBeans.iterator();for (int i = 0; i < paramTypes.length; i++) {String autowiredBeanName = it.next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);}}}this.cachedMethodArguments = cachedMethodArguments;} else {this.cachedMethodArguments = null;}this.cached = true;}}}if (arguments != null) {try {// 反射调用方法// 像我们的setter方法就是在这里调用的ReflectionUtils.makeAccessible(method);method.invoke(bean, arguments);} catch (InvocationTargetException ex) {throw ex.getTargetException();}}
}

4、查询匹配的bean对象

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// descriptor代表当前需要注入的那个字段,或者方法的参数,也就是注入点// ParameterNameDiscovery用于解析方法参数名称descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());// 所需要的类型是Optionalif (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}// 所需要的的类型是ObjectFactory,或ObjectProviderelse if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);} else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName);} else {// 在属性或set方法上使用了@Lazy注解,那么则构造一个代理对象并返回,真正使用该代理对象时才进行类型筛选BeanObject result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// ★ 核心步骤// descriptor表示某个属性或某个set方法// requestingBeanName表示正在进行依赖注入的Bean名称result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}

DefaultListableBeanFactory#doResolveDependency 处理属性依赖关系的核心方法

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 相当于打个点,记录下当前的步骤位置  返回值为当前的InjectionPoint InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {// 简单的说就是去Bean工厂的缓存里去看看,有没有名称为此的Bean,有就直接返回,没必要继续往下走了// 原型模式这里才有值Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}// 此处为:class com.fsx.bean.GenericBeanClass<?> type = descriptor.getDependencyType();//处理@Value注解 获取@Value中的value属性Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);// 若存在value值,那就去解析它// 也就是使用StringValueResolver处理器去处理一些表达式~~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());return (descriptor.getField() != null ?converter.convertIfNecessary(value, type, descriptor.getField()) :converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}// 如果descriptor所对应的类型是数组、Map这些// 就将descriptor对应的类型所匹配的所有bean方法,不用进一步做筛选了Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}// 获取所有【类型】匹配的Beans,形成一个Map(此处用Map装,是因为可能不止一个符合条件)// 该方法就特别重要了,对泛型类型的匹配、对@Qualifierd的解析都在这里面,下面详情分解Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);// 若没有符合条件的Bean。。。if (matchingBeans.isEmpty()) {// 并且是必须的,那就抛出没有找到合适的Bean的异常吧// 我们非常熟悉的异常信息:expected at least 1 bean which qualifies as autowire candidate...if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}String autowiredBeanName;Object instanceCandidate;//如果类型匹配的bean不止一个,Spring需要进行筛选,筛选失败的话继续抛出异常// 如果只找到一个该类型的,就不用进这里面来帮忙筛选了~~~~~~~~~if (matchingBeans.size() > 1) {// 该方法作用:从给定的beans里面筛选出一个符合条件的bean// Spring在查找依赖的时候遵循先类型再名称的原则(没有@Qualifier注解情况下)autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);// 无法推断出具体的名称if (autowiredBeanName == null) {// 如果依赖是必须的,直接抛出异常// 如果依赖不是必须的,但是这个依赖类型不是集合或者数组,那么也抛出异常if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(type, matchingBeans);}// Spring4.3之后才有:表示如果是required=false,或者就是List Map类型之类的,即使没有找到Bean,也让它不抱错,因为最多注入的是空集合嘛// 依赖不是必须的,但是依赖类型是集合或者数组,那么返回一个nullelse {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);}else {// 仅仅只匹配上一个,走这里 很简单  直接拿出来即可// 注意这里直接拿出来的技巧:不用遍历,直接用iterator.next()即可Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}// 把找到的autowiredBeanName 放进去if (autowiredBeanNames != null) {autowiredBeanNames.add(autowiredBeanName);}// 底层就是调用了beanFactory.getBean(beanName);  // 确保该实例肯定已经被实例化了的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;}// 再一次校验,type和result的type类型是否吻合if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());}return result;}// 最终把节点归还回来finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}

总结:

  1. 注入原型对象会从缓存中获取(其实是getBean创建新对象)
  2. 处理@Value注解,获取@Value中的value属性
  3. 对map、collection、数组类型的依赖进行处理
  4. 根据指定类型可能会找到多个bean
    • 如果一个都没找到
      • required=true(即依赖是必须的),抛出异常
      • required=false(即依赖不是必须的),返回null
    • 如果通过类型找到多个
      • 优先选择@Primary注解bean
      • 再选择@Priority注解优先级最高的(值最小
      • 最后根据名称匹配,还匹配不上则抛异常
    • 如果只找到一个
      • 就直接使用该bean

4.1、搜索类型匹配的bean的Map

  • 将获取类型匹配的Bean工作交给BeanFactoryUtils.beanNamesForTypeIncludingAncestors
    • 该方法除了当前beanFactory还会递归对父parentFactory进行查找
  • 如果注入类型是特殊类型或其子类(ApplicationContext、BeanFactory等等),会将特殊类型的实例添加到结果
  • 对结果进行筛选
    • BeanDefinition的autowireCandidate属性,表示是否允许该bena注入到其他bean中,默认为true
    • 泛型类型的匹配,如果存在的话
    • Qualifier注解。如果存在Qualifier注解的话,会直接比对Qualifier注解中指定的beanName(Spring处理自己定义的Qualifier注解,还支持javax.inject.Qualifier注解)
  • 如果筛选后,结果为空,Spring会放宽筛选条件,再筛选一次
protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {// 1.从BeanFactory中找出和requiredType所匹配的beanName,仅仅是beanName// 这些bean不一定经过了实例化,只有到最终确定某个Bean了// 如果这个Bean还没有实例化才会真正进行实例化String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());//记录所有匹配的bean, key-beanName, value-bean实例Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);// 2.注入类型是特殊类型或其子类// 比如你要注入ApplicationContext、BeanFactory等等for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {if (autowiringType.isAssignableFrom(requiredType)) {Object autowiringValue = this.resolvableDependencies.get(autowiringType);autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);if (requiredType.isInstance(autowiringValue)) {result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);break;}}}// 3.candidateNames可能会有多个,这里就要开始过滤了,比如@Qualifier、泛型等等for (String candidate : candidateNames) {// 不是自引用 && 符合注入条件// 不是自引用,什么是自引用?// 1.候选的Bean的名称跟需要进行注入的Bean名称相同,意味着,自己注入自己// 2.或者候选的Bean对应的factoryBean的名称跟需要注入的Bean名称相同,// 也就是说A依赖了B但是B的创建又需要依赖A// 符合注入条件// 检查泛型和@Qualifierif (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}// 4.结果集为空 && 注入属性是非数组、容器类型  那么Spring就会放宽注入条件,然后继续寻找// 什么叫放宽:比如泛型不要求精确匹配了、比如自引用的注入等等if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {// 是泛型,就需要获取真实的类型,然后进行匹配DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}// 5. 如果result为空, 则表明依赖的就是自己,则将自己添加到result中if (result.isEmpty()) {for (String candidate : candidateNames) {if (isSelfReference(beanName, candidate) &&(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}}}return result;
}

4.2、出现多个bean,如何筛选最后的bean

  • 从多个Bean中,筛选出一个符合条件的Bean
@Nullable
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {Class<?> requiredType = descriptor.getDependencyType();// 看看传入的Bean中有没有标注了@Primary注解的String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);// 如果找到了 就直接返回// 由此可见,@Primary的优先级还是非常的高的if (primaryCandidate != null) {return primaryCandidate;}//找到一个标注了javax.annotation.Priority注解的。(备注:优先级的值不能有相同的,否则报错)String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);if (priorityCandidate != null) { return priorityCandidate;}// 这里是最终的处理(相信绝大部分情况下,都会走这里~~~~~~~~~~~~~~~~~~~~)// 此处就能看出resolvableDependencies它的效能了,他会把解析过的依赖们缓存起来,不用再重复解析了for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateName = entry.getKey();Object beanInstance = entry.getValue();// 到这一步就比较简单了,matchesBeanName匹配上Map的key就行。// 需要注意的是,bean可能存在很多别名,所以只要有一个别名相同,就认为是能够匹配上的  具体参考AbstractBeanFactory#getAliases方法//descriptor.getDependencyName() 这个特别需要注意的是:如果是字段,这里调用的this.field.getName() 直接用的是字段的名称// 因此此处我们看到的情况是,我们采用@Autowired虽然匹配到两个类型的Bean了,即使我们没有使用@Qualifier注解,也会根据字段名找到一个合适的(若没找到,就抱错了)if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||matchesBeanName(candidateName, descriptor.getDependencyName())) {return candidateName;}}return null;
}

筛选@Primary注解的Bean

  • @Primary只能标注一个在同类型的Bean上
  • 多个会抛出异常
@Nullable
protected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) {String primaryBeanName = null;for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateBeanName = entry.getKey();Object beanInstance = entry.getValue();// isPrimary就是去看看容器里(包含父容器)对应的Bean定义信息是否有@Primary标注if (isPrimary(candidateBeanName, beanInstance)) {if (primaryBeanName != null) {boolean candidateLocal = containsBeanDefinition(candidateBeanName);boolean primaryLocal = containsBeanDefinition(primaryBeanName);// 这个相当于如果已经找到了一个@Primary的,然后又找到了一个 那就抛出异常// @Primary只能标注到一个同类型的Bean上if (candidateLocal && primaryLocal) {throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),"more than one 'primary' bean found among candidates: " + candidates.keySet());}else if (candidateLocal) {primaryBeanName = candidateBeanName;}}// 把找出来的标注了@Primary的Bean的名称返回出去else {primaryBeanName = candidateBeanName;}}}return primaryBeanName;
} 

筛选@Priority注解优先级最高的Bean

  • @Priority虽然可以标注多个,但是里面的优先级值,不能出现相同的
  • @Priority是JSR 250标准,值越优先级越高
  • 如果优先级的值相等,是不允许的,会抛出异常
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {String highestPriorityBeanName = null;Integer highestPriority = null;for (Map.Entry<String, Object> entry : candida         tes.entrySet()) {String candidateBeanName = entry.getKey();Object beanInstance = entry.getValue();if (beanInstance != null) {//AnnotationAwareOrderComparator#getPriority// 这里就是为了兼容JDK6提供的javax.annotation.Priority这个注解,然后做一个优先级排序// 注意注意注意:这里并不是@Order,和它木有任何关系~~~// 它有的作用像Spring提供的@Primary注解Integer candidatePriority = getPriority(beanInstance);// 大部分情况下,我们这里都是null,但是需要注意的是,@Primary只能标注一个,这个虽然可以标注多个,但是里面的优先级值,不能出现相同的(强烈建议不要使用~~~~而使用@Primary)if (candidatePriority != null) {if (highestPriorityBeanName != null) {// 如果优先级的值相等,是不允许的,这里需要引起注意,个人建议一般还是使用@Primary吧if (candidatePriority.equals(highestPriority)) {throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),"Multiple beans found with the same priority ('" + highestPriority +"') among candidates: " + candidates.keySet());}else if (candidatePriority < highestPriority) {highestPriorityBeanName = candidateBeanName;highestPriority = candidatePriority;}}else {highestPriorityBeanName = candidateBeanName;highestPriority = candidatePriority;}}}}return highestPriorityBeanName;
}

5、冷知识:使用@Value进行依赖注入

  • AutowiredAnnotationBeanPostProcessor不仅处理@Autowired也处理@Value
@Configuration
public class Config {@Beanpublic Person person() {return new Person();}// 这样就能够实现依赖注入了@Value("#{person}")private Person person;
}

注意

  • 只能是#{person}而不能是${person}
  • person表示beanName,因此请保证此Bean必须存在
    • 比如若写成这样@Value(“#{person2}”)就报错:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'person2' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217)at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104)

@Value(#{})与@Value(${})的区别

  • @Value("#{}"): 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量
  • @Value(${}):获取配置文件中的属性值
  • 它俩可以结合使用:比如:@Value("#{'${spring.redis.cluster.nodes}'.split(',')}")是一个结合使用的案例~ 这样就可以把如下配置解析成List了
spring.redis.cluster.nodes=10.102.144.94:7535,10.102.144.94:7536,10.102.144.95:7535,10.102.144.95:7536,10.102.148.153:7535,10.102.148.153:7536

相关文章:

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor

Spring源码系列文章 Spring源码解析(一)&#xff1a;环境搭建 Spring源码解析(二)&#xff1a;bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三)&#xff1a;bean容器的刷新 Spring源码解析(四)&#xff1a;单例bean的创建流程 Spring源码解析(五)&…...

【C#学习笔记】引用类型(1)

文章目录 引用类型class匿名类 记录引用相等和值相等record声明 接口delegate 委托合并委托/多路广播委托 引用类型 引用类型的变量存储对其数据&#xff08;对象&#xff09;的引用&#xff0c;而值类型的变量直接包含其数据。 对于引用类型&#xff0c;两种变量可引用同一对…...

STM32CubeMX+VSCODE+EIDE+RT-THREAD 工程创建

Eide环境搭建暂且不表&#xff0c;后续补充。主要记录下Vscode环境下 创建Rt-thread工程的过程。分别介绍STM32CubeMX添加rtt支持包的方式和手动添加rtt kernel方式。STM32CubeMX生成工程的时候有"坑"&#xff0c;防止下次忘记&#xff0c;方便渡一下有缘人&#xff…...

java中javamail发送带附件的邮件实现方法

java中javamail发送带附件的邮件实现方法 本文实例讲述了java中javamail发送带附件的邮件实现方法。分享给大家供大家参考。具体分析如下&#xff1a; JavaMail&#xff0c;顾名思义&#xff0c;提供给开发者处理电子邮件相关的编程接口。它是Sun发布的用来处理email的API。它…...

Stable Diffusion高阶技能(2)-稳定扩散百态:解密AI绘画工具「SD WebUI」的提示词高级使用策略

简介 在我们的生活中,艺术元素可谓无处不在,而处于中心地位的绘画,无疑是携带着强烈的艺术魅力。现如今随着AI技术的日新月异,AI绘画对我们的生活世界的改造影响越来越深远。那么,如何让我们在AI绘画工具中更好的指导AI完成我们心中的作品呢? 这需要我们玩转这个工具的…...

【果树农药喷洒机器人】Part2:机器人变量喷药系统硬件选型

本专栏介绍:付费专栏,持续更新机器人实战项目,欢迎各位订阅关注。 关注我,带你了解更多关于机器人、嵌入式、人工智能等方面的优质文章! 文章目录 一、引言二、变量喷药系统总体要求2.1系统功能要求2.2系统技术要求三、机器人关键硬件选型3.1深度相机概述与选型3.2单片机选…...

解决vite+vue3项目npm装包失败

报错如下&#xff1a; Failed to remove some directories [ npm WARN cleanup [ npm WARN cleanup D:\\V3Work\\v3project\\node_modules\\vue, npm WARN cleanup [Error: EPERM: operation not permitted, rmdir D:\V3Work\v3project\node_modules\vue\reactivity\…...

Rust之错误处理

在Rust中&#xff0c;将错误分为两种&#xff0c;可恢复错误和不可恢复错误。所谓可恢复错误就是指类似于文件未找到这类错误&#xff0c;一般需要将它们报告给用户并再次尝试进行操作&#xff0c;而不可恢复错误往往就是Bug&#xff0c;需要停止程序的运行。 1、不可恢复错误…...

docker compose快速编排

Docker-compose概述 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容集群的快速编排 Docker-Compose将所管理的容器分为三层&#xff0c;分别是工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&#xff09;以及容器&#x…...

java.io.File类的使用

文章目录 概述构造器常用方法1、获取文件和目录基本信息2、列出目录的下一级3.File类的重命名功能4、判断功能的方法5、创建、删除功能 练习 概述 File类及本章下的各种流&#xff0c;都定义在java.io包下。一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录&#…...

TypeScript技能总结(三)

typescript是js的超集&#xff0c;目前很多前端框架都开始使用它来作为项目的维护管理的工具&#xff0c;还在不断地更新&#xff0c;添加新功能中&#xff0c;我们学习它&#xff0c;才能更好的在的项目中运用它&#xff0c;发挥它的最大功效 //泛型 > 参数和返回值类型相…...

python绿色版运行程序,python 绿色版免安装

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python绿色版运行程序&#xff0c;python 绿色版免安装&#xff0c;今天让我们一起来看看吧&#xff01; 软件简介 Python3.7.0 是一种被广大从业者广泛使用的通用型设计语言。该软件提供了丰富全面的模块&#xff0c;并…...

Python 向Excel写数据

1.项目终端导入 xlwt 库 pip install xlwt2.导入依赖包 import xlwt3.创建Excel表格类型文件 调用xlwt模块中的Workbook方法来创建一个excel表格类型文件&#xff0c;其中的第一个参数是设置数据的编码格式&#xff0c;这里是’utf-8’的形式&#xff0c;style_compression设…...

MySQL(1)

MySQL创建数据库和创建数据表 创建数据库 1. 连接 MySQL mysql -u root -p 2. 查看当前的数据库 show databases; 3. 创建数据库 create database 数据库名; 创建数据库 4. 创建数据库时设置字符编码 create database 数据库名 character set utf8; 5. 查看和显示…...

Android10 Recovery系列(二)增加OTG升级功能

一 、背景 起因是遇到了客户有这个需求,本着了解的原则,去看了一下之前Android版本的代码,想看看之前有没有现成的实现,移植过来。结果很不幸,没有找到。于是自己开始了功能实现的过程。下面分享一下该功能的实现 二 、准备工作 首先简单了解一下Recovery 模块的系统升…...

el-popover使用自定义图标

使用el-popover实现鼠标点击或浮动到自定义图标上弹出表格弹窗&#xff0c;官方文档上使用的是按钮el-button&#xff0c;如果想换成图标或其他的组件的话直接把el-button替换掉即可。注意替换之后的组件一定要加slot“reference”&#xff0c;不然组件是显示不出来的。 代码如…...

KCOM4串口转键鼠控制线测试说明

1.KOCM4介绍 KCOM4是一款最新开发的串口转键盘鼠标控制线&#xff0c;采用32位内核&#xff0c;最大60Mhz的工作频率&#xff0c;完美适用于游戏挂机等应用场景&#xff08;如果是用在工作电脑控制或展厅电脑控制推荐CH9329双头线&#xff09;。KCOM4支持普通键盘、相对鼠标、…...

2023华数杯数学建模C题完整5问代码思路分析

目前已经写出2023华数杯C题母亲身心健康对婴儿成长的影响全部5问的完整代码和42页论文&#xff08;正文30页&#xff0c;论文部分摘要如下&#xff1a; 本文共解决了五个问题&#xff0c;涉及婴儿行为特征、睡眠质量与母亲的身体指标和心理指标的关系&#xff0c;以及如何优化…...

02_kafka_基本概念_基础架构

文章目录 常见的消息队列工作模式基本概念kafka 特性Kafka 基本架构topic 分区的 目的/ 好处 日志存储形式消费者&#xff0c;消费方式 逻辑消费组 高性能写入&#xff1a; 顺序写 mmap读取&#xff1a;零拷贝DMA 使用场景 常见的消息队列工作模式 至多一次&#xff1a;消息被…...

HTTP 常用状态码 301 302 304 403

HTTP 常用状态码 301 302 304 403 301 永久重定向&#xff0c;浏览器会把重定向后的地址缓存起来&#xff0c;将来用户再次访问原始地址时&#xff0c;直接引导用户访问新地址 302 临时重定向&#xff0c;浏览器会引导用户进入新地址&#xff0c;但不会缓存原始地址&#xff0c…...

分布式 - 服务器Nginx:一小时入门系列之静态网页配置

文章目录 1. 静态文件配置2. nginx listen 命令解析3. nginx server_name 命令解析4. nginx server 端口重复5. nginx location 命令 1. 静态文件配置 在 /home 文件下配置一个静态的AdminLTE后台管理系统&#xff1a; [rootnginx-dev conf.d]# cd /home [rootnginx-dev home…...

kubernetes网络之网络策略-----Network Policies - Example

创建一个Deployment并配置Service 创建一个 nginx Deployment 用于演示 Kubernetes 的 NetworkPolicy&#xff1a; kubectl create deployment nginx --imagenginx 输出结果 deployment.apps/nginx created通过Service暴露该Deployment kubectl expose deployment nginx --po…...

【GDI/GDI+】如何抓取屏幕保存到bitmap文件?

问题 如何抓取屏幕保存到bitmap文件&#xff1f; 方法 GDI 方法 1、抓取。 HBITMAP CRectChartUI::GetBitmap(HDC hDC) {HDC hMemDC;int x, y;int nWidth, nHeight;HBITMAP hBitmap, hOldBitmap;hMemDC CreateCompatibleDC(hDC);nWidth GetDeviceCaps(hDC, HORZRES);nHei…...

HDFS介绍

目录 ​编辑 一、HDFS基础 1.1 概述 1.2 HDFS的设计目标 1.2.1 硬件故障 1.2.2 流式数据访问 1.2.3 超大数据集 1.2.4 简单的一致性模型 1.2.5 移动计算而不是移动数据 1.2.6 跨异构硬件和软件平台的可移植性 1.3 基础概念 1.3.1 块&#xff08;Block&#xff09; 1.3.2 复制…...

每日一题——两数之和

题目 给出一个整型数组 numbers 和一个目标值 target&#xff0c;请在数组中找出两个加起来等于目标值的数的下标&#xff0c;返回的下标按升序排列。 &#xff08;注&#xff1a;返回的数组下标从1开始算起&#xff0c;保证target一定可以由数组里面2个数字相加得到&#xff0…...

Maven: ‘mvn‘ is not recognized as an internal or external command

下载并配置好Maven之后&#xff0c;CMD测试安装是否成功&#xff1a;mvn -v 提示&#xff1a; mvn is not recognized as an internal or external command, operable program or batch file. 检查环境变量&#xff1a; MAVEN_HOME: %MAVEN_HOME%\bin: 看上去没问题&#x…...

CubeSLAM: Monocular 3D Object SLAM——论文简述

一、简介 提出一种在动态和静态环境中同时进行3D目标检测和定位建图的方法&#xff0c;并且能够互相提升准确度。具体地&#xff0c;对于3D目标&#xff0c;其位置、方向和尺寸通过slam进行了优化&#xff1b;而3D目标作为slam中的路标&#xff0c;可以提供额外的语义和几何约…...

【雕爷学编程】MicroPython动手做(30)——物联网之Blynk 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…...

linux scp 拷贝文件到目标linux系统

scp -P 8866 -r jsonrpc/ root192.168.6.66:/folder_path...

Oracle-expdp报错ORA-39077、06502(Bug-16928674)

问题: 用户在使用expdp进程导出时&#xff0c;出现队列报错ORA-39077、ORA-06502 ORA-31626: job does not exist ORA-31638: cannot attach to job SYS_EXPORT_SCHEMA_01 for user SYS ORA-06512: at "SYS.DBMS_SYS_ERROR", line 95 ORA-06512: at "SYS.KUPV$…...