【Spring专题】Spring之Bean的生命周期源码解析——阶段二(二)(IOC之属性填充/依赖注入)

目录

  • 前言
    • 阅读准备
    • 阅读指引
    • 阅读建议
  • 课程内容
    • 一、依赖注入方式(前置知识)
      • 1.1 手动注入
      • 1.2 自动注入
        • 1.2.1 XML的autowire自动注入
          • 1.2.1.1 byType:按照类型进行注入
          • 1.2.1.2 byName:按照名称进行注入
          • 1.2.1.3 constructor:按照构造方法进行注入
          • 1.2.1.4 其他
          • 1.2.1.5 XML的autowire自动注入方式总结
        • 1.2.2 @Autowired注解的自动注入
        • 1.2.3 自动注入总结
    • 二、依赖注入过程
      • 2.1 简单回顾
      • 2.2 概念回顾
      • 2.3 核心方法讲解
    • 三、【寻找注入点】方法讲解
      • 3.1 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors:寻找注入点代码入口
      • 3.2 AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
      • 3.3 AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata:寻找注入点
      • *3.4 AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata:构建注入点
      • 3.5 ReflectionUtils#doWithLocalFields:利用反射遍历类上的【字段】
      • 3.6 AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation:寻找字段上的自动装配注解
      • 3.7 Modifier.isStatic
      • 3.8 剩余步骤
    • 四、【寻找注入点】逻辑流程图
    • 四点五、==特别声明==
    • 五、【属性填充】逻辑流程图
    • 六、【属性填充】方法讲解
      • 6.1 AbstractAutowireCapableBeanFactory#populateBean
      • 6.2 InstantiationAwareBeanPostProcessor#postProcessProperties:处理属性
      • 6.3 InjectionMetadata#inject:根据注入点注入
      • 6.4 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject:字段类属性注入【入口】
      • 6.5 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue:解决字段属性注入【入口】
      • 6.6 DefaultListableBeanFactory#resolveDependency:解决属性注入【入口】
      • 6.7 DefaultListableBeanFactory#doResolveDependency:解决属性注入,真正干活的地方
  • 学习总结

前言

阅读准备

由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序):

  • Spring底层核心原理解析【学习难度:★★☆☆☆
  • 手写简易Spring容器过程分析【学习难度:★★☆☆☆
  • Spring之底层架构核心概念解析【学习难度:★★★☆☆,重要程度:★★★★★
  • Bean的生命周期流程图【学习难度:☆☆☆☆☆,重要程度:★★★★★
  • Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)【学习难度:★★☆☆☆,重要程度:★★★☆☆
  • Spring之Bean的生命周期源码解析——阶段二(IOC之实例化)【学习难度:★★★★★,重要程度:★★★☆☆

(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)

阅读指引

我们在上一节课已经说到过了,本次Spring源码剖析的总入口是new AnnotationConfigApplicationContext("org.tuling.spring");,这里就不再重复解释了。本节课要说的内容,是SpringIOC的属性填充/依赖注入,我们这里直接给到入口吧,调用链如下:(调用链比较深,不要纠结细枝末节)

  1. AbstractApplicationContext#refresh:刷新方法,不用在意
  2. AbstractApplicationContext#finishBeanFactoryInitialization:在这里实例化所有剩余的(非lazy-init)单例
  3. DefaultListableBeanFactory#preInstantiateSingletons:在这里实例化所有剩余的(非lazy-init)单例(上面的方法,核心干活的方法就是这里)
  4. DefaultListableBeanFactory#getBean:获取Bean的方法
  5. AbstractBeanFactory#doGetBean:返回指定bean的一个实例,它可以是共享的,也可以是独立的
  6. 上面这个AbstractBeanFactory#doGetBean里面的一段局部代码写的回调方法,如下:
	// 如果是单例创建bean实例
	if (mbd.isSingleton()) {
		sharedInstance = getSingleton(beanName, () -> {
			try {
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				destroySingleton(beanName);
				throw ex;
			}
		});
		beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
	}
  1. AbstractAutowireCapableBeanFactory#createBean:这个类的中心方法:创建一个bean实例,填充bean实例,应用后处理器,等等。
  2. AbstractAutowireCapableBeanFactory#doCreateBean:【实例化】及后面声明周期调用地方。
  3. 【入口一】AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors:应用合并BeanDefinition后置处理器给给定的BeanDefinition。
  4. 【入口二】AbstractAutowireCapableBeanFactory#populateBean:使用来自bean定义的属性值在给定的BeanWrapper中填充bean实例。

如上面的调用链所示,最后两个方法,才是我们本次要研究的核心方法。为什么这里会说有两个入口呢?主要是,本章的【属性填充/依赖注入】将分为两个部分来解析。【入口一】对应的是【第一部分:寻找注入点】;【入口二】对应的是【第二部分:属性填充及填充后】

阅读建议

  1. 看源码,切记纠结细枝末节,不然很容易陷进去。正常来说,看主要流程就好了
  2. 遇到不懂的,多看看类注释或者方法注释。Spring这种优秀源码,注释真的非常到位
  3. 如果你是idea用户,多用F11的书签功能。
    • Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签 (必备)
    • Shift + F11 弹出书签显示层 (必备)
    • Ctrl +1,2,3…9 定位到对应数值的书签位置 (必备)

课程内容

一、依赖注入方式(前置知识)

