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…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
