Spring Bean 的生命周期详解
所谓万物皆对象,对于一个 bean 而言,从出生到死亡,他要经历哪些阶段呢?
生命周期
理解对象的生命周期,可以帮助我们更好的做一些扩展。
一个对象从被创建到被垃圾回收,可以大致分为这 5 个阶段:
- 创建/实例化阶段:调用类的构造方法,产生一个新对象;
- 初始化阶段:此时对象已经被创建了,但还未被正常使用,可以在这里做一些初始化的操作;
- 运行使用期:此时对象已经完全初始化好,程序正常运行,对象被使用;
- 销毁阶段:此时对象准备被销毁,需要预先的把自身占用的资源等处理好(如关闭、释放数据库连接);
- 回收阶段:此时对象已经完全没有被引用了,被垃圾回收器回收。
理解了一个 Bean 的生命周期后,下面我们看下SpringFramework怎么对Bean 的生命周期做干预的。
单实例 Bean 的生命周期
init-method & destroy-method
1)创建 Bean
package com.study.spring.a_initmethod;public class Cat {private String name;public Cat() {System.out.println("Cat 构造方法执行了。。。");}public void setName(String name) {System.out.println("setName方法执行了。。。");this.name = name;}public void init() {System.out.println(name + " 被初始化了。。。");}public void destroy() {System.out.println(name + " 被销毁了。。。");}
}public class Dog {private String name;public Dog() {System.out.println("Dog 构造方法执行了。。。");}public void setName(String name) {System.out.println("setName方法执行了。。。");this.name = name;}public void init() {System.out.println(name + "被初始化了。。。");}public void destroy() {System.out.println(name + "被销毁了。。。");}
}
2)分别创建 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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.study.spring.a_initmethod.Cat" init-method="init" destroy-method="destroy"><property name="name" value="小米米"/></bean>
</beans>
@Configuration
public class Config {@Bean(initMethod = "init",destroyMethod = "destroy")public Dog dog(){Dog dog = new Dog();dog.setName("小勾勾");return dog;}
}
3)分别测试xml 和注解驱动
private static void testXml() {System.out.println("准备初始化IOC容器。。。");ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-initmethod.xml");System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");context.close();System.out.println("IOC容器销毁完成。。。");}private static void testAnnotationConfig() {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");applicationContext.close();System.out.println("IOC容器销毁完成。。。");}
输出如下:
准备初始化IOC容器。。。
Cat 构造方法执行了。。。
setName方法执行了。。。
小米米 被初始化了。。。
IOC容器初始化完成。。。准备销毁IOC容器。。。
小米米 被销毁了。。。
IOC容器销毁完成。。。-----------------------------准备初始化IOC容器。。。
Dog 构造方法执行了。。。
setName方法执行了。。。
小勾勾被初始化了。。。
IOC容器初始化完成。。。准备销毁IOC容器。。。
小勾勾被销毁了。。。
IOC容器销毁完成。。。
由此可以得出结论:在 IOC 容器初始化之前,默认情况下 Bean 已经创建好了,而且完成了初始化动作;容器调用销毁动作时,先销毁所有 Bean ,最后 IOC 容器全部销毁完成。
同时也可以看出来,在 Bean 的生命周期中,是先对属性赋值,后执行 init-method
标记的方法。
@PostConstruct & @PreDestroy
上面的 Cat 和 Dog 都是我们手动声明注册的,但是对于那些使用模式注解的 Bean ,这种方式就不好使了,因为没有可以声明 init-method
和 destroy-method
的地方了。
此时可以使用JSR250
规范提供的 @PostConstruct
和 @PreDestroy
这两个注解,分别对应 init-method
和 destroy-method
。
比如,Spring中常用的模式注解:@Component、@Service、@Repository、@Controller,使用这些注解,Spring容器可以在启动时自动扫描并注册这些Bean,而不需要显式地在XML配置文件中声明或在Java配置类中手动注册。
1)创建Bean
@Component
public class Pen {private Integer ink;public Pen(){System.out.println("钢笔的构造方法");}@PostConstructpublic void addInk() {System.out.println("钢笔中已加满墨水。。。");this.ink = 100;}@PreDestroypublic void outWellInk() {System.out.println("钢笔中的墨水都放干净了。。。");this.ink = 0;}@Overridepublic String toString() {return "Pen{" + "ink=" + ink + '}';}
}
2)测试
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.study.spring.b_jsr250");System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");ctx.close();System.out.println("IOC容器销毁完成。。。");}
}
输出如下:
准备初始化IOC容器。。。
钢笔的构造方法
钢笔中已加满墨水。。。
IOC容器初始化完成。。。准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
可以得出结论:这两个注解实现的效果和 init-method
、 destroy-method
是一样的。
JSR250规范 与 init-method 共存
如果 @PostConstruct
和 @PreDestroy
和 init-method
和 destroy-method
共存,执行顺序是怎样的呢?
1)创建 Bean
public class Pen {private Integer ink;public Pen(){System.out.println("钢笔的构造方法");}public void open() {System.out.println("init method ...打开钢笔");}public void close() {System.out.println("destroy-method - 合上钢笔。。。");}@PostConstructpublic void addInk() {System.out.println("@PostConstruct...钢笔中已加满墨水。。。");this.ink = 100;}@PreDestroypublic void outWellInk() {System.out.println("@PreDestroy...钢笔中的墨水都放干净了。。。");this.ink = 0;}@Overridepublic String toString() {return "Pen{" + "ink=" + ink + '}';}
}
2)创建配置类
@Configuration
public class JSR250Configuration {@Bean(initMethod = "open",destroyMethod = "close")public Pen pen() {return new Pen();}
}
3)测试执行
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JSR250Configuration.class);System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");applicationContext.close();System.out.println("IOC容器销毁完成。。。");}
}
输出结果:
准备初始化IOC容器。。。
钢笔的构造方法
@PostConstruct...钢笔中已加满墨水。。。
init method ...打开钢笔
IOC容器初始化完成。。。准备销毁IOC容器。。。
@PreDestroy...钢笔中的墨水都放干净了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
可以得出结论:JSR250 规范的执行优先级高于 init / destroy。
InitializingBean & DisposableBean
是 SpringFramework 内部预先定义好的两个关于生命周期的接口,他们的触发时机与 init-method & destroy-method
、 @PostConstruct
@PreDestroy
一样,都是在 Bean 的初始化和销毁阶段要回调的。
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {private Integer ink;@Overridepublic void destroy() throws Exception {System.out.println("钢笔中的墨水都放干净了。。。");this.ink = 0;}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("钢笔中已加满墨水。。。");this.ink = 100;}@Overridepublic String toString() {return "Pen{" + "ink=" + ink + '}';}
}
2)测试执行
public class InitializingDisposableAnnoApplication {public static void main(String[] args) throws Exception {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.study.spring.c_initializingbean");System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");ctx.close();System.out.println("IOC容器销毁完成。。。");}
}
输出结果:
准备初始化IOC容器。。。
钢笔中已加满墨水。。。
IOC容器初始化完成。。。准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
三种生命周期并存
当一个 Bean 同时用这三种生命周期控制时,执行顺序又是怎么样的呢?
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {private Integer ink;public Pen(){System.out.println("构造方法");}public void open() {System.out.println("init-method - 打开钢笔。。。");}public void close() {System.out.println("destroy-method - 合上钢笔。。。");}@PostConstructpublic void addInk() {System.out.println("@PostConstruct 钢笔中已加满墨水。。。");this.ink = 100;}@PreDestroypublic void outwellInk() {System.out.println("@PreDestroy 钢笔中的墨水都放干净了。。。");this.ink = 0;}@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean - 写完字了。。。");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean - 准备写字。。。");}@Overridepublic String toString() {return "Pen{" + "ink=" + ink + '}';}}
2)创建配置类
@Configuration
public class Config {@Bean(initMethod = "open",destroyMethod = "close")public Pen pen(){return new Pen();}
}
3)测试执行
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);System.out.println("IOC容器初始化完成。。。");System.out.println();System.out.println("准备销毁IOC容器。。。");ctx.close();System.out.println("IOC容器销毁完成。。。");}
}
输出结果:
准备初始化IOC容器。。。
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。准备销毁IOC容器。。。
@PreDestroy 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
可以看到执行顺序是:@PostConstruct → InitializingBean → init-method
。
原型Bean的生命周期
当面介绍的都是单实例 Bean 的生命周期,而对于原型 Bean,它与单实例 Bean 的生命周期是不一样的。
单实例 Bean 的生命周期是陪着 IOC 容器一起的,容器初始化,单实例 Bean 也跟着初始化(延迟 Bean 例外);
容器销毁,单实例 Bean 也跟着销毁。
原型 Bean 由于每次都是取的时候才产生一个,所以它的生命周期与 IOC 容器无关。
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {private Integer ink;public Pen(){System.out.println("构造方法");}public void open() {System.out.println("init-method - 打开钢笔。。。");}public void close() {System.out.println("destroy-method - 合上钢笔。。。");}@PostConstructpublic void addInk() {System.out.println("@PostConstruct 钢笔中已加满墨水。。。");this.ink = 100;}@PreDestroypublic void outWellInk() {System.out.println("@PreDestroy 钢笔中的墨水都放干净了。。。");this.ink = 0;}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean - 准备写字。。。");}@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean - 写完字了。。。");}@Overridepublic String toString() {return "Pen{" + "ink=" + ink + '}';}
}
2)创建配置类
@Configuration
public class Config {@Bean(initMethod = "open",destroyMethod = "close")@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //原型Beanpublic Pen pen(){return new Pen();}
}
3)测试执行
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);System.out.println("IOC容器初始化完成。。。");}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
由此得出结论:原型 Bean 的创建不随 IOC 的初始化而创建。
然后,在main 方法中去获取该 Bean,再次执行:
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);System.out.println("IOC容器初始化完成。。。");System.out.println("准备获取pen");Pen pen = ctx.getBean(Pen.class);System.out.println("获取到pen");}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
准备获取pen
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
获取到pen
由此得出结论:原型Bean的初始化动作与单实例Bean完全一致。
接着,我们把销毁 Bean 的代码也加上:
public class Client {public static void main(String[] args) {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);System.out.println("IOC容器初始化完成。。。");System.out.println("准备获取pen");Pen pen = ctx.getBean(Pen.class);System.out.println("获取到pen");System.out.println("用完Pen了,准备销毁。。。");ctx.getBeanFactory().destroyBean(pen);System.out.println("Pen销毁完成。。。");}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
准备获取pen
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
获取到pen
用完Pen了,准备销毁。。。
@PreDestroy 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
Pen销毁完成。。。
由此得出结论;原型 Bean 在销毁时不处理 destroyMethod
标注的方法。
后置处理器 BeanPostProcessor
BeanPostProcessor
是一个容器的扩展点,它可以在 bean 的生命周期过程中,初始化阶段前后添加自定义处理逻辑,并且不同 IOC 容器间的BeanPostProcessor
不会相互干预。
也可以配置多个BeanPostProcessor
实例,通过设置order
属性来控制BeanPostProcessor
实例的执行顺序。
1)创建 bean
public class Dog implements InitializingBean {public void initMethod() {System.out.println("initMethod ...");}@PostConstructpublic void postConstruct() {System.out.println("@PostConstruct ...");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean ...");}
}
2)创建配置类
@Configuration
public class Config {@Bean(initMethod = "initMethod")public Dog dog() {return new Dog();}
}
3)创建两个后置处理器
@Component
public class InstantiationTracingBeanPostProcessor1 implements BeanPostProcessor, Ordered {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Dog) {System.out.println("【第一个后置处理器】拦截到Bean的初始化之前:" + bean);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Dog) {System.out.println("【第一个后置处理器】拦截到Bean的初始化之后:" + bean);}return bean;}@Overridepublic int getOrder() {return 1;}
}
@Component
public class InstantiationTracingBeanPostProcessor2 implements BeanPostProcessor, Ordered {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Dog) {System.out.println("【第二个后置处理器】拦截到Bean的初始化之前:" + bean);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Dog) {System.out.println("【第二个后置处理器】拦截到Bean的初始化之后:" + bean);}return bean;}@Overridepublic int getOrder() {return 2;}
}
4)测试执行
public class Client {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.study.spring.postprocessor");}
}
输出结果:
【第一个后置处理器】拦截到Bean的初始化之前:com.study.spring.postprocessor.Dog@7f9fcf7f
【第二个后置处理器】拦截到Bean的初始化之前:com.study.spring.postprocessor.Dog@7f9fcf7f
@PostConstruct ...
InitializingBean ...
initMethod ...
【第一个后置处理器】拦截到Bean的初始化之后:com.study.spring.postprocessor.Dog@7f9fcf7f
【第二个后置处理器】拦截到Bean的初始化之后:com.study.spring.postprocessor.Dog@7f9fcf7f
由此得出 bean 的初始化阶段的全流程:BeanPostProcessor#postProcessBeforeInitialization → @PostConstruct → InitializingBean → init-method → BeanPostProcessor#postProcessAfterInitialization
。
参考资料:《从 0 开始深入学习Spring小册》
相关文章:

Spring Bean 的生命周期详解
所谓万物皆对象,对于一个 bean 而言,从出生到死亡,他要经历哪些阶段呢? 生命周期 理解对象的生命周期,可以帮助我们更好的做一些扩展。 一个对象从被创建到被垃圾回收,可以大致分为这 5 个阶段:…...

MySQL【知识改变命运】12
视图 1:什么是视图2:创建视图使用视图(视图的好处)2.1.隐藏敏感字段2.2.对外提供统一访问3:视图和真实表进⾏表连接查询 4:修改视图数据4.1:通过真实表修改数据,会影响视图4.2&#…...
shell编程(完整版)
目录 一、shell脚本解释器 二、shell脚本的执行 三、变量的使用 四、永久环境变量 按用户设置永久环境变量 文件路径: 示例步骤: 删除永久环境变量 五、脚本程序传递参数怎么实现 六、用编程进行数学运算 shell中利用expr进行运算 运算与变量…...

数字逻辑(一)——导论
1.导论 1.1什么是数字逻辑? 数字逻辑是指在数字电路设计、计算机科学领域中对于离散的二进制信号进行逻辑处理、运算、存储和传输的基本原理和方法。 1.2数字量和模拟量的区别 数字量:在时间上和数量上都是离散的、不连续的物理量。模拟量࿱…...
量化交易系统开发-实时行情自动化交易-4.4.做市策略
19年创业做过一年的量化交易但没有成功,作为交易系统的开发人员积累了一些经验,最近想重新研究交易系统,一边整理一边写出来一些思考供大家参考,也希望跟做量化的朋友有更多的交流和合作。 接下来继续说说做市策略原理。 做市策…...