在Spring中,属性注入的方式分为两种,分别是:【手动注入】和【自动注入】。

1.1 手动注入

在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值。如下:

<bean name="userService" class="com.luban.service.UserService">
 <property name="orderService" ref="orderService"/>
</bean>

有经验的同学应该知道,上面这种底层是通过setXxx方法进行注入的。另外,还有一种方式,是通过构造方法进行注入的,如下:

<bean name="userService" class="com.luban.service.UserService">
 <constructor-arg index="0" ref="orderService"/>
</bean>

所以手动注入的底层也就是分为两种:【set方法注入】和【构造方法注入】。

1.2 自动注入

自动注入又分为两种:【XML的autowire自动注入】和【@Autowired注解的自动注入】。

1.2.1 XML的autowire自动注入

在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式,它有如下几种方式:

1.2.1.1 byType:按照类型进行注入

byType注入方式,底层是基于setXxx方法实现的,所以setter方法不能少。这里说的类型是【入参】的类型。
Spring在通过byType的自动填充属性时流程是:

  1. 获取到set方法中的唯一参数的参数类型,并且根据该类型去容器中获取bean
  2. 如果找到多个,会报错

使用示例如下:

<bean id="userService" class="com.luban.service.UserService" autowire="byType"/>
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }

如上示例的类型,就是指入参orderService的类型OrderService

1.2.1.2 byName:按照名称进行注入

byType注入方式,底层是基于setXxx方法实现的,所以setter方法不能少。这里说的【名称】,是指setXxx后面的Xxx部分。
所以,Spring在通过byName的自动填充属性时流程是:

  1. 找到所有set方法所对应的Xxx部分的名字
  2. 根据Xxx部分的名字去获取bean

使用示例如下:

    <bean id="userXmlBean" class="org.tuling.spring.xml.bean.UserXmlBean" autowire="byName"/>
    <bean id="walletXmlBean" class="org.tuling.spring.xml.bean.WalletXmlBean"/>

如上,我们定义了userXmlBean的自动注入类型是byName,并且定义了一个名字叫walletXmlBean的bean。

public class UserXmlBean {

    private WalletXmlBean wallet;

    public void printProperty() {
        System.out.println(wallet);
    }

    public void setWalletXmlBean(WalletXmlBean param) {
        this.wallet = param;
    }
}

如上,我们定义了一个UserXmlBean ,他有成员变量WalletXmlBean wallet。同时给他声明了一个成员方法printProperty()用来打印它的成员属性的地址。

测试代码:

public class MyXmlApplicationContextTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        UserXmlBean userXmlBean = (UserXmlBean)context.getBean("userXmlBean");
        userXmlBean.printProperty();
    }
    
    // 系统输出:
    // org.tuling.spring.xml.bean.WalletXmlBean@1d16f93d
}

如上,UserXmlBeanWalletXmlBean类型的属性出现了2个名称,一个是成员变量wallet,另一个是setter方法入参中的param,但是一点都不妨碍我们byName注入。因为,根据byName的规则,寻找的是setXxx后面的Xxx部分。还不够信服是吗?我们改一下UserXmlBean里面的setter方法,如下:

    public void setWalletXmlBean123(WalletXmlBean param) {
        this.wallet = param;
    }

这个时候再去调用,输出null

1.2.1.3 constructor:按照构造方法进行注入

constructor表示通过构造方法注入,其实这种情况就比较简单了,没有byType和byName那么复杂。
如果是constructor,那么就可以不写set方法了,当某个bean是通过构造方法来注入时,spring利用构造方法的参数信息从Spring容器中去找bean,找到bean之后作为参数传给构造方法,从而实例化得到一个bean对象,并完成属性赋值(属性赋值的代码得程序员来写)。
(PS:我们这里先不考虑一个类有多个构造方法的情况,后面单独讲推断构造方法。我们这里只考虑只有一个有参构造方法。)
其实构造方法注入相当于byType+byName。Spring在通过byName的自动填充属性时流程是:

  1. 通过构造方法中的参数类型去找bean,如果对应的类型只有一个bean,那就是它了;
  2. 如果找到多个会根据参数名确定
  3. 如果最后根据参数名都无法确定,则报错

使用示例如下:

    <bean id="userXmlBean" class="org.tuling.spring.xml.bean.UserXmlBean" autowire="constructor"/>
    <bean id="walletXmlBean123" class="org.tuling.spring.xml.bean.WalletXmlBean"/>
    <bean id="walletXmlBean" class="org.tuling.spring.xml.bean.WalletXmlBean"/>

bean示例:

public class UserXmlBean {

    private WalletXmlBean wallet;

    public void printProperty() {
        System.out.println(wallet);
    }
    
    public UserXmlBean(WalletXmlBean walletXmlBean) {
        this.wallet = walletXmlBean;
    }
}

具体的调用跟错误方式这边就不介绍了,大家回头自己试试吧

1.2.1.4 其他

其他,诸如:

  • default:表示默认值,我们一直演示的某个bean的autowire,而也可以直接在<beans>标签中设置autowire,如果设置了,那么<bean>标签中设置的autowire如果为default,那么则会用<beans>标签中设置的autowire
  • no:表示关闭autowire,不自动注入
1.2.1.5 XML的autowire自动注入方式总结

那么XML的自动注入底层其实也就是:

  1. set方法注入
  2. 构造方法注入

1.2.2 @Autowired注解的自动注入

