Springboot扩展点之InstantiationAwareBeanPostProcessor
Springboot扩展点系列实现方式、工作原理集合:
Springboot扩展点之ApplicationContextInitializer
Springboot扩展点之BeanFactoryPostProcessor
Springboot扩展点之BeanDefinitionRegistryPostProcessor
Springboot扩展点之BeanPostProcessor
Springboot扩展点之InstantiationAwareBeanPostProcessor
Springboot扩展点之SmartInstantiationAwareBeanPostProcessor
Springboot扩展点之ApplicationContextAwareProcessor
Springboot扩展点之@PostConstruct
Springboot扩展点之InitializingBean
Springboot扩展点之SmartInitializingSingleton
Springboot扩展点之CommandLineRunner和ApplicationRunner
Springboot扩展点之FactoryBean
Springboot扩展点之DisposableBean
Springboot扩展点系列之终结篇:Bean的生命周期
前言
前面介绍了Springboot的扩展点之BeanPostProcessor,再来介绍另一个扩展点InstantiationAwareBeanPostProcessor就容易多了。因为InstantiationAwareBeanPostProcessor也属于Bean级的后置处理器,还继承于BeanPostProcessor,因此InstantiationAwareBeanPostProcessor除了可以实现BeanPostProcessor的扩展外,又额外增加了三个扩展点,这篇文章也是围绕这三个扩展点展示,主要介绍InstantiationAwareBeanPostProcessor扩展接口的功能特性、实现方式、工作原理、和应用场景,BeanPostProcessor扩展部分可以翻看前面的文章Springboot的扩展点之BeanPostProcessor。
功能特性
1、虽然InstantiationAwareBeanPostProcessor继承于BeanPostProcessor,但是InstantiationAwareBeanPostProcessor的执行时机要稍早于BeanPostProcessor;
2、InstantiationAwareBeanPostProcessor有三个扩展方法,分别是:postProcessBeforeInstantiation()、postProcessAfterInstantiation()、postProcessProperties();
3、postProcessBeforeInstantiation()在Spring中Bean实例化前触发执行;
4、postProcessAfterInstantiation()、postProcessProperties()在Spring中Bean实例化后,属性注入前触发执行;
5、InstantiationAwareBeanPostProcessor继承于BeanPostProcessor的postProcessBeforeInitialization()和postProcessAfterInitialization()则在Spring中Bean实例化、属性注入完成后触发执行;
6、postProcessBeforeInstantiation()扩展点可以自定义个性化的Bean来替换掉目标Bean,需要注意的是替换掉目标Bean后,postProcessAfterInstantiation()会执行,其他的扩展点将不再触发;
7、postProcessAfterInstantiation()的返回值为布尔类型,如果返回值为true,则第三个扩展点postProcessProperties()会继续执行;如果返回值为false,则第三个扩展点postProcessProperties()将不再执行;
8、postProcessProperties()扩展点可以在目标Bean实例化后,属性注入前,对要注入的属性值内容进行更改,以替换掉原来的属性值;
实现方式
总的来说InstantiationAwareBeanPostProcessor扩展点的实现方式很简单,实现接口,重写相应的方法实现扩展逻辑,并用@Component注解标记实现类,其余的由Spring自动完成。下面通过示例逐步详细介绍:
1、定义一个Controller类(ExampleController),通过setter方法注入属性ExampService;
@RestController
@RequestMapping("/example")
@Slf4j
public class ExampleController {private String creator="gaox";private ExampleService exampleService;@Autowiredpublic void setExampleService(ExampleService exampleService) {this.exampleService = exampleService;log.info("----ExampleController内的exampleService属性被注入");}public void setCreator(String creator) {this.creator = creator;log.info("----ExampleController内的creator属性被注入");}public String getCreator() {return creator;}public ExampleController() {log.info("----ExampleController无参构造方法被执行");}
}
2、定义ExampleService类,以便作为引用属性注入到ExampleController中;
@Service
@Slf4j
public class ExampleService {public ExampleService() {log.info("----ExampleService无参构造方法被执行");}public void test(){System.out.println("test");}
}
3、定义InstantiationAwareBeanPostProcessor接口的实现类示例类MyInstantiationAwareBeanPostProcessor,并重写InstantiationAwareBeanPostProcessor接口的三个扩展方法和继承于BeanPostProcessor接口的两个扩展方法;
@Component
@Slf4j
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {@SneakyThrows@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if (beanName.equals("exampleController")) {log.info("----postProcessBeforeInstantiation被执行:" + beanName);return null;}return null;}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if (beanName.equals("exampleController")) {log.info("----postProcessAfterInstantiation被执行:" + beanName);}return true;}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {if (beanName.equals("exampleController")) {log.info("----postProcessProperties被执行:" + beanName);MutablePropertyValues mutablePropertyValues=new MutablePropertyValues();mutablePropertyValues.addPropertyValue("creator","fanfu");pvs=mutablePropertyValues;}return pvs;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("exampleController")) {log.info("----postProcessBeforeInitialization---" + beanName);//如果特定的bean实例化完成后,还未执行InitializingBean.afterPropertiesSet()方法之前,有一些其他操作,可以在这里实现}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("exampleController")) {log.info("----postProcessAfterInitialization---" + beanName);//如果特定的bean实例化完成,InitializingBean.afterPropertiesSet()方法执行后,有一些其他操作,可以在这里实现}return bean;}
}
4、编写单元测试验证InstantiationAwareBeanPostProcessor接口的功能特性;
@Test
public void test3(){AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");ExampleController bean = context.getBean(ExampleController.class);Assert.isTrue("fanfu".equals(bean.getCreator()),"属性替换失败");log.info("----"+bean.getCreator());}
执行结果如下:

工作原理
注册时机
1、因为InstantiationAwareBeanPostProcessor接口继承于BeanPostProcessor接口,所以InstantiationAwareBeanPostProcessor接口的实现类的注册时机和BeanPostProcessor是一致的,因此很快就找到了InstantiationAwareBeanPostProcessor接口的实现类的注册入口,即org.springframework.context.support.AbstractApplicationContext#refresh--->registerBeanPostProcessors;

2、进入到AbstractApplicationContext#registerBeanPostProcessors方法内,会发现这段代码很干净,即依赖于PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法;
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
3、进入到PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法又是另一番洞天:大致是可以分为四步,第一步,获取所有实现BeanPostProcessor接口的实现类的名称,实现方式示例中的MyBeanPostProcessors就在其中;

第二步,提前注册BeanPostProcessorChecker,主要用途是用于Bean创建过程中的日志信息打印记录;

第三步,就是把所有的BeanPostProcessor接口的实现类,按照是否实现PriorityOrdered接口、是否实现Ordered接口、其他,分为三组;

最后一步内容很长,不过很简单,即按第二步分成的三类,依次注册,具体的顺序是:1:实现PriorityOrdered接口BeanPostProcessor接口的实现类、2:实现实现Ordered接口BeanPostProcessor接口的实现类、3:其他的BeanPostProcessor接口的实现类,其中MyInstantiationAwareBeanPostProcessor属于第三类;

总结,InstantiationAwareBeanPostProcessor接口的实现类的注册,其本质是把接口的实现类注入到Spring容器的一个集合里存起来,具体的注册逻辑在PostProcessorRegistrationDelegate#registerBeanPostProcessors()。

执行时机
从实现方式的示例的单元测试执行结果可以看出,InstantiationAwareBeanPostProcessor接口继承于BeanPostProcessor的postProcessBeforeInitialization()和postProcessAfterInitialization()扩展点的执行时机是在Bean(ExampleController)实例化、属性注入完成后触发执行的,其具体的工作原理可以参考Springboot的扩展点之BeanPostProcessor,下面就InstantiationAwareBeanPostProcessor接口的另外三个扩展点的执行时机进行分析。
1、从实现方式示例中,可知InstantiationAwareBeanPostProcessor的第一个扩展点postProcessBeforeInstantiation()是在Eean实例化前触发执行的,所以寻找其执行时机的第一步就要找到Bean实例化的入口,通过debug很快找到了这个位置 ,AbstractApplicationContext#refresh--->finishBeanFactoryInitialization();

2、进入到finishBeanFactoryInitialization()方法,会发现ExampleController这个Bean的实例化是在
DefaultListableBeanFactory#preInstantiateSingletons方法;

3、这里实际被调用的是DefaultListableBeanFactory#preInstantiateSingletons方法,进入到这个方法内,又调用了AbstractBeanFactory#getBean(),从这才算真正Bean的获取或创建的入品口口。这里大致介绍一下getBean()的逻辑:当获取某一个bean时,先查询缓存确定是否存在,若存在,则直接返回,若不存在,则开始创建Bean,若Bean内依赖了另外一个Bean,则是上述过程的一个递归。

4、进入到AbstractBeanFactory#getBean方法,发现又调用了AbstractBeanFactory#doGetBean
-->DefaultSingletonBeanRegistry#getSingleton()
-->AbstractAutowireCapableBeanFactory#createBean();

这里需要注意一下,用到了lambda表达式,即把下面的函数式接口的具体实现作为DefaultSingletonBeanRegistry#getSingleton()的第二个形参singletonFactory
传进去,在DefaultSingletonBeanRegistry#getSingleton()内,执行到singletonFactory.getObject()时触发下面lambda表达式内的createBean();(lambda表达式的应用在Spirng的源码中应用很广泛,所以对这块不太熟悉的小伙伴可以着重研究一下)
() -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {destroySingleton(beanName);throw ex;}
}
postProcessBeforeInstantiation()
5、源码分析本身并不难,难的是需要耐心,分析这么多了,实际上还未走到进入到InstantiationAwareBeanPostProcessor的扩展点,但是已经很接近了。进入到AbstractAutowireCapableBeanFactory#createBean()
--->AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation(),定睛一看第一个扩展点到了,具体且看下面的注释内容:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {Object bean = null;if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {// 检查bean是不是合成的Bean,//InstantiationAwareBeanPostProcessor接口的实现类是否己注册if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {//查询出目标Bean的Class实例Class<?> targetType = determineTargetType(beanName, mbd);if (targetType != null) {//开始执行第一个扩展方法postProcessBeforeInstantiationbean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);if (bean != null) {bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);}}}mbd.beforeInstantiationResolved = (bean != null);}return bean;
}
具体的扩展点postProcessBeforeInstantiation()的执行也是朴实无华:查询出所有接口的实现类,然后循环执行;只一需要注意的是扩展点处的返回值处理,这个时候目标Bean(ExampleController)未实例化,这时可以自定义一个Bean对象作为返回值把目标Bean替换掉,那么后续的postProcessBeforeInitialization扩展点还会触发执行,而其他扩展点将会跳过不再执行;什么意思呢?简单点理解就是正常情况下Spring管理目标Bean会一步一步的实例化、属性注入、初始化,但你想要实现点个性化的东西,Spring就把这个Bean实例化及以后的权利交给你了。
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);if (result != null) {return result;}}}return null;
}
postProcessAfterInstantiation()
6、从AbstractAutowireCapableBeanFactory#createBean()
--->AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation()完成第一个扩展点的执行,继续往下走就是AbstractAutowireCapableBeanFactory#doCreateBean了,要找到第二个、第三个扩展点都在这里面;