《线性代数的本质》
之前收藏的一门课,刚好期末复习,顺便看一看哈哈 课程链接:【线性代数的本质】合集-转载于3Blue1Brown官方双语】 向量究竟是什么 线性代数中最基础、最根源的组成部分就是向量,需要先明白什么是向量 不同专业对向量的看法 物理专…...
Gbase8s 允许内置用户创建用户以及创建只读权限用户以及利用角色管理普通用户权限
Gbase8s 允许内置用户创建用户以及创建只读权限用户以及利用角色管理普通用户权限 普通安装实例创建数据库以后,DBA权限只有gbasedbt用户。gbasdbt可以创建普通用户,并且给普通用户赋予库及权限或者表级权限。 但是gbasedbt用户口令和操作系统相关,所以想在不提供gbasedbt的…...

24/11/25 视觉笔记 深度传感器和手势识别
本章的目的是开发一个应用程序,使用深度传感器的输出实时检测和跟踪简单的手势。该应用程序将分析每个已捕捉的帧。并执行以下任务。 手部区域分割:通过分析Kinect传感器的深度图输出,在每一帧中提取用户的手部区域,这是通过阈值…...
迄今为止的排序算法总结
迄今为止的排序算法总结 7.10 迄今为止的排序算法总结复杂度和稳定性时间复杂度测试程序sortAlgorithm.hsortAlgorithm.cpptest.cpp 时间复杂度测试结果 7.10 迄今为止的排序算法总结 复杂度和稳定性 排序算法平均情况最好情况最坏情况稳定性空间复杂度选择排序O(n^2)O(n^2)O…...
HTML和CSS 表单、表格练习
HTML和CSS 表格练习 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTML表格练习</title>…...

