深入理解SpringBoot(一)----SpringBoot的启动流程分析
1、SpringApplication 对象实例化
SpringApplication 文件
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {// 传递的source其实就是类Bootstrapreturn new SpringApplication(sources).run(args);// 实例化一个SpringApplication对象执行run方法
}
实例化的时候又会执行initialize 方法
private void initialize(Object[] sources) {// 这个source依旧是上文说的Bootstrap.class 类if (sources != null && sources.length > 0) {this.sources.addAll(Arrays.asList(sources));// 添加到source资源列表里面去}this.webEnvironment = deduceWebEnvironment();// 设置其是否为web环境setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 拆分为两步,一步是getSpringFactoriesInstances,再者就是set操作// set操作很简单,就是设置当前对象的初始化对象以及监听器this.mainApplicationClass = deduceMainApplicationClass();// 通过堆栈信息,推断 main方法的类对象为当前的主程序类
}private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };private boolean deduceWebEnvironment() {for (String className : WEB_ENVIRONMENT_CLASSES) {// 遍历包含上述两个类名称的数组if (!ClassUtils.isPresent(className, null)) {// 一旦发现不存在该类,就立即返回 deduce 推断不是web环境return false;}}// 必须同时包含两个类,才推断出为web环境return true;
}
getSpringFactoriesInstances 方法操作
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {// 传递的type就是上面说的ApplicationContextInitializer.class以及ApplicationListener.class类// 类型以及参数目前都没有具体指ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 通过SpringFactoriesLoader 获取对应的名称,具体详情可以看下面的代码块// 这点需要重点关注下!!!// 结果就是返回一个set集合List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);// 看样子就是创建一个实例的集合AnnotationAwareOrderComparator.sort(instances);// 然后通过AnnotationAwareOrderComparator 的排序规则跪实例集合进行排序// 排序就是看是否存在Order或者Priority注解,然后取得注解的值,排在集合前面return instances;
}private <T> List<T> createSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,Set<String> names) {List<T> instances = new ArrayList<T>(names.size());for (String name : names) {// 遍历上面取到的name 集合try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);// 取到这个类名称的类Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);// 获取当前类的符合当前参数的构造器T instance = (T) BeanUtils.instantiateClass(constructor, args);// 利用反射的方式生成具体的对象instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}// 最后生成name映射的实例集合return instances;
}
SpringFactoriesLoader.loadFactoryNames 方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {// 传递的factoryClass 就是上面的ApplicationContextInitializer、ApplicationListener.等String factoryClassName = factoryClass.getName();// 获取类的全名称try {Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));// 如果类加载器为null,则使用系统默认的方法,否则使用当前传递的类加载器读取// 当前类加载器可以获取到的所有文件路径为“META-INF/spring.factories” 的地址List<String> result = new ArrayList<String>();while (urls.hasMoreElements()) {// 迭代遍历urlURL url = urls.nextElement();Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));// 读取映射的spring.factories 文件的KV键值对,存放到properties对象中String factoryClassNames = properties.getProperty(factoryClassName);// 类似于map一般,获取对应的值result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));// 对值使用逗号分隔,生成list,然后去重添加到result}// 总结下来就是遍历当前类环境中的所有路径为“META-INF/spring.factories”的文件// 读取文件,然后获取k为当前类名称的所有值,然后存储到set中返回return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);}
}
到这里整个的initialize操作就已经清楚了,通过类加载器可获取的所有为“META-INF/spring.factories” 的地址的文件内容,然后获取key为ApplicationContextInitializer.class以及ApplicationListener.class的类名称的值集合
然后依次就行实例化,最后排序返回,最后保存到当前对象的初始化集合以及监听器集合中,便于后续操作
需要注意到SpringFactoriesLoader.loadFactoryNames 后面很多地方都需要使用该方法去获取相关内容
当然现在只是完成了SpringApplication构造器里面的方法,还剩下后面的run(args)方法执行
如下代码块就是SpringBoot的执行过程(最后的套路依旧是Spring Framework的执行策略)
利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。
1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
2、ApplicationListener 当springboot启动时事件change后都会触发
总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类
2、SpringApplication的run方法启动
public ConfigurableApplicationContext run(String... args) {// 这是个计时器StopWatch stopWatch = new StopWatch();stopWatch.start();// 记录当前服务开始启动ConfigurableApplicationContext context = null;// 上下文context,非常关键FailureAnalyzers analyzers = null;configureHeadlessProperty();// 给系统设置headless属性值,就是设置了一些环境变量SpringApplicationRunListeners listeners = getRunListeners(args);// 获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法// 就是通过SpringFactoriesLoader 获取到所有SpringApplicationRunListener.class的对象// 其中args是用来进行实例化SpringApplicationRunListener对应的对象的构造器参数// 最后返回listener是整个系统的监听器listeners.starting();// 监听器开始执行try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 默认程序参数ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);// 准备运行的环境上下文Banner printedBanner = printBanner(environment);// 打印banner,默认输出当前springboot版本等内容,可以自定义设置文本或者图片// 具体看下面的方法详解context = createApplicationContext();// 创建SpringBoot最重要的上下文容器analyzers = new FailureAnalyzers(context);// 分析上下文出现问题的点,便于使用者可以直观的发现问题出现在哪里// 其实套路类似,就是使用SpringFactoriesLoader获取所有的FailureAnalyzer实例对象,然后设置其bean工厂为context的bean工厂上下文prepareContext(context, environment, listeners, applicationArguments,printedBanner);// 看名称就是对context的前置准备工作,细节在后面说refreshContext(context);// 切入到spring framework的方式去完成context内容的装载// 如果需要注册终止钩子,则注册一个afterRefresh(context, applicationArguments);// 基本上认为springboot所需的服务都加载完成,进行最后的处理操作// 里面常用的就是CommandLineRunnerlisteners.finished(context, null);// 监听器的启动结束事件,stopWatch.stop();// 表示SpringBoot服务启动步骤完成,统计下启动时间等操作if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);// 打印SpringBoot启动成功的消息,例如 Started xxx in 12.4 seconds 等信息}return context;}catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);// 启动失败了就会输出Application startup failed 日志// 并且会输出具体的错误内容信息throw new IllegalStateException(ex);}
}private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();// 如果当前环境值不为null,直接返回// 否则根据上文推断出的webEnvironment boolean 值 生成对象的环境对象// 当为true的时候,生成StandardServletEnvironment// 否则生成的是StandardEnvironmentconfigureEnvironment(environment, applicationArguments.getSourceArgs());listeners.environmentPrepared(environment);if (!this.webEnvironment) {// 如果不是web的环境,再对当前的环境进行包装,生成一个新的运行环境对象environment = new EnvironmentConverter(getClassLoader()).convertToStandardEnvironmentIfNecessary(environment);}return environment;
}private Banner printBanner(ConfigurableEnvironment environment) {// 参数environment就是上面生成的环境对象if (this.bannerMode == Banner.Mode.OFF) {// 如果设置了banner关闭模式,则不进行打印输出操作return null;}ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader: new DefaultResourceLoader(getClassLoader());// 资源加载器生成SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);// 后续使用SpringApplicationBannerPrinter 类的print进行输出操作if (this.bannerMode == Mode.LOG) {// 打印模式,如果是log则输出到log中,否则输出到终端中return bannerPrinter.print(environment, this.mainApplicationClass, logger);}return bannerPrinter.print(environment, this.mainApplicationClass, System.out);// 大致操作就是先看是否存在自定义的图片类型或者文字类型 banner,如果有就优先确定banner对象// 否则就默认使用SpringBootBanner的banner(这个里面就包含了常规的springboot输出内容)// 然后解析banner的资源,得出将要输出的字符串内容(利用日志直接输出),存储到PrintedBanner
}public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {contextClass = Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);// 如果是web环境,则使用AnnotationConfigEmbeddedWebApplicationContext// 否则就使用AnnotationConfigApplicationContext}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);// 直接通过类,反射生成无构造参数的对象,一般情况就是AnnotationConfigEmbeddedWebApplicationContext对象了
}private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 传递上下文、环境、上下文参数等数据context.setEnvironment(environment);postProcessApplicationContext(context);// 前置处理context上下文,包含了beanNameGenerator和resourceLoader// 其中beanNameGenerator 可以自定义规则约定bean的名称功能applyInitializers(context);// 应用ApplicationContextInitializer去初始化完成对context的操作// 具体的ApplicationContextInitializer对象就是在SpringApplication对象的构造方法中实例化创建的// 可以给context添加额外的操作,同时也可以很方便的自定义完成自己需要的功能listeners.contextPrepared(context);// 执行contextPrepared 上下文准备工作的事件if (this.logStartupInfo) {// 日志启动标志位,默认为truelogStartupInfo(context.getParent() == null);logStartupProfileInfo(context);// 明确当前执行的主函数log,输出SpringBoot的开始启动信息}// 注册springApplicationArguments 这个bean到context中去context.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);if (printedBanner != null) {context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);// 同样是注册,打印早就完成了}// Load the sourcesSet<Object> sources = getSources();// 一般情况下这个source就是SpringBoot 启动的主类Class,注意不是实例对象Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[sources.size()]));// 把source也就是主类当做bean,加载到spring的容器中listeners.contextLoaded(context);// 监听器的上下文导入完成事件 执行
}private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<Object>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);// 从context获取ApplicationRunner和CommandLineRunner 对象// 然后按照对应的规则进行排序for (Object runner : new LinkedHashSet<Object>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}// 分别执行各自的run方法}// 一般情况,我们如果需要在SpringBoot加载完成后需要完成一些自定义操作就是注册// ApplicationRunner或者CommandLineRunner 的bean对象,然后自定义实现run方法即可
}
3、总结
就SpringBoot的启动整个过程而已,还是很清晰的,SpringBoot的套用SpringFramework的机制,为我们自定义实现功能提供了很好的便利,整个的SpringBoot就是重新包装了一个SpringFramework。
1、new了一个SpringApplication对象,使用SPI技术加载加载 ApplicationContextInitializer、ApplicationListener 接口实例
2、调用SpringApplication.run() 方法
3、调用createApplicationContext()方法创建上下文对象,创建上下文对象同时会注册spring的核心组件类(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等)。
4、调用refreshContext() 方法启动Spring容器和内置的Servlet容器(tomcat),内置的Servlet容器就是在onRefresh() 方法里面启动的
引用(本文章只供本人学习以及学习的记录,如有侵权,请联系我删除)
SpringBoot 启动过程源码分析
SpringBoot启动流程总结

