Spring Bean 的生命周期

一、前言:

Spring Bean 的生命周期之前先来了解两个概念:
1.1 什么是 Bean

In Spring, the objects that form the backbone of your application and
that are managed by the Spring IoC container are called beans. A bean
is an object that is instantiated, assembled, and otherwise managed by
a Spring IoC container. Otherwise, a bean is simply one of many
objects in your application. Beans, and the dependencies among them,
are reflected in the configuration metadata used by a container.
简而言之,bean 是由 Spring IoC 容器实例化、组装和管理的对象。

1.2 什么是 Spring Bean 的生命周期

对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。

而 Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new
对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,我们需要哪个对象,去问 IoC 容器要即可。IoC
其实就是解决对象之间的耦合问题,Spring Bean 的生命周期完全由容器控制。

二、Spring Bean 的生命周期

这里我们说的 Spring Bean 的生命周期主要指的是 singleton bean,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

我们也来复习下 Spring 中的 bean 的作用域有哪些?

singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。 prototype :每次请求都会创建一个新的 bean 实例。
request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
global-session: 全局 session 作用域,仅仅在基于 Portlet 的 web
应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

我们知道对于普通的 Java 对象来说,它们的生命周期就是:

·实例化
·该对象不再被使用时通过垃圾回收机制进行回收

而对于 Spring Bean 的生命周期来说:

实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction

实例化 -> 属性赋值 -> 初始化 -> 销毁