H5流媒体播放器EasyPlayer.js网页直播/点播播放器如果H.265视频在播放器上播放不流畅,可以考虑的解决方案
随着流媒体技术的迅速发展,H5流媒体播放器已成为现代网络视频播放的重要工具。其中,EasyPlayer.js网页直播/点播播放器作为一款功能强大的H5播放器,凭借其全面的协议支持、多种解码方式以及跨平台兼容性,赢得了广泛的关注和应用。…...
Http 转 https 中 Nginx 的详细配置过程
摘要 本节将简要介绍从 HTTP 到 HTTPS 的配置过程,并完整展示 Nginx 的相关配置信息。 经过两天断断续续的调试,终于将 http 变成 https 了。现在说说这个安装 ssl 证书的过程。 服务器是在某云上。这个过程大致分为三个步骤:申请 ssl 证书、…...

【测试工具JMeter篇】JMeter性能测试入门级教程(二)出炉,测试君请各位收藏了!!!
上篇文章:CSDN 我们介绍了JMeter的一些原理介绍,以及安装配置和启动流程,本文我们就来讲讲JMeter如何使用。 一、JMeter目录结构组成 1. 根目录 Jmeter安装包解压后的根目录如下图: 1.1 backups目录:脚本备份目录&am…...

Otter 安装流程
优质博文:IT-BLOG-CN 一、背景 随着公司的发展,订单库的数据目前已达到千万级别,需要进行分表分库,就需要对数据进行迁移,我们使用了otter,这里简单整理下,otter 的安装过程,希望对…...