7、进入到AbstractAutowireCapableBeanFactory#doCreateBean方法,如下图,instanceWrapper.getWrappedInstance()完成了目标Bean(ExampleController)的示例化,但是属性exampleService还未注入;

继续往下走,调用populateBean(),完成对已实例化的目标Bean(ExampleController)的属性注入;如图进入到populateBean()就开始了第二个扩展点postProcessAfterInstantiation()方法的执行;这里要注意一下返回值的类型为布尔类型:如果返回true,则会继续执行第三个扩展点;如果返回fase,则第三个扩展点不会执行,直接return跳出了当前方法;

postProcessProperties()
还是在AbstractAutowireCapableBeanFactory#populateBean方法中,第二个扩展点执行过且返回值为true,接着往下就会执行到第三个扩展点postProcessProperties();

第三个扩展点要着重说一下,第三个扩展触发执行的时候Bean已经实例化,但是未完成属性注入,但是这里可以在里替换掉即将注入的属性。在实现方式的示例代码ExampleController类中,定义了一个String类型的属性creator,默认值为"gaox",在MyInstantiationAwareBeanPostProcessor#postProcessProperties中,把“gaox”替换为“fanfu”,再看单元测试的执行结果,默认值“gaox”被替换成了“fanfu”;
应用场景
其实了解了InstantiationAwareBeanPostProcessorr的功能特性、实现方式和工作原理,在遇到类似的业务需求的时候都可以应用这些扩展点,这里举一个平时开发中经用到但是应该没有注意到的一个Spring内部经典实现AutowiredAnnotationBeanPostProcessor。
通过的UML类图,可以看到AutowiredAnnotationBeanPostProcessor继承于InstantiationAwareBeanPostProcessorAdapter,而InstantiationAwareBeanPostProcessorAdapter又实现了SmartInstantiationAwareBeanPostProcessor接口,SmartInstantiationAwareBeanPostProcessor接口又继承了InstantiationAwareBeanPostProcessor。