@Autowired注解,本质上也是byType和byName的结合。它是先byType,如果找到多个则byName。这个跟xml构造方式注入原理如出一辙。就是:

  1. 先根据类型去找bean,如果对应的类型只有一个bean,那就是它了;
  2. 如果找到多个会根据属性名确定
  3. 如果最后根据属性名都无法确定,则报错

@Autowired注解可以写在:

  1. 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个(属性注入)
  2. 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个(构造方法注入)
  3. set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个(set方法注入)

1.2.3 自动注入总结

可以发现XML中的自动注入是挺强大的,那么问题来了,为什么我们平时都是用的@Autowired注解呢?而没有用上文说的这种自动注入方式呢?
其实啊,@Autowired注解相当于XML中的autowire属性的注解方式的替代。从本质上讲,@Autowired注解提供了与autowire相同的功能,但是拥有更细粒度的控制和更广泛的适用性。
XML中的autowire控制的是整个bean的所有属性,而@Autowired注解是直接写在某个属性、某个set方法、某个构造方法上的。
再举个例子,如果一个类有多个构造方法,那么如果用XML的autowire=constructor,你无法控制到底用哪个构造方法,而你可以用@Autowired注解来直接指定你想用哪个构造方法。
同时,用@Autowired注解,还可以控制,哪些属性想被自动注入,哪些属性不想,这也是细粒度的控制。

二、依赖注入过程

2.1 简单回顾

依赖注入的过程,大体上其实能分为以下三步的:【寻找注入点】、【填充属性】、【填充属性后】。但其实,【寻找注入点】这个过程,会在两个地方被调用。第一个就是箭头所向,【实例化】阶段【BeanDefinition后置处理】那个地方。怎么理解呢?因为,【寻找注入点】的实现类就是【BeanDefinition后置处理】中的一个
在这里插入图片描述
上面说的概念多少有点绕。简单来说,【寻找注入点】就是寻找被@Autowird@Value@Inject@Resource注解修饰的属性、方法等等;然后,【属性填充】的时候再来处理这些找到的注入点,将他们设置到对应Bean属性中。

2.2 概念回顾

在这个【实例化】过程中,涉及到了一些Spring底层设计的概念,我在上一个笔记里面有大概介绍过Spring底层概念的一些讲解,不记得的同学记得回去翻一翻。
主要涉及的概念有:

  • BeanDefinition(设计图纸):BeanDefinition表示Bean定义,BeanDefinition中存在很多属性用来描述一个Bean的特征
  • MergedBeanDefinitionPostProcessor:合并BeanDefinition后置处理器。但这里其实主要说的是AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor。他俩有什么作用呢?前者是处理Spring内部定义的@Autowired@Value自动注入注解;后者是处理jdk定义的@Resource注解。主要是用到了这个MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition去完成【寻找注入点】的操作
  • InstantiationAwareBeanPostProcessor:感知实例化的Bean后置处理器。这个也是跟上面一样,其实主要说的也是AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor。这两个类也继承了InstantiationAwareBeanPostProcessor,并且在里面的postProcessProperties完成了对应注解【自动注入】的操作

CommonAnnotationBeanPostProcessor接口定义如下:

 /**
   * 这个后置处理器通过继承InitDestroyAnnotationBeanPostProcessor和InstantiationAwareBeanPostProcessor注解,
   * 获得了对@PostConstruct和@PreDestroy的支持。
   * 另外,这个类的核心处理元素是@Resource注解
   */
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
		implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
	// 具体代码就不贴了。学到这里大家应该知道如何通过【接口】继承、实现来猜测类能力了吧
}

AutowiredAnnotationBeanPostProcessor接口定义如下:

// 继承类跟CommonAnnotationBeanPostProcessor 如出一辙,唯一不同的是,继承了功能更强大的
// SmartInstantiationAwareBeanPostProcessor(InstantiationAwareBeanPostProcessor子类)
// 实现这个类,是为了实现里面的推断构造方法
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
		MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {

2.3 核心方法讲解

本节【属性注入】,将会分两个部分来讲。第一部分是:【寻找注入点】;剩下的是第二部分。
先说第一部分,第一部分主要涉及【3个类,7个核心方法】。
第二部分,待定…

三、【寻找注入点】方法讲解

我在上面说过,【寻找注入点】其实是有两个地方会调用到的。一个是在【属性填充populateBean()】之前的【合并BeanDefinitionapplyMergedBeanDefinitionPostProcessors()】,另一个就是在【属性填充】里面了。(【寻找注入点】源码以AutowiredAnnotationBeanPostProcessor举例)

3.1 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors:寻找注入点代码入口

全路径:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
将MergedBeanDefinitionPostProcessors应用于指定的bean定义,调用它们的postProcessMergedBeanDefinition方法。

源码如下:

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
	for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
		processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
	}
}

我们点开这个循环的类对象,找到他们的两个实现类:AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor。为了方便,我们这里就只举例AutowiredAnnotationBeanPostProcessor,因为他俩实现方式基本雷同,只不过前者处理的Spring的注解,由Spring本家写的;后者处理的JDK的注解。

3.2 AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

方法调用链:由3.2的applyMergedBeanDefinitionPostProcessors()调用进来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
方法注释:对指定bean的给定合并bean定义进行后处理。

源码如下:

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

方法解读:代码很简单,整个过程真正干活的其实是里面的findAutowiringMetadata()方法