喜欢的朋友记得点赞、收藏、关注哦!!!
相关文章:

深入理解SpringBoot(一)----SpringBoot的启动流程分析
1、SpringApplication 对象实例化 SpringApplication 文件 public static ConfigurableApplicationContext run(Object[] sources, String[] args) {// 传递的source其实就是类Bootstrapreturn new SpringApplication(sources).run(args);// 实例化一个SpringApplication对象执…...

MySql基础-单表操作
1. MYSQL概述 1.1 数据模型 关系型数据库 关系型数据库(RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库。 特点: 使用表存储数据,格式统一,便于维护 使用SQL语言操作,标准统一&…...

【STM32系统】基于STM32设计的SD卡数据读取与上位机显示系统(SDIO接口驱动、雷龙SD卡)——文末资料下载
基于STM32设计的SD卡数据读取与上位机显示系统 演示视频: 基于STM32设计的SD卡数据读取与上位机显示系统 简介:本研究的主要目的是基于STM32F103微控制器,设计一个能够读取SD卡数据并显示到上位机的系统。SD卡的数据扇区读取不仅是为了验证存…...
SpringBoot开发——整合Redis
文章目录 1、创建项目,添加Redis依赖2、创建实体类Student3、创建Controller4、配置application.yml5、整合完成 Redis ( Remote Dictionary Server )是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(…...
OpenCV结构分析与形状描述符(17)判断轮廓是否为凸多边形的函数isContourConvex()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 测试轮廓的凸性。 该函数测试输入的轮廓是否为凸的。轮廓必须是简单的,即没有自相交。否则,函数的输出是不确定的。 cv:…...
P5425 [USACO19OPEN] I Would Walk 500 Miles G
*原题链接* 很离谱的题。首先可以想到暴力连边,整个图为一个完全图,将所有的边选出来,然后从小到大一条条加入,当剩下集合数量 <K 的时候就结束。答案为加入的最后一条边的大小。如果用prim算法的话时间复杂度为。足以通过此题…...

Java高级Day41-反射入门
115.反射 反射机制 1.根据配置文件re.properties指定信息,创建Cat对象并调用hi方法 SuppressWarnings({"all"}) public class ReflectionQuestion {public static void main(String[] args) throws IOException {//根据配置文件 re.properties 指定信息…...
在Linux系统上使用Docker部署java项目
一.使用Docker部署的好处: 在Linux系统上使用Docker部署项目通常会大大简化部署流程,因为Docker可以将应用程序及其依赖打包到一个独立的容器中。 Docker打包应用程序时会将其与所有依赖项(操作系统、库等)一起打包。这样&#…...

【C++】标准库IO查漏补缺
【C】标准库 IO 查漏补缺 文章目录 系统I/O1. 概述2. cout 与 cerr3. cerr 和 clog4. 缓冲区5. 与 printf 的比较 系统I/O 1. 概述 标准库提供的 IO 接口,包含在 iostream 文件中 输入流: cin输出流:cout / cerr / clog。 输入流只有一个 cin&#x…...
python简单易懂的lxml读取HTML节点及常用操作方法
python简单易懂的lxml读取HTML节点及常用操作方法 1. 初始化和基本概念 lxml 是一个强大的pyth库,用于处理XML和HTML文档。它提供了类似BeautifulSoup的功能,但性能更高。在使用lxml时,通常会先解析HTML或XML文档,得到一个Eleme…...

Java | Leetcode Java题解之第406题根据身高重建队列
题目: 题解: class Solution {public int[][] reconstructQueue(int[][] people) {Arrays.sort(people, new Comparator<int[]>() {public int compare(int[] person1, int[] person2) {if (person1[0] ! person2[0]) {return person2[0] - perso…...

安卓获取apk的公钥,用于申请app备案等
要申请app的icp备案等场景,需要app的 证书MD5指纹和公钥,示例如下: 步骤1:使用keytool从APK中提取证书 1. 打开命令行,cd 到你的apk目录,如:app/release 2. 解压APK文件: unzip yo…...

【leetcode_python】杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]] 方案&#…...

Parallels Desktop 20 for Mac中文版发布了?会哪些新功能
Parallels Desktop 20 for Mac 正式发布,完全支持 macOS Sequoia 和 Windows 11 24H2,并且在企业版中引入了全新的管理门户。 据介绍,新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新,最大的亮点是全新推出的 Parallels…...
SpringBoot整合SSE-灵活管控连接
SpringBoot整合SSE(管控连接) 1、sse单向通信整成逻辑双向通信。 2、轻量级实现端对端信息互通。 3、避免繁琐配置学习。 核心点通过记录连接码和心跳检测实现伪双向通道,避免无效连接占用过多内存。 服务器推送(Server Push)技术允许网站和应用在有新内容可用时主动向用户…...

挖矿木马-Linux
目录 介绍步骤 介绍 1、挖矿木马靶机中切换至root用户执行/root目录下的start.sh和attack.sh 2、题目服务器中包含两个应用场景,redis服务和hpMyAdmin服务,黑客分别通过两场景进行入侵,入侵与后续利用线路路如下: redis服务&…...
【leetcode——415场周赛】——python前两题
3289. 数字小镇中的捣蛋鬼 数字小镇 Digitville 中,存在一个数字列表 nums,其中包含从 0 到 n - 1 的整数。每个数字本应 只出现一次,然而,有 两个 顽皮的数字额外多出现了一次,使得列表变得比正常情况下更长。 为了…...

【CSS in Depth 2 精译_029】5.2 Grid 网格布局中的网格结构剖析(上)
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一章 层叠、优先级与继承(已完结) 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位(已完结) 2.1 相对…...
ZYNQ LWIP(RAW API) TCP函数学习
1 LWIP TCP函数学习 tcp_new()–新建控制块 这个函数用于分配一个TCP控制块,它通过tcp_alloc()函数分配一个TCP控制块结构来存储TCP控制块的数据信息, 如果没有足够的内容分配空间,那么tcp_alloc()函数就会尝试释放一些不太重要的TCP控制块, 比如就会释放处于TIME_WAIT、C…...
Spring Boot,在应用程序启动后执行某些 SQL 语句
在 Spring Boot 中,如果你想在应用程序启动后执行某些 SQL 语句,可以利用 spring.sql.init 属性来配置初始化脚本。这通常用于在应用启动时创建数据库表、索引、视图等,或者填充默认数据。data-locations 和 schema-locations 指定了 SQL 脚本…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...