AutowiredAnnotationBeanPostProcessor又实现了InstantiationAwareBeanPostProcessor的postProcessProperties();实现方式中示例代码ExampleController类的exampleService属性用@Autowired注解标记后,实际的注入实现逻辑就在AutowiredAnnotationBeanPostProcessor的postProcessProperties()内,有兴趣的小伙伴可以继续深入下去探寻一翻。
@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;
}
相关文章:

Springboot扩展点之InstantiationAwareBeanPostProcessor
Springboot扩展点系列实现方式、工作原理集合:Springboot扩展点之ApplicationContextInitializerSpringboot扩展点之BeanFactoryPostProcessorSpringboot扩展点之BeanDefinitionRegistryPostProcessorSpringboot扩展点之BeanPostProcessorSpringboot扩展点之Instant…...

基于 U-Net 网络的遥感图像语义分割 完整代码+论文
一、研究目的U-Net 是一种由全卷积神经网络启发的对称结构网络,在医疗影像分割领域取得了很好的效果。 此次研究尝试使用 U-Net 网络在对多光谱遥感影像数据集上进行训练,尝试使用卷积神经网络自动分割出建筑,希望能够得到一种自动分割遥感影…...

Codeql 编译Shiro1.2.4爬坑
0x00 前言 这个Codeql一定要编译才能生成Database,是真的比较恼火,很多项目都不一定可以生成,环境就是一个非常大的坑,为了防止以后,所以将shiro1.2.4编译过程进行记录。 0x01 正文 首先是需要下载到shiro1.2.4的源…...