3.3 AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata:寻找注入点

方法调用链:由3.2的applyMergedBeanDefinitionPostProcessors()调用进来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
方法注释:寻找注入点

**	private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {

		// 设置缓存key
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		
		// 先看缓存里面有没有
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				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;
	}**

方法解读:在这里,用到了一个缓存,说白了就是一个map,来判断是否已经【寻找过注入点】了,也是为了方便后续做注入。在这里,最核心的操作还是通过调用buildAutowiringMetadata,构建了当前类的注入点信息,并且包装成了InjectionMetadata

*3.4 AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata:构建注入点

方法调用链:由3.3的findAutowiringMetadata()调用进来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
方法注释:构建注入点

源码如下:

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {

	// 第一步:判断当前类是否候选类(是否需要【寻找注入点】)
	if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
		return InjectionMetadata.EMPTY;
	}

	List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
	Class<?> targetClass = clazz;

	do {
		final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
		
		// 第二步:利用反射,寻找【字段】上是否有【自动注入】的注解
		ReflectionUtils.doWithLocalFields(targetClass, field -> {
			MergedAnnotation<?> ann = findAutowiredAnnotation(field);
			if (ann != null) {
				if (Modifier.isStatic(field.getModifiers())) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation is not supported on static fields: " + field);
					}
					return;
				}
				boolean required = determineRequiredStatus(ann);
				currElements.add(new AutowiredFieldElement(field, required));
			}
		});


		// 第三步:利用反射,寻找【方法】上是否有【自动注入】的注解
		ReflectionUtils.doWithLocalMethods(targetClass, method -> {
			Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
			if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
				return;
			}
			MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
			if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
				if (Modifier.isStatic(method.getModifiers())) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation is not supported on static methods: " + method);
					}
					return;
				}
				if (method.getParameterCount() == 0) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation should only be used on methods with parameters: " +
								method);
					}
				}
				boolean required = determineRequiredStatus(ann);
				PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
				currElements.add(new AutowiredMethodElement(method, required, pd));
			}
		});

		elements.addAll(0, currElements);
		targetClass = targetClass.getSuperclass();
	}
	while (targetClass != null && targetClass != Object.class);

	return InjectionMetadata.forElements(elements, clazz);
}

方法解读:上面的方法看似很长,但整体上就分为三个步骤而已,没什么特别难理解的地方。特别是,如果你如果看过我前面的【手写Spring-引导篇】的话。
第一个步骤,判断当前类是否候选类(是否需要【寻找注入点】)。说实在这里暂时不确定啥意思,看代码就是过滤java.开头的类跟注解(百度了下,java.开头的一般是JDK开放的API,其中元注解就在里面声明,如:@Retention、@Target等)。所以我的理解是,这里的判断逻辑是让Spring的类,或者说我们自定义的类,可以使用JDK的【自动装配】注解,比如@Resource;但是JDK只能用JDK自己的【自动装配】注解。
第二个步骤:我们都知道,@Autowired注解,可以修饰在字段和方法上的,第二个步骤就是处理【字段】类型的注解。
第三个步骤:处理【方法】类型的注解。
关于第二、三步的源码实现其实都一样,只不过处理对象不一样而已,所以这里就只拿处理【字段】的逻辑来讲讲了。即如下:

ReflectionUtils.doWithLocalFields(targetClass, field -> {
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
					boolean required = determineRequiredStatus(ann);
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});

不过还是要先说一点。大家可能注意到了,这整个处理是在一个do-while循环体里面完成的,为什么呢?其实这里的do-while循环就是为了处理存在【继承关系的Bean】的注入而已。

3.5 ReflectionUtils#doWithLocalFields:利用反射遍历类上的【字段】

方法调用链:由3.4的buildAutowiringMetadata()调用进来
全路径:org.springframework.util.ReflectionUtils#doWithLocalFields
方法注释:对给定类中所有局部声明的字段调用给定的回调。

反射工具方法实现如下:

	public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
		for (Field field : getDeclaredFields(clazz)) {
			try {
				fc.doWith(field);
			}
			catch (IllegalAccessException ex) {
				throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
			}
		}
	}

(PS:这上边的代码,如果对函数式接口,或者说lambda表达式使用不清楚的可能看不懂,得赶紧去复习下了)

3.6 AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation:寻找字段上的自动装配注解

方法调用链:由3.5的doWithLocalFields()调用进来
全路径:org.springframework.util.ReflectionUtils#doWithLocalFields
方法注释:对给定类中所有局部声明的字段调用给定的回调。

再然后,就是在反射【回调函数】里面,调用findAutowiredAnnotation判断当前字段、方法是否有【自动装配】的注解。如下:

	@Nullable
	private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
		MergedAnnotations annotations = MergedAnnotations.from(ao);
		for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
			MergedAnnotation<?> annotation = annotations.get(type);
			if (annotation.isPresent()) {
				return annotation;
			}
		}
		return null;
	}

细心的朋友可能会问了, this.autowiredAnnotationTypes的值是啥?是的,我知道是@Autowired@Value注解,但是在哪里赋值呢?啊,这个目前不会讲到,这是在Spring容器启动的章节才会给大家讲。但是可以先告诉大家,这个是在AutowiredAnnotationBeanPostProcessor的构造方法中初始化的。如下:

	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.
		}
	}

