Sping源码(九)—— Bean的初始化(非懒加载)— Bean的创建方式(构造器方法)

news2024/11/24 2:02:18

序言

前面几篇文章介绍了Spring中几种方式下Bean对象的实例化的过程,那如果之前的几种都不满足,按照Spring中正常Bean的实例化步骤,该如何创建这个Bean对象呢?

测试类

我们先创建几个debug中用到的栗子。

Person
以一个平平无奇的Person对象的创建为切入点。

public class Person {
	// 省略了get set和构造方法,但是debug过程中构造方法很重要。
	private String name;
	private int age;
}	

person.xml
xml中bean标签配置了person对象信息,并且设置了scope = prototype以及<constructor-arg>。注释掉的<property>标签其实加载逻辑和<constructor-arg>是一样的,我们这里拿<constructor-arg>为例。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="person" class="org.springframework.factoryMethod.Person" scope="prototype">
		<bean id="person" class="org.springframework.factoryMethod.Person" scope="prototype">
<!--		<property name="age" value="22"/>-->
<!--		<property name="name" value="李四"/>-->
		<constructor-arg name="age" value="18"/>
		<constructor-arg name="name" value="张三"/>
	</bean>
	</bean>
</beans>

main

public class ConstructorTest {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("person.xml");
		Person p1 = (Person)ac.getBean("person");
		Person p2 = (Person)ac.getBean("person");
	}
}

所以根据我们在xml中的配置,在Person(p1)对象实例化中的流程大体如下:

  1. 获取构造器方法列表(因为类中可能定义很多的构造器方法)
  2. 遍历构造器,获取当前构造器方法的参数名称(name,age)
  3. 获取xml中配置的我们配置的具体属性值(18,张三)
  4. 将获取到的参数名称、值与构造器方法做匹配
  5. 计算构造器权重,确认最终使用的构造器
  6. 缓存当前所使用的的构造器方法,实例化Bean对象。

而当我们p2对象进行实例化时,就可以直接从缓存中进行获取。无需再重新遍历。

主流程方法

接下来我们回到doCreateBean的主流程方法中的createBeanInstance方法中,接着往下看。

doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		//这个beanWrapper是用来持有创建出来的bean对象的
		BeanWrapper instanceWrapper = null;

		//如果是单例对象,从factoryBeanInstanceCache缓存中移除该信息
		if (mbd.isSingleton()) {
			// 如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		// 没有就创建实例
		if (instanceWrapper == null) {
			// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		//省略部分代码
		return exposedObject;
	}

createBeanInstance
debug时省略了之前提到的SupplierFactoryMethod创建方法的步骤,我们直接从boolean resolved = false;开始看。

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		// 锁定class,根据设置的class属性或者根据className来解析class
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		// 如果beanClass != null
		// 并且,访问修饰符不是public修饰, 抛异常
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException;
		}
		// 获取Supplier
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		// 如果不为null,
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		//如果工厂方法不为null,则采用工厂方法来实例化
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
		//标记下,防止重复创建一个Bean
		boolean resolved = false;
		//是否需要自动装配
		boolean autowireNecessary = false;
		//如果args为null,则进行同步锁,获取构造方法
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				//如果已经解析了构造方法或工厂方法
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		//如果已经解析了构造方法或工厂方法
		if (resolved) {
			//构造器有参数
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				//使用默认的无参构造
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		//从SmartInstantiationAwareBeanPostProcessor中确认我们的构造器
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		//获取首选的构造器,方法return null 自己自定义扩展
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		// 如果没有特殊处理,则采用无参构造器来实例化对象
		return instantiateBean(beanName, mbd);
	}

此时来到了p1对象的创建,设置resolved等标志位后,因为此时缓存中resolvedConstructorOrFactoryMethod == null,所以并不会进到下面的判断。
在这里插入图片描述

此时resolvedConstructorOrFactoryMethod == null,并不会修改resolved的值,等到下面的 if 判断,因为在xml中设置了构造器参数,所以此时mbd.hasConstructorArgumentValues()返回true,能够进到判断中,接下来我们看autowireConstructor方法。
在这里插入图片描述
而我们的atowireMode共有5种,我们这里没有配置,所以no。
在这里插入图片描述

autowireConstructor

protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