直接给到最终的主要逻辑。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
    }

    if (instanceWrapper == null) {
    	// 实例化阶段
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    ...

    Object exposedObject = bean;

    try {
    	// 属性赋值阶段
        this.populateBean(beanName, mbd, instanceWrapper);
        // 初始化阶段
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {
        ...
    }

    ...
}

至于销毁,是在容器关闭时调用的,详见 *ConfigurableApplicationContext#close()*

至于 BeanPostProcessor、BeanFactoryPostProcessor 以及其他的类,只不过是对主流程四个步骤的一系列扩展点而已。

三、Spring Bean 的生命周期的扩展点

Spring Bean 的生命周期的扩展点超级多,老周这里不可能全部列出来,只说核心的扩展点。这也就是为什么 Spring 的扩展性很好的原因,开了很多的口子,尽可能让某个功能高内聚松耦合,用户需要哪个功能就用哪个,而不是直接来一个大而全的东西。

3.1 Bean 自身的方法

比如构造函数、getter/setter 以及 init-method 和 destory-method 所指定的方法等,也就对应着上文说的实例化 -> 属性赋值 -> 初始化 -> 销毁四个阶段。

3.2 容器级的方法(BeanPostProcessor 一系列接口)

主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessor、BeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。
在这里插入图片描述

3.2.1 InstantiationAwareBeanPostProcessor 源码分析

我们翻一下源码发现 InstantiationAwareBeanPostProcessor 是继承了 BeanPostProcessor
在这里插入图片描述
在这里插入图片描述
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 调用点

Object postProcessBeforeInstantiation(Class<?> beanClass, String
beanName)
返回值:如果返回的不为null,那么后续的Bean的创建流程【实例化、初始化afterProperties】都不会执行,而是直接使用返回的快捷Bean,此时的正常执行顺序如下:
InstantiationAwareBeanPostProcessor接口中的postProcessBeforeInstantiation,在实例化之前调用。
BeanPostProcessor接口中的postProcessAfterInitialization,在实例化之后调用。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总之,postProcessBeforeInstantiation 在 doCreateBean 之前调用,也就是在 bean 实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的 Bean 作为代理,这也是 AOP 等功能实现的关键点。

InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 调用点

boolean postProcessAfterInstantiation(Object bean, String beanName)
throws BeansException 正常情况下在实例化之后在执行populateBean之前调用
返回值:如果有指定的bean的时候返回false,那么后续的属性填充和属性依赖注入【populateBean】将不会执行,同时后续的postProcessPropertyValues将不会执行,但是初始化和BeanPostProcessor的仍然会执行。
在这里插入图片描述

public PropertyValues postProcessPropertyValues(PropertyValues pvs,
PropertyDescriptor[] pds, Object bean, String beanName)
实例化之后调用,在方法applyPropertyValues【属性填充】之前
返回值:如果返回null,那么将不会进行后续的属性填充,比如依赖注入等,如果返回的pvs额外的添加了属性,那么后续会填充到该类对应的属性中。
pvs:PropertyValues对象,用于封装指定类的对象,简单来说就是PropertyValue的集合,里面相当于以key-value形式存放类的属性和值。
pds:PropertyDescriptor对象数组,PropertyDescriptor相当于存储类的属性,不过可以调用set,get方法设置和获取对应属性的值。

在这里插入图片描述

3.2.2 BeanPostProcessor 源码分析

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

在这里插入图片描述

进入初始化接口:
在这里插入图片描述
我们先来看

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

在这里插入图片描述

首先获取到所有的后置处理器 getBeanPostProcessors()
在 for 循环中依次调用后置处理器的方法 processor.postProcessBeforeInitialization(result, beanName);

进入postProcessBeforeInitialization 方法

org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization

在这里插入图片描述

进入 invokeAwareInterfaces(bean); 方法,当前 bean 实现了 ApplicationContextAware 接口。

在这里插入图片描述

ApplicationContextAwareProcessor#postProcessBeforeInitialization 首先判断此bean 是不是各种的Aware,如果是它列举的那几个 Aware 就获取 Bean工厂的权限,可以向容器中导入相关的上下文环境,目的是为了 Bean 实例能够获取到相关的上下文,如果不是它列举的几个 Aware,那就调用 invokeAwareInterfaces(bean),向容器中添加相关接口的上下文环境。

3.3 工厂后处理器方法(BeanFactoryProcessor 一系列接口)包括:

AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等。这些都是 Spring 框架中已经实现好的 BeanFactoryPostProcessor,用来实现某些特定的功能。

我们知道 Spring IoC 容器初始化的关键环节就在org.springframework.context.support.AbstractApplicationContext#refresh
方法中 ,容器创建的主体流程都在这个方法里面,这个方法是真的重要!!!

对于工厂后处理器方法看 invokeBeanFactoryPostProcessors(beanFactory);
方法,这个方法处理的是 BeanFactoryPostProcessor 接口的 Bean。调用方法如下:
在这里插入图片描述

跟到最重要的方法里去,代码虽长,但逻辑中规中矩。

BeanFactoryPostProcessor:一切处理 BeanFactory 的父接口
BeanDefinitionRegistryPostProcessor:实现了 BeanFactoryPostProcessor:接口的接口

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

1、调用 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(registry) 方法。参数 beanFactoryPostProcessors 传入的优先处理掉。然后获取容器注册的,对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。
2、调用 BeanFactoryPostProcessor#postProcessBeanFactory(beanFactory) 方法。备注:BeanDefinitionRegistryPostProcessor 属于 BeanFactoryPostProcessor 子接口。先处理属于 BeanDefinitionRegistryPostProcessor 接口实例的 postProcessBeanFactory(beanFactory) 方法,然后获取容器注册的。对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。

3.4 Bean 级生命周期方法

可以理解为 Bean 类直接实现接口的方法,比如 BeanNameAware、BeanFactoryAware、ApplicationContextAware、InitializingBean、DisposableBean 等方法,这些方法只对当前 Bean 生效。

3.4.1 Aware 类型的接口

Aware 类型的接口的作用就是让我们能够拿到 Spring 容器中的一些资源。基本都能够见名知意,Aware 之前的名字就是可以拿到什么资源,例如 BeanNameAware 可以拿到 BeanName,以此类推。调用时机需要注意:所有的 Aware 方法都是在初始化阶段之前调用的

Aware 接口众多,这里同样通过分类的方式帮助大家记忆。Aware 接口具体可以分为两组,至于为什么这么分,详见下面的源码分析。如下排列顺序同样也是 Aware 接口的执行顺序,能够见名知意的接口不再解释。

Aware Group1

BeanNameAware

BeanClassLoaderAware

BeanFactoryAware

Aware Group2

EnvironmentAware

EmbeddedValueResolverAware

这个知道的人可能不多,实现该接口能够获取 Spring EL 解析器,用户的自定义注解需要支持 SPEL 表达式的时候可以使用,非常方便。

ApplicationContextAware(ResourceLoaderAware/ApplicationEventPublisherAware/MessageSourceAware)
这几个接口可能让人有点懵,实际上这几个接口可以一起记,其返回值实质上都是当前的 ApplicationContext 对象,因为 ApplicationContext 是一个复合接口,如下:
在这里插入图片描述
Aware 调用时机源码分析
在这里插入图片描述
可以看到并不是所有的 Aware 接口都使用同样的方式调用。Bean××Aware 都是在代码中直接调用的,而 ApplicationContext 相关的 Aware 都是通过 BeanPostProcessor#postProcessBeforeInitialization() 实现的。感兴趣的可以自己看一下 ApplicationContextAwareProcessor 这个类的源码,就是判断当前创建的 Bean 是否实现了相关的 Aware 方法,如果实现了会调用回调方法将资源传递给 Bean。

BeanPostProcessor 的调用时机也能在这里体现,包围住 invokeInitMethods 方法,也就说明了在初始化阶段的前后执行。

关于 Aware 接口的执行顺序,其实只需要记住第一组在第二组执行之前就行了。

3.4.2 生命周期接口

至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。

InitializingBean 对应生命周期的初始化阶段,在上面源码的 invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。
有一点需要注意,因为 Aware 方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用 Aware 接口获取的资源,这也是我们自定义扩展 Spring 的常用方式。
除了实现 InitializingBean 接口之外还能通过注解或者 xml 配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。
DisposableBean 类似于 InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了 DisposableBean 接口的 Bean 然后调用其 destroy() 方法,感兴趣的可以自行跟一下源码。

3.5 Spring Bean 生命周期流程图

在这里插入图片描述

四、常用接口说明

4.1 BeanNameAware

该接口只有一个方法 setBeanName(String name),用来获取 bean 的 id 或者 name

4.2 BeanFactoryAware

该接口只有一个方法 setBeanFactory(BeanFactory beanFactory),用来获取当前环境中的 BeanFactory

4.3 ApplicationContextAware

该接口只有一个方法 setApplicationContext(ApplicationContext applicationContext),用来获取当前环境中的 ApplicationContext

4.4 InitializingBean

该接口只有一个方法 afterPropertiesSet()在属性注入完成后调用。

4.5 DisposableBean

该接口只有一个方法 destroy(),在容器销毁的时候调用,在用户指定的 destroy-method 之前调用。

4.6 BeanPostProcessor

该接口有两个方法:

postProcessBeforeInitialization(Object bean, String beanName):初始化之前调用此方法
postProcessAfterInitialization(Object bean, String beanName):在初始化之后调用此方法
通过方法签名我们可以知道,我们可以通过 beanName 来筛选出我们需要进行个性化定制的 bean。

4.7 InstantiationAwareBeanPostProcessor

该类是 BeanPostProcessor 的子接口,常用的有如下三个方法:

postProcessBeforeInstantiation(Class beanClass, String beanName):在bean实例化之前调用
postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean实例化之后设置属性前调用
postProcessAfterInstantiation(Class beanClass, String beanName):bean实例化之后调用

五、代码演示

思路:创建一个类 UserBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。

5.1 UserBean 类

@Component
public class UserBean implements InitializingBean, BeanNameAware, DisposableBean, ApplicationContextAware {
	private int id;

	private String name;

	public UserBean(int id, String name) {
		this.id = id;
		this.name = name;
		System.out.println("2. 调用构造函数");
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
		System.out.println("5. 属性注入 id");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
		System.out.println("5. 属性注入 name");
	}

	@Override
	public void setBeanName(String name) {
		System.out.println("6. 调用 BeanNameAware.setBeanName() 方法");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		UserBean userBean = (UserBean) applicationContext.getBean("userBean");
		System.out.println(userBean);
		System.out.println("7. 调用 BeanNameAware.setBeanName() 方法");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("9. 调用 InitializingBean.afterPropertiesSet() 方法");
	}

	public void myInit() {
		System.out.println("10. 调用 init-method 方法");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("12. 调用 DisposableBean.destroy() 方法");
	}

	public void myDestroy() {
		System.out.println("13. 调用 destroy-method 方法");
	}

	@Override
	public String toString() {
		return "UserBean{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
}

5.2 InstantiationAwareBeanPostProcessor 接口实现类
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
	@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("1. 调用 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() 方法");
		}
		return null;
	}

	@Override
	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			UserBean userBean = (UserBean) bean;
			System.out.println("3. 调用 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() 方法");
			System.out.println(userBean);
		}
		return true;
	}

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("4. 调用 InstantiationAwareBeanPostProcessor.postProcessProperties() 方法");
		}
		return null;
	}
}