3.7 Modifier.isStatic

方法调用链:由3.5的doWithLocalFields()调用进来
全路径:java.lang.reflect.Modifier#isStatic
方法注释:对给定类中所有局部声明的字段调用给定的回调。

然后,如果找到有被@Autowired和@Value注解的字段或者方法,还会判断该字段或者方法是否被static修饰,即静态的。静态的就不处理了。源码如下:

if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}

if (Modifier.isStatic(method.getModifiers())) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation is not supported on static methods: " + method);
					}
					return;
				}

点解啊?道理很简单的,你从原型Bean考虑一下就知道了。我们知道静态的是属于类的,不是属于对象的,那如果你每次注入的时候还要处理静态,那不就重复覆盖了吗?举例:

@Component
@Scope("prototype")
public class OrderService {
}

@Component
@Scope("prototype")
public class UserService  {

 @Autowired
 private static OrderService orderService;

 public void test() {
  System.out.println("test123");
 }

}

看上面代码,UserService和OrderService都是原型Bean,假设Spring支持static字段进行自动注入,那么现在调用两次

UserService userService1 = context.getBean("userService")
UserService userService2 = context.getBean("userService")

问此时,userService1的orderService值是什么?还是它自己注入的值吗?答案是不是,一旦userService2 创建好了之后,static orderService字段的值就发生了修改了,从而出现bug。

3.8 剩余步骤

剩余步骤,干了三件事,如下:

  1. 设置@Autowired(required = false)这个属性
  2. 将得到构建点包装成InjectionMetadata.InjectedElement
  3. 将得到的所有注入点,封装成InjectionMetadata,接着缓存起来

四、【寻找注入点】逻辑流程图

在这里插入图片描述
流程描述:

  1. 遍历当前类的所有的属性字段Field
  2. 查看字段上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该字段是一个注入点
  3. 如果字段是static的,则不进行注入
  4. 获取@Autowired中的required属性的值
  5. 将字段信息构造成一个AutowiredFieldElement对象,作为一个注入点对象添加到currElements集合中。
  6. 遍历当前类的所有方法Method
  7. 判断当前Method是否是桥接方法,如果是找到原方法
  8. 查看方法上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该方法是一个注入点
  9. 如果方法是static的,则不进行注入
  10. 获取@Autowired中的required属性的值
  11. 将方法信息构造成一个AutowiredMethodElement对象,作为一个注入点对象添加到currElements集合中。
  12. 遍历完当前类的字段和方法后,将遍历父类的,直到没有父类。
  13. 最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对于的注入点集合对象,并缓存。

四点五、特别声明

兄弟们,提前声明一下,下面的内容个人感觉特别复杂,是我按照Spring源码阅读顺序以来,目前最复杂的一部分。不过也由于分了两个部分,层次感还是有的,所以大家如果是在没有继续看下去的欲望了,剩下的下次再看吧。[/狗头][/狗头]
所以,我想着,在后面的讲解中,换一种方式来写写。

  1. 首先,我会先给出流程图,并且按照树形结构的方式写
  2. 其次,我将按照源码调用次序,【自上而下、从左往右】的方式映射到树形结构上
  3. 最后到了这里,我感觉大家需要打开Spring源码,一边看文章,一边看源码的方式来阅读了
  4. 我并不会讲解所有的源码,只会将一些比较陌生、或者关键的源码点一下

五、【属性填充】逻辑流程图

整体源码逻辑流程图如下:(画的我吐血)
在这里插入图片描述

图片点开来很不清晰,建议大家:右键、新建窗口打开。 然后就可以放大观看了

六、【属性填充】方法讲解

我们在【阅读建议】中已经说过【入口二】是AbstractAutowireCapableBeanFactory#populateBean。在这个方法,我们在上面属性流程图画的第一二层,讲的就是这个方法。

6.1 AbstractAutowireCapableBeanFactory#populateBean

全路径:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
方法解释:使用来自bean定义的属性值在给定的BeanWrapper中填充bean实例。

对应流程图:
在这里插入图片描述

源码如下:(标记了步骤一、二、三才是本章研究内容)

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

        // 空判断,有属性,但是bean为空,则报错
        if (bw == null) {
            if (mbd.hasPropertyValues()) {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
            }
            else {
                // Skip property population phase for null instance.
                return;
            }
        }

        // 这个在之前的【实例化阶段】的【实例化后】讲过了,不在本次研究范围内
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
                if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }


        // 下面才是我们本节课要研究的起点
        // 下面才是我们本节课要研究的起点
        // 下面才是我们本节课要研究的起点

        PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

        // 步骤一:
        // 处理beanDefinition的autowire属性。比如@Bean标签就可以设置这个属性;xml也可以设置这个属性
        int resolvedAutowireMode = mbd.getResolvedAutowireMode();
        if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
            MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
            // Add property values based on autowire by name if applicable.
            if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
                autowireByName(beanName, mbd, bw, newPvs);
            }
            // Add property values based on autowire by type if applicable.
            if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
                autowireByType(beanName, mbd, bw, newPvs);
            }
            pvs = newPvs;
        }

        // 步骤二:
        // 处理@Autowired、@Value、@Resource等【自动注入】的属性填充(注意,之前是寻找注入点,这里才是真正赋值的地方)
        boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
        PropertyDescriptor[] filteredPds = null;
        if (hasInstAwareBpps) {
            if (pvs == null) {
                pvs = mbd.getPropertyValues();
            }
            for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
                PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    if (filteredPds == null) {
                        filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                    }
                    pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        return;
                    }
                }
                pvs = pvsToUse;
            }
        }

        // 依赖检查,【细枝末节】,不看了
        if (needsDepCheck) {
            if (filteredPds == null) {
                filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            checkDependencies(beanName, mbd, filteredPds, pvs);
        }

        // 步骤三:
        // 处理BeanDefinition里面的propertyValues。比如:我们在操作beanDefinition的时候会修改;
        // 或者,在第一步处理@Bean的autowire属性的时候,实际上也是把结果跟旧有BeanDefinition的propertyValues合并。
        // 最后在这里处理注入操作
        if (pvs != null) {
            applyPropertyValues(beanName, mbd, bw, pvs);
        }
    }

