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

news2024/11/28 16:30:37

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 作为一个缓存集合,保存已经处理过的bean
	if (!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 语言的构造函数,不太明白,一般为null
				Constructor<?> 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 的代理类,如果是则获取原始类,否则直接返回beanClass
						Class<?> 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对应的InjectionMetadata
            metadata = 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() {
            @Override
            public 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修饰的field
                    if (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,添加到currElements
                    currElements.add(new AutowiredFieldElement(field, required));
                }
            }
        });
 
        // 2.3 如果targetClass的方法上有@Autowired注解,则用工具类获取注解信息
        ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
            @Override
            public 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修饰的method
                    if (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,添加到currElements
                    currElements.add(new AutowiredMethodElement(method, required, pd));
                }
            }
        });
 
        // 2.4 将本次循环获取到的注解信息添加到elements
        elements.addAll(0, currElements);
        // 2.5 在解析完targetClass之后,递归解析父类
        // 将所有的@Autowired的属性和方法收集起来,且类的层级越高其属性会被越优先注入
        targetClass = targetClass.getSuperclass();
    }
    // 2.6 递归解析targetClass父类(直至父类为Object结束)
    while (targetClass != null && targetClass != Object.class); 
 
    // 2.7 将clazz和解析到的注入的元素封装成InjectionMetadata
    return 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属性不包含该member
        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
            // 3.将该member添加到beanDefinition的externallyManagedConfigMembers属性
            beanDefinition.registerExternallyManagedConfigMember(member);
            // 4.并将element添加到checkedElements
            checkedElements.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,否则使用injectedElements
    Collection<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为true
                if (value != null || this.required) {
                    // 6.如果属性依赖注入的bean不止一个(Array,Collection,Map),缓存cachedFieldValue放的是DependencyDescriptor
                    this.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.缓存标识设为true
                this.cached = true;
            }
        }
    }
    if (value != null) {
        // 12.设置字段访问性
        ReflectionUtils.makeAccessible(field);
        // 13.通过反射为属性赋值,将解析出来的bean实例赋值给field
        field.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());

	// 所需要的类型是Optional
	if (Optional.class == descriptor.getDependencyType()) {
		return createOptionalDependency(descriptor, requestingBeanName);
	}
	// 所需要的的类型是ObjectFactory,或ObjectProvider
	else 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注解,那么则构造一个代理对象并返回,真正使用该代理对象时才进行类型筛选Bean
		Object 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.GenericBean
		Class<?> 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,也让它不抱错,因为最多注入的是空集合嘛
				// 依赖不是必须的,但是依赖类型是集合或者数组,那么返回一个null
				else {
					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
        // 符合注入条件
        // 检查泛型和@Qualifier
		if (!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 {
    @Bean
    public 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

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

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

相关文章

IBM HR Analytics 员工流失 EDA 和可视化绩效分析

IBM HR Analytics 员工流失与绩效分析 背景导入库输出前五行数据清洗检查空值删除不必要的列 可视化商务旅行直方图离家的距离箱形图教育与数字公司的关系年龄和月收入散点图按教育领域和工作角色划分的工作满意度相关矩阵的交互式热图 背景 揭示导致员工流失的因素&#xff0…

陪诊小程序开发|陪诊系统定制|数字化医疗改善就医条件

健康问题这几年成为人们关注的焦点之一&#xff0c;然而看病却是一个非常麻烦的过程&#xff0c;特别是对于那些身处陌生城市或者不熟悉就医流程的人来说。幸运的是现在有了陪诊小程序下&#xff0c;为您提供便捷的助医服务&#xff0c;使得就医过程得更加简单和轻松。 陪诊系统…

国联易安网页防篡改保护系统“渠道招募”启动啦!

作为业内专注于保密与非密领域的分级保护、等级保护、业务连续性安全和大数据安全的领军企业&#xff0c;国联易安网页防篡改保护系统基于“高效同步”、“安全传输”两项技术&#xff0c;具备了独特的“五重防护”新特性&#xff0c;支持网页的全自动发布、网页监控、报警和自…

webpack基础知识十:与webpack类似的工具还有哪些?区别?

一、模块化工具 模块化是一种处理复杂系统分解为更好的可管理模块的方式 可以用来分割&#xff0c;组织和打包应用。每个模块完成一个特定的子功能&#xff0c;所有的模块按某种方法组装起来&#xff0c;成为一个整体(bundle) 在前端领域中&#xff0c;并非只有webpack这一款…

c51单片机16个按键密码锁源代码(富proteus电路图)

注意了&#xff1a;这个代码你是没法直接运行的&#xff0c;但是如果你看得懂&#xff0c;随便改一改不超过1分钟就可以用 #include "reg51.h" #include "myheader.h" void displayNumber(unsigned char num) {if(num1){P10XFF;P10P11P14P15P160;}else if…

js修改img的src属性显示变换图片到前端页面,img的src属性显示java后台读取返回的本地图片

文章目录 前言一、HTML 图像- 图像标签&#xff08; <img>&#xff09;1.1图像标签的源属性&#xff08;Src&#xff09;1.2图像标签源属性&#xff08;Src&#xff09;显示项目中图片1.3图像标签源属性&#xff08;Src&#xff09;显示网络图片 二、图像标签&#xff08…

韦东山Linux驱动入门实验班(6)LED驱动---设备树

前言 &#xff08;1&#xff09;在韦东山Linux驱动入门实验班&#xff08;5&#xff09;LED驱动—驱动分层和分离&#xff0c;平台总线模型我们已经讲解了如何将驱动程序和硬件程序进行剥离。但是大佬们感觉这样还不行&#xff0c;他们认为要专门弄一个结构存储硬件信息&#x…

树,森林的遍历,以及其与二叉树遍历之间的关系

树和森林的的遍历 树的遍历 先根遍历 以下列树为演示 首先将树转化成二叉树&#xff08;孩子兄弟表示法&#xff1a;就是每个节点的左边连着它的左孩子&#xff0c;右边连自己右边的第一个兄弟&#xff09; 然后把转化为的二叉树进行先序遍历&#xff0c;中序遍历 进行先序…

【c语言初级】c++基础

文章目录 1. C关键字2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入&输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.2 C函数重载的原理--名字修饰采用C语言编译器编译后结果 1. C关键字 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想…

Spring Cloud Alibaba官方网站

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 官方网站 SCA&#xff08;Spring Cloud Alibaba&#xff09;为分布式应用开发提供一站式解决方案。它包含开发分布式应用程序所需的所有核心组件&#xff0c;使您可以轻松地…

LeetCode 周赛上分之旅 #38 结合排序不等式的动态规划

⭐️ 本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架&#xff0c;你的思考越抽象&#xff0c;它能覆盖的问题域就越广&#xff0c;理解难度…

思维能力的学习

前言 在工作中&#xff0c;随着工作时间的增长&#xff0c;我们与他人的差异不是知识本身的差异&#xff0c;主要是思维方面的差异&#xff0c;所以我们需要培养自己的思维能力。 思维能力的学习 思维是一个具备内在框架和逻辑的系统工程&#xff0c;思维覆盖了学习、认知、问…

43..利用fsolve函数解对应lambda下的方程组(matlab程序)

1.简述 fsolve的基本用法 : x fsolve(fun,x0) 其中fun应为函数句柄&#xff0c;x0为搜索的种子&#xff0c;即预估的fun0的解的大致位置。 函数句柄的定义方式主要有两种&#xff1a; 1.定义函数文件&#xff0c;使用操作符 定义function文件root2d.m, 如下&#xff1a; …

nvm下载node导致npm报错无法使用

有个依赖库需要更新下node&#xff0c;用nvm下载后项目跑不起来了&#xff0c;npm -v 还报错 其实一开始是npm下载不来&#xff0c;然后换了淘宝镜像后还是报错 然后就只能手动下载下了 进入node.js官网 https://nodejs.org/en/download 下载后注意要安装在你nvm目录中&#x…

httpd+Tomcat(jk)的Web动静分离搭建

动静分离是指将动态请求和静态请求分别交给不同的服务器来处理&#xff0c;可以提高服务器的效率和性能。在Java Web开发中&#xff0c;常见的动态请求处理方式是通过Tomcat来处理&#xff0c;而静态请求则可以通过Apache服务器来处理。本文将详细讲解如何结合Apache和Tomcat来…

MySQL的数据插入总结(不存在就插入,存在就更新)

MySQL的数据插入总结(不存在就插入&#xff0c;存在就更新) 1. on duplicate key update 当在insert语句后面带上ON DUPLICATE KEY UPDATE 子句&#xff0c;而要插入的行与表中现有记录的惟一索引或主键中产生重复值&#xff0c;那么就会发生旧行的更新&#xff1b;如果插入的…

高性能计算集群使用

一、PuTTY的下载与安装 PuTTY是一款开源的连接软件&#xff0c;是 SSH、Telnet、Rlogin 和 SUPDUP 网络协议的客户端程序。 下载网址&#xff1a;Download PuTTY - a free SSH and telnet client for Windows 安装好后连接自己的服务器 输入用户名和密码&#xff0c;回车登录…

前端安全XSS和CSRF讲解

文章目录 XSSXSS攻击原理常见的攻击方式预防措施 CSRFCSRF攻击原理常见攻击情景预防措施&#xff1a; CSRF和XSS的区别 XSS 全称Cross Site Scripting&#xff0c;名为跨站脚本攻击。为啥不是单词第一个字母组合CSS&#xff0c;大概率与样式名称css进行区分。 XSS攻击原理 不…

【数据结构】单链表OJ题(二)

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、链表分割 &#x1f4a1;方法一&#xff1a; 二、链表的回文 &#x…