新C++(9):谈谈,翻转那些事儿
"相信羁绊,相信微光,相信一切无常。"一、AVL树翻转那些事儿(1)什么是AVL树?在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。…...
Java深克隆的几种方式
目录 1、通过继承Cloneable接口,重写clone方法实现深克隆 2、通过序列化与反序列化的方式实现深克隆 3、第三方工具类实现深克隆,克隆对象需继承Serializable接口 3.1、Apache Commons Lang的SerializationUtils.clone方法 3.2、Gson工具类 3.3、F…...

PointNet++的源码运行
首先,从github上下载源码https://github.com/yanx27/Pointnet_Pointnet2_pytorch也可以从百度网盘下载链接:https://pan.baidu.com/s/1sgTYuqnBVC9p3bib450SOQ 提取码:gujd再下载对应的测试数据分类数据modelnet40_normal_resampled下载&…...

npm 上传自己的包
mkdir demo 创建一个新的文件夹 npm init 初始化项目 生成一个package.json文件 name version description等等touch index.js 创建一个node 可执行脚本新的js 文件 #!/usr/bin/env node // 必须在文件头加如上内容指定运行环境为node console.log(hello cli)在package.json 中…...

【Linux】常用命令大全(二)
目录 4. Linux常用命令 4.1 Linux命令初体验 4.2 文件目录操作命令 4.3 拷贝移动命令 4.4 打包压缩命令 4.5 文本编辑命令 4.6 查找命令 4. Linux常用命令 4.1 Linux命令初体验 4.1.1 常用命令演示 在这一部分中,我们主要介绍几个常用的命令,…...