5.3 BeanPostProcessor 接口实现类
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("8. 调用 BeanPostProcessor.postProcessBeforeInitialization() 方法");
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("11. 调用 BeanPostProcessor.postProcessAfterInitialization() 方法");
		}
		return bean;
	}
}

5.4 BeanFactoryPostProcessor 接口实现类
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("0. 调用 BeanFactoryPostProcessor.postProcessBeanFactory() 方法");
	}
}

5.5 applicationContext.xml
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
">

	<bean class="com.riemann.test.MyInstantiationAwareBeanPostProcessor" />

	<bean id="userBean" class="com.riemann.test.UserBean" init-method="myInit" destroy-method="myDestroy">
		<!-- 构造函数注入 -->
		<constructor-arg index="0" type="int">
			<value>1</value>
		</constructor-arg>
		<constructor-arg index="1" type="java.lang.String">
			<value>微信公众号【老周聊架构】</value>
		</constructor-arg>

		<!-- setter方法注入 -->
		<property name="id" value="2"/>
		<property name="name" value="riemann"/>
	</bean>

	<bean class="com.riemann.test.MyBeanPostProcessor" />

	<bean class="com.riemann.test.MyBeanFactoryPostProcessor" />
	
</beans>

5.6 测试类
public class BeanLifeCycleTest {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		UserBean user = (UserBean) applicationContext.getBean("userBean");
		((AbstractApplicationContext) applicationContext).close();
	}
}