一文学会Golang里拼接字符串的6种方式(性能对比)
g o l a n g golang golang的 s t r i n g string string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去。主要有以下几种拼接方式 拼接方式介绍 1.使用 s t r i n g string string自带的运算符 ans ans s2. 使用…...
【笔记】Linux下编译Python3.10.15为动态库同时正确处理OpenSSL3依赖
之前自己第一次编译Python后发现pip会提示无法使用SSL,后来了解到是自己编译时没有配置OpenSSL。这个过程有点曲折,里面有一个坑,怕忘记于是写博客记录一下。 首先是下载OpenSSL,Python3.10.15支持此时最新版的OpenSSL 3.4.0&…...
Go语言获取客户端真实IP
在一些需求中,服务器需要记录客户端的ip地址,要获取ip地址,则需要有http.Request的对象参数传入,以下代码直接放在util中使用。 文件名:ip_utils.go package utilsimport ("context""github.com/spf1…...
大模型论文速递(11.23-11.25)
BlueLM-V3B 关键词:动态分辨率,图像放大,适应性网格化方法 研究问题:如何改进现有的动态分辨率匹配方法以减少在模型训练和部署中的计算复杂度? 方法: 分析现有动态分辨率匹配算法(如LLaVA-…...
维护在线重做日志(二)
迁移和重命名 可以使用操作系统命令重新定位重做日志,然后使用ALTER DATABASE语句使数据库知道它们的新名称(位置)。这个过程是必要的,例如,如果当前用于一些重做日志文件的磁盘将被删除,或者如果数据文件…...

.net core MVC入门(一)
文章目录 项目地址一、环境配置1.1 安装EF core需要包1.2 配置数据库连接二、使用EF创建表2.1 整体流程梳理2.1 建表详细流程三、添加第一个视图3.1整体流程梳理3.1 添加视图,并显示在web里四、使用EF增加Catogory数据,并且读取数据到页面4.1整体流程梳理4.2 实现五、增加Cat…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...