第一章 操作系统概述
目录一、什么是操作系统?1、操作系统的概念2、计算系统的构成3、主要作用二、操作系统有哪些功能?1、操作系统的目标2、操作系统的功能三、操作系统有哪些特征?1、并发性2、共享性3、虚拟性4、异步性四、操作系统的运行机制是怎样的ÿ…...

ChatGPT为什么不受开发者喜欢?
记得 ChatGPT 最开始上线不久的时候,看到的大部分尝鲜和测试结果都是开发者在做进行敲代码测试,可以说职业危机感非常强的一群人了。 再者,加上 ChatGPT 要使用起来其实是有一些技术门槛的,愿意折腾的人也多是程序员,…...
Lua table
Table(表) table 是 lua 中唯一的数据结构,可以用于表示 数组,字典与结构体。它非常强大,可以储存任何数据类型。 table 的数据单元为一对键值。 table 是不固定大小的,你可以根据自己需要进行扩容。 构…...

JavaScript:使用for in不是一个很好的抉择
for in 如果让你遍历对象中的key和value,你第一个想到的一定是使用for in const o{name:"chengqige",age:23 } for (let key in o){console.log(key,o[key]); }看起来是没有问题的,但是如果我在下面加一行代码,输出的结果就可能让…...
Go语言学习小笔记(一)
Go语言学习小笔记(一) 入口 项目的主入口:一般在main.go 包导入 一个包定义一组编译过的代码,包的名字类似命名空间,可以用来间接访问包内声明的标识符 所有处于同一个文件夹中的代码文件,必须使用同一…...

前端Docker部署方案
一、Docker容器和镜像概念 首先明确镜像和容器的概念。我们可以用 docker 构建一个镜像,这个镜像可以导入导出,用于传输,重复利用。然后如果把他 run 起来,则称为一个容器。容器是运行时,会包括运行时上下文ÿ…...
Java——无重叠区间
题目链接 leetcode在线oj题——无重叠区间 题目描述 给定一个区间的集合 intervals ,其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。 题目示例 输入: intervals [[1,2],[2,3],[3,4],[1,3]] 输出: 1 解释…...
数据库和数据表创建与管理操作
数据库和数据表创建与管理操作 MySQL中,一个完整的而数据存储过程主要分成4步: 创建数据库确认字段创建数据表插入数据 标识符命名规则 数据库名、表名不得超过30个字符,变量名限制为29个必须只能包含 A–Z, a–z, 0–9, _共63个字符数据…...
buu [ACTF新生赛2020]crypto-rsa3 1
题目描述: from flag import FLAG from Cryptodome.Util.number import * import gmpy2 import random e65537 p getPrime(512) q int(gmpy2.next_prime) n p*q m bytes_to_long(FLAG) c pow(m,e,n) print(n) print( c ) n 177606504836499246970959030226871…...

知识库:在医疗行业的知识管理有着怎样的意义与实际影响?
知识库中还可存在一个通常被称作典型方法库的特殊部分。如果对于某些问题的解决途径是肯定和必然的,就可以把其作为一部分相当肯定的问题解决途径直接存储在典型方法库中。这种宏观的存储将构成知识库的另一部分。在使用这部分时,机器推理将只限于选用典…...

带你一步步搭建Web自动化测试框架
测试框架的设计有两种思路,一种是自底向上,从脚本逐步演变完善成框架,这种适合新手了解框架的演变过程。另一种则是自顶向下,直接设计框架结构和选取各种问题的解决方案,这种适合有较多框架事件经验的人。本章和下一张…...