5.7 控制台结果打印

在这里插入图片描述

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

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

相关文章

“面向目标值的排列匹配“和“面向目标值的背包组合问题“的区别和leetcode例题详解

1 目标值排列匹配 1.1 从目标字符串的角度来看&#xff0c;LC139是一个排列问题&#xff0c;因为最终目标子串的各个字符的顺序是固定的&#xff1f; 当我们从目标字符串 s 的角度来看 LC139 “单词拆分” 问题&#xff0c;确实可以认为它涉及到排列的概念&#xff0c;但这种…

C++ Qt 学习(四):自定义控件与 qss 应用

1. qss 简介 Qt style sheet&#xff08;qss&#xff0c;Qt 样式表&#xff09;&#xff0c;不需要用 C 代码控件进行重载&#xff0c;就可以修改控件外观&#xff0c;类似于前端的 css 2. qss 选择器 2.1 通配符选择器 /* 设置后控件窗口背景色都被修改为黄色 */ * {backg…

便捷Benchmark.sh 自动匹配workload(自用)

​ 因为db_bench选项太多&#xff0c;而测试纬度很难做到统一&#xff08;可能一个memtable大小的配置都会导致测试出来的写性能相关的的数据差异很大&#xff09;&#xff0c;所以官方给出了一个benchmark.sh脚本用来对各个workload进行测试。 该脚本能够将db_bench测试结果中…

深度解析NLP定义、应用与PyTorch实战

1. 概述 文本摘要是自然语言处理&#xff08;NLP&#xff09;的一个重要分支&#xff0c;其核心目的是提取文本中的关键信息&#xff0c;生成简短、凝练的内容摘要。这不仅有助于用户快速获取信息&#xff0c;还能有效地组织和归纳大量的文本数据。 1.1 什么是文本摘要&#x…

《詩經别解》——國風·周南·雎鳩​​​​​​​

一、关于古文的一个认识 目前可以阅读的古文经典&#xff0c;大多是经历了几千年的传承。期间的武力战争、文化纷争、宗教侵袭、官僚介入及文人的私人恩怨与流派桎梏&#xff0c;印刷与制作技术&#xff0c;导致这些古文全部都已经面目全非。简单地说&#xff0c;你读到的都是…

树与二叉树作业

1. 已知一个二叉树的中序遍历序列和后序遍历序列&#xff0c;求这棵树的前序遍历序列 【问题描述】 已知一个二叉树的中序遍历序列和后序遍历序列&#xff0c;求这棵树的前序遍历序列。 【输入形式】 一个树的中序遍历序列 该树后序遍历序列&#xff0c;中间用空格分开。输…

el-table实现展开当前行时收起上一行的功能

<el-tableref"tableRef":data"tableData":expand-row-keys"expandRowKeys":row-key"handleRowKey" // 必须指定 row-keyexpand-change"handleExpandChange" // 当用户对某一行展开或者关闭的时候会触发该事件> <…

Creo螺旋扫描/弹簧画法

一&#xff1a;点击螺旋扫描 二&#xff1a;参考–》螺旋轮廓的定义&#xff1a; 三、草绘轮廓线&#xff1a;视图放正 四、草绘弹簧丝线形状&#xff1a; 在非中轴线上画圆&#xff1a; 制螺旋线&#xff1a; 首先理清Creo绘制螺旋线的逻辑&#xff08;不同于UG直接给定直径…

华为ensp:边缘端口并启动BUDU保护

如上图前提是三个交换机都做了rstp&#xff0c;则在边缘的地方做 边缘端口并启动BUDU保护&#xff0c;也就是我用绿色圈出来的地方 边缘1 进入交换机的系统视图 interface e0/0/3 进入接口 stp edged-port enable quit 再退回系统视图 stp bpdu-protection 这样就可以了…

Arduino ESP8266使用AliyunIoTSDK.h连接阿里云物联网平台