方法解读:方法看着很长,但其实逻辑还算是比较清晰的。只不过有点可惜的是,感觉代码风格突然变了,跟前面研究的方法明显出于不同之人。比如,在之前,populateBean()里面德的【处理实例化后】阶段源码,会搞个resolveAfterInstantiation()方法来封装起来;步骤一也会写成一个resolveAutowireMode()方法,语义会更加清晰点。唉,后面这样写,多少给源码阅读增添了点难受。
废话不多说了,我们分析里面的流程吧。

  • 步骤一:主要是处理@Bean这个标签的autowire属性。其实严格来说,是处理beanDefinition下的autowire属性。估计大家没怎么用过,如下:
@Bean(autowire = Autowire.BY_NAME)
public OrderService orderService1() {
    return new OrderService();
}

或者这样:

 <bean id="userService" class="org.example.spring.bean.UserService" autowire="byType"/>
  • 步骤二:处理@Autowired、@Value、@Resource等【自动注入】的属性填充(注意,之前是寻找注入点,这里才是真正赋值的地方)。这个是本节课的核心内容,后面给大家细讲。
  • 步骤三:处理BeanDefinition里面的propertyValues。比如:我们在操作beanDefinition的时候会修改;或者,在第一步处理@Bean的autowire属性的时候,实际上也是把结果跟旧有BeanDefinition的propertyValues合并,最后在这里处理注入操作。这个其实也没啥好讲的

下面着重讲步骤二【自动注入注解】的属性填充

6.2 InstantiationAwareBeanPostProcessor#postProcessProperties:处理属性

我们在前面的【概念回顾】讲过,这里用到的,其实还是AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor,这里就简单的拿AutowiredAnnotationBeanPostProcessor举例吧。

方法调用链:由6.1中的populateBean()调用进来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
方法注释:在工厂将给定的属性值应用到给定的bean之前,对它们进行后处理,不需要任何属性描述符。

对应流程图:
在这里插入图片描述

源码如下:

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			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;
	}

方法解读:这里面就干了两件事,其中第一件事就是【寻找注入点】的入口。我们在前面也介绍过了,并且也说过,【寻找注入点】会在两个地方被调用,这里就是说的第二个地方。但是这里,通常是直接拿到了缓存里面的东西的,重新【寻找注入点】的情况不多,这些就属于【细枝末节】了,毕竟看不看对我们掌握整体脉络影响不大。
所以,这里最主要的还是看这个metadata.inject()方法,一看就知道是注入的意思

6.3 InjectionMetadata#inject:根据注入点注入

方法调用链:由6.2中的postProcessProperties()调用过来
全路径:org.springframework.beans.factory.annotationInjectionMetadata#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);
			}
		}
	}

方法解读:方法挺简单的,这个this.injectedElements就是我们在【寻找注入点】阶段缓存起来的注入点信息。那么大家还记得这里的注入点信息有哪些吗?哈,就是字段类、方法类封装出来的对象嘛。这里就是遍历所有,无论是方法类还是字段类的注入点了,然后依次调用对方的注入方法。
说到这里,Spring为了维护【单一职责】性,对于不同的注入对象,设计了两个类。分别是:

  • AutowiredFieldElement:表示有关带注解【字段】的注入信息
  • AutowiredMethodElement:表示有关带注解【方法】的注入信息

他们两个是AutowiredAnnotationBeanPostProcessor定义的内部类
老样子,这两个都差不多,我们拿AutowiredFieldElementelement.inject()举例吧

6.4 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject:字段类属性注入【入口】

方法调用链:由6.3的inject()调用过来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
方法注释:就是简单的,执行元素注入逻辑

源码如下:

		@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 = resolvedCachedArgument(beanName, this.cachedFieldValue);
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Unexpected removal of target bean for cached argument -> re-resolve
					value = resolveFieldValue(field, bean, beanName);
				}
			}
			else {
				value = resolveFieldValue(field, bean, beanName);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}

方法解读:很显然,我们第一次调用,理论上是没有缓存的,所以我们直接看else的逻辑

6.5 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue:解决字段属性注入【入口】

方法调用链:由6.4的inject()调用过来
全路径:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

源码如下:

	@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<>(1);
			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);
			}
	
			// 步骤三:设置bean依赖信息
			synchronized (this) {
				if (!this.cached) {
					Object cachedFieldValue = null;
					if (value != null || this.required) {
						cachedFieldValue = desc;
						registerDependentBeans(beanName, autowiredBeanNames);
						if (autowiredBeanNames.size() == 1) {
							String autowiredBeanName = autowiredBeanNames.iterator().next();
							if (beanFactory.containsBean(autowiredBeanName) &&
									beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
								cachedFieldValue = new ShortcutDependencyDescriptor(
										desc, autowiredBeanName, field.getType());
							}
						}
					}
					this.cachedFieldValue = cachedFieldValue;
					this.cached = true;
				}
			}
			return value;
		}
	}