Redis进阶-缓存问题
Redis 最常用的一个场景就是作为缓存,本文主要探讨Redis作为缓存,在实践中可能会有哪些问题?比如一致性、击穿、穿透、雪崩、污染等。 为什么要理解Redis缓存问题 在高并发业务场景下,数据库大多数情况都是用户并发访问最薄弱的…...

VS Code Spring 全新功能来了!
大家好,欢迎来到我们 2023 年的第一篇博客!我们想与您分享几个与 Spring 插件、代码编辑和性能相关的激动人心的更新,让我们开始吧! Spring 插件包的新入门演练 演练(Walkthrough) 是一种多步骤、向导式的体…...
关于大数据导入流程引擎ccflow的方案
问题: 1. 现在的流程系统里有几百万条已经运行的流程其它的流程架构上 2. 需要把这样的数据导入到ccflow流程引擎里面去。 数据结构分析: 1. ccflow有流程引擎注册表,工作人表,业务数据表与日志表4大表. 2. ccflow的流程实例是一个int类型的…...

AI 生成二次元女孩,免费云端部署(仅需5分钟)
首先需要google的colab,免费版本GPU有额度。其次,打开github网站,选择一个进入colab,修改代码 !apt-get -y install -qq aria2 !pip install -q https://github.com/camenduru/stable-diffusion-webui-colab/releases/download/0.0.16/xforme…...

掌握MySQL分库分表(六)解决主键重复问题--Snowflake雪花算法
文章目录问题及需求常用ID解决方案数据库自增IDUUIDRedis发号器Snowflake雪花算法分布式 ID 生成算法Snowflake原理关于bit与byte雪花算法的位数Snowflake必须注意的地方全局唯⼀、不能重复保证各个系统时间一致Snowflake雪花算法实现雪花算法测试结果问题及需求 单库下⼀般使…...

Melis4.0[D1s]:1.启动流程(与adc按键初始化相关部分)跟踪笔记
文章目录1.启动流程1.1 最先进入的文件:head_s.S1.2 start_kernel()函数所在的文件:init.c1.3 input_init()函数所在文件:sys_input.c1.4 INPUT_LKeyDevInit()所在文件:keyboarddev.c1.5 esINPUT_RegLdev()所在文件:in…...
GNU make 中文手册 第三章:Makefile 总述
一、Makefile 总述 3.1 Makefile 的内容 在一个完整的 Makefile 中,包含了 5 个东西:显式规则、隐含规则、变量定义、指示符和注释。关于“规则”、“变量” 和 “Makefile 指示符” 将在后续的章节进行详细的讨论。本章讨论的是一些基本概念。 显式规…...

简历的专业技能怎么写?排版需要注意的事项
一、简历的专业技能怎么写? 首先,先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几 天时间学习一下,然后在简历上可以写上自己了解这个技…...

【Git】为什么需要版本控制?版本控制工具有那些?
目录 一、为什么需要版本控制? 二、版本控制工具有那些? 💟 创作不易,不妨点赞💚评论❤️收藏💙一下 一、为什么需要版本控制? 首先我们要知道什么是版本控制?对版本控制进行文字…...
SSH远程执行Python3 Error: UnicodeEncodeError: ‘ascii‘ codec
首先确定要执行脚本服务器的语言编码环境,执行 # locale -a C en_US.utf8 POSIX # locale LANGen_US.utf8 LC_CTYPE"en_US.utf8" LC_NUMERIC"en_US.utf8" LC_TIME"en_US.utf8" LC_COLLATE"en_US.utf8" LC_MONETARY"…...
极简TypeScript教程--面向对象
在早期的JavaScript开发中(ES5)我们需要通过函数和原型链来实现类和继承,从ES6开始,引入了class关键字,可以更加方便的定义和使用类。TypeScript作为JavaScript的超集,也是支持使用class关键字的࿰…...