文章目录 1、AliyunIoTSDK简介2、相关库安装3、阿里云创建产品&#xff0c;订阅发布4、对开源的Arduino ESP8266源代码修改5、使用阿里云点亮一个LED灯6、设备向阿里云上传温度数据7、项目源码 1、AliyunIoTSDK简介 AliyunIoTSDK是arduino的一个库&#xff0c;可以在arduino的…

20分钟搭建Ubertooth One开源蓝牙测试工具

kali linux 2023 安装依赖&#xff08;记得使用root用户搭建环境&#xff09; 1、apt-get update 2、apt install ubertooth 更新共享库缓存 3、ldconfig 安装 Ubertooth 工具和驱动程序 4、插入Ubertooth One工具 5、ubertooth-util -v 备注&#xff1a;出现Firmwate v…

Android系统开发快速寻找代码(如何在文件夹中寻找代码)

很多时候对于Android系统开发小白而言&#xff0c;例如预置APK&#xff0c;知道了APK包名不知道具体代码位置需要去寻找代码&#xff0c;但是Android系统代码十分庞大&#xff0c;如何快速准确查询代码是个问题。 本人目前只探索到了一些方法&#xff0c;如有更有效的办法可以…

CMOS介绍

1 二极管 2 CMOS 2.1 栅极、源极、漏极 2.2 内部结构 2.2 导电原理 - 原理&#xff1a;1.通过门级和衬底加一个垂直电场Ev&#xff0c;从而在两口井之间形成反形层2.如果加的电场足够强&#xff0c;反形层就可以把source&#xff08;源极&#xff09;和drain&#xff08;漏极…

UML软件建模软件StarUML mac中文版软件介绍

StarUML for mac是一款UML建模器&#xff0c;StarUML for mac提供了几个模版&#xff0c;帮助用户建立使用新的图表&#xff0c;是目前最流行的UML建模工具&#xff0c;给开发工作带来大大的便利。 StarUML mac软件介绍 StarUML 是一个流行的软件建模工具&#xff0c;用于创建…

【车载开发系列】AutoSar中的CANTP

【车载开发系列】AutoSar中的CANTP 【车载开发系列】AutoSar中的CANTP 【车载开发系列】AutoSar中的CANTP一. CANTP相关术语二. CANTP相关概念1&#xff09;单帧&#xff1a;SF(Single Frame)2&#xff09;首帧&#xff1a;FF(First Frame)3&#xff09;连续帧CF(Consecutive F…

原生微信小程序学习之旅(一) -来简单的使用

文章目录 取消导航栏标头组件创建添加Component组件接收传入的数据 页面创建(Page)关于tabBartabBar自定义样式 轮播图轮播图指示点样式改变 微信小程序快速获取用户信息路由跳转获取url路径中的参数 bindtap(click)传参wx:if编写用户登陆关于默认工程目前的获取方法尝试一下服…

python 中用opencv开发虚拟键盘------可以只选择一个单词不会出现一下选择多个

一. 介绍 OpenCV是最流行的计算机视觉任务库&#xff0c;它是用于机器学习、图像处理等的跨平台开源库&#xff0c;用于开发实时计算机视觉应用程序。 CVzone 是一个计算机视觉包&#xff0c;它使用 OpenCV 和 Media Pipe 库作为其核心&#xff0c;使我们易于运行&#xff0c…

Apache和Nginx实现虚拟主机的3种方式

目录 首先介绍一下Apache和nginx&#xff1a; Nginx和Apache的不同之处&#xff1a; 虚拟主机 准备工作 Apache实现&#xff1a; 方法1&#xff1a;使用不同的ip来实现 方法2&#xff1a;使用相同的ip&#xff0c;不同的端口来实现 方法3&#xff1a;使用相同的ip&…

解决游戏找不到x3daudio1_7.dll文件的5个方法,快速修复dll问题

在电脑使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“x3daudio1_7.dll丢失”。这个错误通常会导致软件游戏无法正常启动运行。为了解决这个问题&#xff0c;我们需要采取一些措施来修复丢失的文件。本文将详细介绍解决x3daudio1_7.dll丢失的方法…

企业云盘与个人云盘:区别与特点一览

企业云盘是企业在寻找文件协同工具的过程中绕不开的一个选项。企业为什么需要专门购置企业网盘&#xff0c;个人云盘能否满足企业的文件协作需求呢&#xff1f;企业云盘和个人云盘有什么区别呢&#xff1f; 企业云盘与个人云盘的区别 1、使用对象&#xff1a;顾名思义&#xf…
最新文章