方法解读:这里整体来说也是可以拆分成三个步骤来看的。
第一步就是做属性注入前的准备啦,比如之前设置的required属性,还有什么类型转换器,确保bean工厂存在等等;
第二步是核心,下面讲,真正处理属性注入的地方;
第三步是设置bean依赖信息。这是啥?简单说就是为了方便后期维护,新增了两个map来记录bean之间的相互依赖关系。这两个map分别为:

  • dependentBeanMap:记录bean依赖了哪些bean。以beanName记录
  • dependenciesForBeanMap:记录bean被哪些bean依赖了。也是以beanName记录

6.6 DefaultListableBeanFactory#resolveDependency:解决属性注入【入口】

方法调用链:由6.5的resolveFieldValue()而来
全路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
方法注释:根据此工厂中定义的bean解析指定的依赖项。

对应流程图:
在这里插入图片描述

源码如下:

	@Override
	@Nullable
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		else if (ObjectFactory.class == descriptor.getDependencyType() ||
				ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
		}
		else {
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			if (result == null) {
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

方法解读:这里开始就很多判断了,判断这个属性要怎么注入,要不要注入。前面这三个if-elseif-elseif说实在不看,显然我们在使用Spring的时候,基本上也不会用到这几个类型。感兴趣的朋友自己去看吧。我们直接看最后的else
这里有一个细节,如果判断到需要注入的地方,有@Lazy,那就直接返回一个【代理对象】,并且直接返回了(这里的写法是判断result==null来确定是不是有@Lazy)。不知道有没有人想过,为什么要用代理对象?啊,大家还记得前面文章提到的【代理范式】吗?
在这里插入图片描述

// 代理对象
public class ProxyModel extends ProxyTarget {
    private ProxyTarget proxyTarget;

    public void setProxyTarget(ProxyTarget proxyTarget) {
        this.proxyTarget = proxyTarget;
    }

    @Override
    public void run() {
        System.out.println("我代理对象可以在这里做加强---1");
        super.run();
        System.out.println("我代理对象也可以在这里做加强---2");
    }
}

就上面这个。然后我们在回忆一下懒加载的特点,不就是使用的时候再注入嘛。所以,在代理模式下,在每个懒加载属性的调用方法里面做个proxyTarge==null的判断不就得了嘛。这就是为什么返回代理对象的原因

6.7 DefaultListableBeanFactory#doResolveDependency:解决属性注入,真正干活的地方

方法调用链:由6.6的resolveDependency()而来
全路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
方法注释:解决属性注入,真正干活的地方

唉,到了这里,源码很长,我都没有截取的心思了。这里干的活,对应的流程图就是这一段:
在这里插入图片描述
说到方法长,还是想吐槽因为跟之前的代码风格不一样,这个作者明显懒一点。

学习总结

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

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

相关文章

如何解决使用npm出现Cannot find module ‘XXX\node_modules\npm\bin\npm-cli.js’错误

遇到问题&#xff1a;用npm下载组件时出现Cannot find module ‘D&#xff1a;software\node_modules\npm\bin\npm-cli.js’ 问题&#xff0c;导致下载组件不能完成。 解决方法&#xff1a;下载缺少的npm文件即可解决放到指定node_modules目录下即可解决。 分析问题&#xff1…

【自创】关于前端js的“嵌套地狱”的遍历算法

欢迎大家关注我的CSDN账号 欢迎大家关注我的哔哩哔哩账号&#xff1a;卢淼儿的个人空间-卢淼儿个人主页-哔哩哔哩视频 此saas系统我会在9月2号之前&#xff0c;在csdn及哔哩哔哩上发布成套系列教学视频。敬请期待&#xff01;&#xff01;&#xff01; 首先看图 这是我们要解…

Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)

文章目录 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)前情提要客户端部分 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三) 前情提要 单例泛型类 using System.Collections; using System.Collections.Generic; …

探索高级UI、源码解析与性能优化,了解开源框架及Flutter,助力Java和Kotlin筑基,揭秘NDK的魅力!

课程链接&#xff1a; 链接: https://pan.baidu.com/s/13cR0Ip6lzgFoz0rcmgYGZA?pwdy7hp 提取码: y7hp 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍&#xff1a; &#x1f4da;【01】Java筑基&#xff1a;全方位指…

政务、商务数据资源有效共享:让数据上“链”,记录每一个存储过程!

数据上链是目前“区块链”最常见的场景。因为链上所有参与方都分享了统一的事实来源&#xff0c;所有人都可以即时获得最新的信息&#xff0c;数据可用不可见。因此&#xff0c;不同参与方之间的协作效率得以大幅提高。同时&#xff0c;因为区块链上的数据难以篡改&#xff0c;…

SpringBoot的配置文件(properties与yml)

文章目录 1. 配置文件的作用2. 配置文件格式3. 配置文件的使用方法3.1. properties配置文件3.1.1. 基本语法和使用3.1.2. properties优缺点分析 3.2. yml配置文件3.2.1. 基本语法与使用3.2.2. yml中单双引号问题3.2.3. yml配置不同类型的数据类型及null3.2.4. 配置对象3.2.5. 配…