创建Bean实例的整体流程。

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		//创建BeanWrapperImpl对象
		BeanWrapperImpl bw = new BeanWrapperImpl();
		//初始化bw (设置ConversionService和工厂中所有的PropertyEditor) 用来属性转换
		this.beanFactory.initBeanWrapper(bw);


		Constructor<?> constructorToUse = null;
		ArgumentsHolder argsHolderToUse = null;
		Object[] argsToUse = null;
		//如果有传入的参数,则直接使用
		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
			//声明一个要解析的args数组,默认为null
			Object[] argsToResolve = null;
			synchronized (mbd.constructorArgumentLock) {
				//将beanDefinition中解析完的构造函数和参数
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
				//BeanDefinition中存在解析过的构造函数和参数 && 存在构造函数参数
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
					//从缓存中找到了构造器,继续从缓存中找准备好的构造器参数
					argsToUse = mbd.resolvedConstructorArguments;
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			// 如果缓存中存在解析好的参数,解析配置的参数
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
			}
		}
		//如果缓存中没有解析好的构造器函数 || 参数构造器参数为空
		if (constructorToUse == null || argsToUse == null) {
			// Take specified constructors, if any.
			// 如果传入的chosenCtors不为null,则使用chosenCtors,否则通过反射获取类中定义的构造器函数
			Constructor<?>[] candidates = chosenCtors;
			if (candidates == null) {
				Class<?> beanClass = mbd.getBeanClass();
				try {
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
				catch (Throwable ex) {
					throw new BeanCreationException;
				}
			}
			//如果只有1个候选的构造器函数 && 传入参数explicitArgs = null && mbd中也没有构造器函数参数值
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				//获取当前构造器函数
				Constructor<?> uniqueCandidate = candidates[0];
				//如果唯一的构造器函数的参数个数为0,则直接使用
				if (uniqueCandidate.getParameterCount() == 0) {
					synchronized (mbd.constructorArgumentLock) {
						//mbd中缓存解析好的构造器函数或工厂方法
						mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
						//mbd中标记构造器参数已解析
						mbd.constructorArgumentsResolved = true;
						//mbd中缓存解析好的构造器参数为EMPTY_ARGS
						mbd.resolvedConstructorArguments = EMPTY_ARGS;
					}
					//通过反射进行bean的实例化并返回
					bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
					return bw;
				}
			}

			// Need to resolve the constructor.
			// 1.如果传入的chosenCtors参数不为null
			// 2.autowireMode 选择的是构造器注入的方式(AUTOWIRE_CONSTRUCTOR)
			// 有以上两种情况之一:说明需要自动装配,设置autowiring为true
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			ConstructorArgumentValues resolvedValues = null;

			//构造器的最小参数个数
			int minNrOfArgs;
			//如果传入的explicitArgs不为null,则minNrOfArgs = explicitArgs.length
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
				//获取配置文件中配置的构造器参数
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				//创建ConstructorArgumentValues对象,
				resolvedValues = new ConstructorArgumentValues();
				//解析参数个数
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}
			//将符合条件的构造器方法进行排序
			AutowireUtils.sortConstructors(candidates);
			// 定义一个差异变量,变量的大小决定着构造函数是否能够被使用
			int minTypeDiffWeight = Integer.MAX_VALUE;
			// 不明确的构造函数集合,正常情况下差异值不可能相同
			Set<Constructor<?>> ambiguousConstructors = null;
			//定义一个用于UnsatisfiedDependencyException的列表
			LinkedList<UnsatisfiedDependencyException> causes = null;

			//遍历获取到的构造方法
			for (Constructor<?> candidate : candidates) {
				//获取每个构造方法的参数个数
				int parameterCount = candidate.getParameterCount();
				// 1. 已经找到选用的构造函数
				// 2. 需要的参数个数小于当前的构造函数参数个数则终止,前面已经经过了排序操作
				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					// Already found greedy constructor that can be satisfied ->
					// do not look any further, there are only less greedy constructors left.
					break;
				}
				// 当前构造器函数所需参数数量 < 要求的参数数量,跳过,遍历下一个
				if (parameterCount < minNrOfArgs) {
					continue;
				}

				ArgumentsHolder argsHolder;
				//获取构造器所需参数类型数组
				Class<?>[] paramTypes = candidate.getParameterTypes();
				if (resolvedValues != null) {
					try {
						// 获取构造函数上的ConstructorProperties注解中的参数
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
						//如果没有上面注解,则获取构造器方法参数列表中的属性名称
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
								//获取指定的构造器参数名称
								paramNames = pnd.getParameterNames(candidate);
							}
						}
						// 根据名称和数据类型创建argsHolder对象
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
					catch (UnsatisfiedDependencyException ex) {
						// Swallow and try next constructor.
						if (causes == null) {
							causes = new LinkedList<>();
						}
						causes.add(ex);
						continue;
					}
				}
				//不存在构造函数参数列表需要解析的参数
				else {
					// Explicit arguments given -> arguments length must match exactly.
					// 如果参数列表的数量与传入进来的参数数量不相等,继续遍历,否则构造参数列表封装对象
					if (parameterCount != explicitArgs.length) {
						continue;
					}
					// 构造函数没有参数的情况
					argsHolder = new ArgumentsHolder(explicitArgs);
				}

				// 计算差异量,根据要参与构造函数的参数列表和本构造函数的参数列表进行计算
				// 省略这部分代码

			// 存在两种情况
			// 1. 没有确定使用的构造器函数
			// 2、存在模糊的构造函数并且不允许存在模糊的构造函数
			if (constructorToUse == null) {
				if (causes != null) {
					UnsatisfiedDependencyException ex = causes.removeLast();
					for (Exception cause : causes) {
						this.beanFactory.onSuppressedException(cause);
					}
					throw ex;
				}
				throw new BeanCreationException;
			}
			else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
				throw new BeanCreationException;
			}
			/**
			 * 没有传入参与构造函数参数列表的参数时,对构造函数缓存到BeanDefinition中
			 * 	1、缓存BeanDefinition进行实例化时使用的构造函数
			 * 	2、缓存BeanDefinition代表的Bean的构造函数已解析完标识
			 * 	3、缓存参与构造函数参数列表值的参数列表
			 */
			if (explicitArgs == null && argsHolderToUse != null) {
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

debug

具体流程
方法开始会创建BeanWrapper对象并初始化,初始化时会设置conversionService和注册BeanFactory中所有的Editors,目的是将值进行转换,因为我们在xml中配置的具体value都是字符串,比如说 age,而我们在具体类中对应的是Integer,在这里初始化后等到赋值时可以直接用。

protected void initBeanWrapper(BeanWrapper bw) {
		bw.setConversionService(getConversionService());
		registerCustomEditors(bw);
	}

声明构造器变量以及参数变量,因为我们此时对象还没有加载,所以缓存中constructorArgumentsResolvedargsToResolve等变量目前都为null,所以这些判断都进不去。
在这里插入图片描述


缓存中没有,所以我们需要获取当前实例中的所有构造器,并挑选一个符合条件的作为我们Bean的实例的容器。当前Person对象中我定义了一个无参构造器和一个包含name和age的构造器方法,所以这里candidates长度为2.
在这里插入图片描述


如果只获取到了一个构造器方法 && 没有构造器函数的参数值(mbd.hasConstructorArgumentValues()) && 也没有传入显示指定的参数(explicitArgs == null
则直接使用instantiate进行Bean的实例化,并设置缓存resolvedConstructorOrFactoryMethodconstructorArgumentsResolvedresolvedConstructorArguments
我们这里有两个构造器参数,所以进不来。
在这里插入图片描述


看是否需要进行自动装配
chosenCtors是方法的传参,这里是null,并且我们也没有在xml中配置 autowiring 属性,所以这里也进不来。
在这里插入图片描述


上面所有的条件都不满足,接下来就要根据我们获得的构造器方法列表和xml中配置的定义属性来挑选一个符合条件的构造器,作为对象实例化加载的容器。

获取到xml中配置的构造器参数。
在这里插入图片描述
解析参数个数,赋值给resolevedValues变量中。并将获取到的构造器进行排序操作。

排序:排序操作很重要,因为如果类中属性特别多,那么对应的生成的构造函数也可能会特别多,排序后再遍历会将不符合条件的直接continue,效率会大大提升。
在这里插入图片描述


遍历我们获取到的构造方法,因为做了排序,所以此时来到的是 name,age的构造方法。
如果已经在缓存中找到了构造函数 && 当前构造函数的参数个数(parameterCount) < 具体实例化时要用到的参数个数(argsToUse)。
说明没有再继续遍历的必要了,直接break。
如果当前构造方法的参数个数(parameterCount) < 我们需要实例化最小的参数个数 (minNrOfArgs),跳过这次循环 continue(当我们第二次循环condidates集合时,来到了无参构造,就会直接continue)
在这里插入图片描述


获取构造方法中的参数名称。
在这里插入图片描述


根据我们解析到xml中的值和当前构造方法的paramNames,创建并封装成argsHolder对象。
在这里插入图片描述


Spring中会计算每个构造器的权重,并通过逻辑选出最适合当前的构造器参数。
在这里插入图片描述


此时,将最终确认的构造器以及参数进行缓存。并调用instantiate方法进行bean的实例化创建并返回。
在这里插入图片描述

public void storeCache(RootBeanDefinition mbd, Executable constructorOrFactoryMethod) {
			synchronized (mbd.constructorArgumentLock) {
				mbd.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod;
				mbd.constructorArgumentsResolved = true;
				if (this.resolveNecessary) {
					mbd.preparedConstructorArguments = this.preparedArguments;
				}
				else {
					mbd.resolvedConstructorArguments = this.arguments;
				}
			}
		}

此时!我们的p1对象已经创建完成,当我们p2对象再次进行创建时。
缓存中不为null,设置我们的resolved 和 autowireNecessary变量为true。
在这里插入图片描述


再次调用autowireConstructor方法时。缓存不为null直接从缓存中找。找到后直接调用instantiate方法进行bean的实例化。(只有xml中配置 scope=“prototype” 才可以看到效果,不然创建完p1后直接放入 singletonObjects 缓存, 等到p2创建直接从singletonObjects中获取了)

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

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

相关文章

pytest-两种不同写法

XUnit 的写法 熟悉 unittest 框架的人都知道&#xff0c;unittest 里面 fixture 的写法是 setUp 和 tearDown&#xff0c;setUp_class 和 tearDown_class&#xff0c;只有这一种写法&#xff0c;而且是固定的写法哈。 Pytest 是兼容 unittest 的&#xff0c;当然也支持这样写…

B : 斐波那契数列第n项Plus

Description 斐波那契数列即 1, 1, 2, 3, 5...&#xff0c;&#xfffd;(&#xfffd;)&#xfffd;(&#xfffd;−1)&#xfffd;(&#xfffd;−2) 。求斐波那契数列第 n 项 Input 每组数据给出 1≤&#xfffd;≤109 。 Output 斐波那契数列第 n 项 对 1097 取模 Sam…

hadoop词频统计

1 Hadoop 安装与伪分布的搭建 2 Hadoop词频统计 此文章基于搭建好hadoop之后做的词频统计实验&#xff0c;以上是链接为搭建hadoop的教程 目录 1 HDFS 文件系统常用命令 2 词频统计实验准备工作 2.1 启动hadoop 关闭防火墙 2.2 查看图形化界面 2.3 文件上传 3 词频统计…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

ios13多窗口(UIWindowScene)学习笔记

ios13引入了UIWindowScene类、UIWindowSceneDelegate协议以便支持多窗口功能&#xff0c;但其适用于ipad&#xff0c;不适用于iphone&#xff0c;因为iphone不支持多窗口功能。注意&#xff0c;这里说的窗口不是UIWindow&#xff0c;而是UIWindowScene。 ios13前后的app的UI架…

结构体 -------- 函数-------传参

在函数题中 return 只能传一个值 如果函数体&#xff08;struct fs a&#xff0c;struct fs b&#xff09;传来了两个值&#xff0c;怎么才能只输出一个值呢&#xff1f; 同样要定义一个struct fs 类型的变量 result&#xff1b; 这样不仅可以访问到结构体中的变量a&#…

ESP32实现UDP连接——micropython版本

代码&#xff1a; import network import socket import timedef wifiInit(name, port):ap network.WLAN(network.AP_IF) # 创建一个热点ap.config(essidname, authmodenetwork.AUTH_OPEN) # 无需密码ap.active(True) # 激活热点ip ap.ifconfig()[0] # 获取ip地址print(…

短视频矩阵系统:打造品牌影响力的新方式

一、短视频矩阵概念 短视频营销革命&#xff1a;一站式解决策略&#xff01;短视频矩阵系统是一款专为企业营销设计的高效工具&#xff0c;旨在通过整合和优化众多短视频平台资源&#xff0c;为企业呈现一个全面的短视频营销策略。该系统致力于协助企业以迅速且高效的方式制作…

Node.js全栈指南:静态资源服务器

上一章【认识 MIME 和 HTTP】。 我们认识和了解了 MIME 的概念和作用&#xff0c;也简单地学习了通过浏览器控制台查看请求和返回的用法。 通过对不同的 HTML、CSS、JS 文件进行判断&#xff0c;设置不同的 MIME 值&#xff0c;得以让我们的浏览器正正确地接收和显示不同的文…

2095.删除链表的中间节点

给你一个链表的头节点 head 。删除链表的中间节点 &#xff0c;并返回修改后的链表的头节点 head。 长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点&#xff08;下标从 0 开始&#xff09;&#xff0c;其中 ⌊x⌋ 表示小于或等于 x 的最大整数。 对于 n 1、2、3、4 和…

逻辑这回事(七)---- 器件基础

Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…

HarmonyOS Next开发学习手册——选项卡 (Tabs)

当页面信息较多时&#xff0c;为了让用户能够聚焦于当前显示的内容&#xff0c;需要对页面内容进行分类&#xff0c;提高页面空间利用率。 Tabs 组件可以在一个页面内快速实现视图内容的切换&#xff0c;一方面提升查找信息的效率&#xff0c;另一方面精简用户单次获取到的信息…

大家都在跳槽,我需要跳槽吗?

文章目录 1. 前言2. 最初的跳槽想法萌芽3. 跳槽想法的再次萌芽4. 我是否需要跳槽呢?5. 那些跳槽的同学怎么样了&#xff1f;6. 小结 1. 前言 两周前&#xff0c;看到研究生同班同学发的一条朋友圈&#xff0c;内容为”下一站 杭州~”&#xff0c;配图是拍的北京开往杭州的列车…

软件框架(Framework)是什么?

可实例化的、部分完成的软件系统或子系统&#xff0c;它为一组系统或子系统定义了统一的体系结构(architecture)&#xff0c;并提供了构造系统的基本构造块(building blocks)&#xff0c;还为实现具体功能定义了扩展点(extending points)。 框架实现了体系结构级别的复用。 其…

八爪鱼现金流-031,宽带到期记一笔负债

到期了&#xff0c;新弄的网络&#xff0c;记录一下负债包。 八爪鱼现金流 八爪鱼

昇思25天学习打卡营第4天|函数式自动微分

学习目标&#xff1a; 重温高数知识&#xff0c;回顾导数、微分、偏导数‘全微分、方向导数、梯度&#xff1b;斜率、切线、切平面&#xff0c;法相平面、法线的知识’ 函数微分与导数的含义 多元函数偏导数、全微分 函数式自动微分应用实践 昇思大模型 &#xff0c;mindspor…

STM32第十三课:DMA多通道采集光照烟雾

文章目录 需求一、DMA&#xff08;直接存储器存取&#xff09;二、实现流程1.时钟使能2.设置外设寄存器地址3.设置存储器地址4.设置要传输的数据量5.设置通道优先级6.设置传输方向7.使通道和ADC转换 三、数据处理四、需求实现总结 需求 通过DMA实现光照强度和烟雾浓度的多通道…

JAVA期末速成库(12)第十三章

一、习题介绍 第十三章 Check Point&#xff1a;P501 13.3&#xff0c;13.17&#xff0c;13.28&#xff0c;13.29 Programming Exercise&#xff1a;13.1&#xff0c;13.6&#xff0c;13.11 二、习题及答案 Check Point&#xff1a; 13.3 True or false? a. An abst…

【做一道算一道】太平洋大西洋水流问题

太平洋大西洋水流问题 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights &…

零基础开始学习鸿蒙开发-读书app简单的设计与开发

目录 1.首页设计 2.发现页面的设计 3.设置页面的设计 4.导航页设计 5.总结&#xff1a; 6.最终的效果 1.首页设计 Entry Component export struct home {State message: string 首页build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.B…