js闭包用法以及和bind的结合使用

bind用法 let info { name: "xuhaitao", age: 36 }function haitao() {console.log(this);}let fun haitao.bind(info)fun();haitao(); 控制台打印: 闭包用法: function xiaoMing() {let v 1;function jia() {v;console.log(v);}function getV() {console.log(…

通过微软Azure调用GPT的接口API-兼容平替OpenAI官方的注意事项

众所周知&#xff0c;我们是访问不通OpenAI官方服务的&#xff0c;但是我们可以自己通过代理或者使用第三方代理访问接口 现在新出台的规定禁止使用境外的AI大模型接口对境内客户使用&#xff0c;所以我们需要使用国内的大模型接口 国内的效果真的很差&#xff0c;现在如果想使…

perl下载与安装教程【工具使用】

Perl是一个高阶程式语言&#xff0c;由 Larry Wall和其他许多人所写&#xff0c;融合了许多语言的特性。它主要是由无所不在的 C语言&#xff0c;其次由 sed、awk&#xff0c;UNIX shell 和至少十数种其他的工具和语言所演化而来。Perl对 process、档案&#xff0c;和文字有很强…

微信小程序胶囊位置计算,避开胶囊位置

由于小程序在不同的手机上顶部布局会发生变化&#xff0c;不能正确避开胶囊位置&#xff0c;所以通过官方给出的胶囊信息&#xff0c;可以计算出胶囊位置&#xff0c;并避开 图示例&#xff1a; 此处思路是&#xff0c;获取胶囊底部位置&#xff0c;并拉开10个px 计算出来的…

iOS textView支持超链接跳转

将某些文字变成高量可以点击的超链接核心功能代码 attri.addAttribute(NSAttributedString.Key.link, value:NSURL.init(string: "dctt:p/userPrivacy.html")!, range: NSRange.init(location: s.count - 4, length: 4) )textView.linkTextAttributes [NSAttributed…

Learning to Super-resolve Dynamic Scenes for Neuromorphic Spike Camera论文笔记

摘要 脉冲相机使用了“integrate and fire”机制来生成连续的脉冲流&#xff0c;以极高的时间分辨率来记录动态光照强度。但是极高的时间分辨率导致了受限的空间分辨率&#xff0c;致使重建出的图像无法很好保留原始场景的细节。为了解决这个问题&#xff0c;这篇文章提出了Sp…

如何声明一个全局变量?有什么注意事项?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 声明全局变量⭐ 注意事项⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚…

python+django+mysql高校校园外卖点餐系统--计算机毕设项目

本文的研究目标是以高校校园外卖点餐为对象&#xff0c;使其高校校园外卖点餐为目标&#xff0c;使得高校校园外卖点餐的信息化体系发展水平提高。论文的研究内容包括对个人中心、美食分类管理、用户管理、商家管理、美食信息管理、工作人员管理、安全检查管理、系统管理、订单…

CI/CD入门(二)

CI/CD入门(二) 目录 CI/CD入门(二) 1、代码上线方案 1.1 早期手动部署代码1.2 合理化上线方案1.3 大型企业上线制度和流程1.4 php程序代码上线的具体方案1.5 Java程序代码上线的具体方案1.6 代码上线解决方案注意事项2、理解持续集成、持续交付、持续部署 2.1 持续集成2.2 持续…

移植PeerTalk开源库IOS的USB通信监听服务到QT生成的FFmpeg工程

1.添加生成的PeerTalk库 下图选中部分为FFmpeg依赖库 将USB通信服务的m与h文件添加到工程 因为OC文件使用了弱指针,所以要启用弱指针支持 因为FFmpeg拉流动用到本地网络,所以要在plist文件中启动本地网络使用 设置PeerTalk为嵌入模式 设置Runpath Search Paths为@executable_p…

【Linux操作系统】Linux系统编程中的共享存储映射(mmap)

在Linux系统编程中&#xff0c;进程之间的通信是一项重要的任务。共享存储映射&#xff08;mmap&#xff09;是一种高效的进程通信方式&#xff0c;它允许多个进程共享同一个内存区域&#xff0c;从而实现数据的共享和通信。本文将介绍共享存储映射的概念、原理、使用方法和注意…

Rabbitmq的应用场景

Rabbitmq的应用场景 一、异步处理 场景说明&#xff1a;用户注册后&#xff0c;需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式 ​​串行方式​​: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有…

前端 -- 基础 网页、HTML、 WEB标准 扫盲详解

什么是网页 : 网页是构成网站的基本元素&#xff0c;它通常由 图片、链接、文字、声音、视频等元素组成。 通常我们看到的网页 &#xff0c;常见以 .html 或 .htm 后缀结尾的文件&#xff0c; 因此俗称 HTML 文件 什么是 HTML : HTML 指的是 超文本标记语言&#xff0c…

kafka--kafka基础概念-ISR详解

kafka基础概念-ISR详解 主要是讲 主 往 从同步中的问题 当绿色P1接收到写入的数据&#xff0c;要同步到紫色的P1S1和P1S2 如何保证一致性呢&#xff1f; 使用In Sync Replicas 也就是ISR概念 为什么不一致的&#xff1f; 因为P1S1同步数据 可能花费 50ms P1S2可能花费60ms…