JVM:类加载子系统
一、类加载子系统概述
类加载子系统由多个类加载器组成,它们负责从文件系统或者网络中读取二进制形式的字节码(.class)文件,并将其加载进 JVM。字节码文件中关于类的定义、类中属性的定义、类中方法的定义以及类中方法的字节码等信息,会存储在方法区(Java 8 以后,元空间是方法区的一种实现方式 )的类信息部分。类的静态变量存储在方法区(Java 8 后位于堆中 ),字节码中的常量池(包括字符串常量、数字常量、类名、方法名、属性名等符号引用 )会被加载到方法区的运行时常量池部分
二、类的加载过程
-
加载:根据双亲委派机制和类的全限定名(类名 + 包名),合适的类加载器会将类的二进制形式的 .class 文件加载进 JVM。类的定义、类中属性的定义、类中方法的定义以及类中方法的字节码等信息会放在方法区(JDK 8 及以后为元空间)的类信息部分;在 JDK 7 及以前,类的静态变量放在方法区的静态变量部分,而 JDK 8 及以后,静态变量存储在堆中;字节码的常量池里的数据会被放在方法区的运行时常量池。并且在堆中生成这个类的 Class 对象,作为该类在 JVM 中的访问入口
-
验证:验证这个字节码文件是否合法,此阶段会从文件格式、元数据、字节码、符号引用等多方面进行检查,确保字节码符合 JVM 规范且不会危害 JVM 安全
-
准备:为类的静态变量分配内存并设置默认初始值。例如,int 类型的静态变量默认初始值为 0,boolean 类型为 false 等。若静态变量被 final 修饰,在准备阶段就会被初始化为指定的值
-
解析:把运行时常量池中的符号引用转换为直接引用。例如,将方法名、类名等符号引用指向方法的字节码、类的实际内存地址等实际引用,使 JVM 能准确找到并调用相关的类、方法和字段
-
初始化:会执行类的 <clinit>() 方法,这个方法由类的静态变量赋值语句和静态代码块合并而成,按照在源代码中的定义顺序执行。父类的 <clinit>() 方法会先执行。并且在多线程状态下,<clinit>() 方法是会加锁的,若多个线程都尝试调用该方法,同一时刻只有一个线程能够执行该方法,以保证类的初始化过程是线程安全的
三、类加载器分类
- 引导类加载器(Bootstrap ClassLoader):由 C/C++ 编写,通过本地的 C/C++ 方法进行初始化。它负责加载 Java 的核心类库,比如java.lang、java.util等基础包中的类,这些类是 JVM 运行的基础
- 扩展类加载器(Extension ClassLoader):由 Java 语言编写,它是由sun.misc.Launcher$extClassLoader实现。它是在 JVM 启动过程中,由引导类加载器加载sun.misc.Launcher类时,该类创建了扩展类加载器实例。扩展类加载器主要从java.ext.dirs系统属性指定的目录,或者 JDK 安装目录的jre/lib/ext子目录下加载类库
- 应用程序类加载器(AppClassLoader):同样由 Java 语言编写,由sun.misc.Launcher$AppClassLoader实现。它也是由sun.misc.Launcher创建。它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库,通常我们自己编写的类默认由它加载
- 用户自定义类加载器:开发人员可以通过继承java.lang.ClassLoader类来实现自定义类加载器,以满足如加载加密的类文件、从特定数据源加载类等特殊需求。在 JDK 1.2 之后,建议将自定义加载逻辑写在findClass()方法中,若需求不太复杂,也可继承URLClassLoader来简化编写
四、ClassLoader 使用说明
(1)ClassLoader 类
是抽象类,其他类加载器(除引导类加载器)都继承自它,包含getParent()、loadClass()等方法,用于获取父加载器、加载类等操作
(2)获取 ClassLoader 途径
- 通过当前类获取 ClassLoader:
- 在 Java 中,每个类都有一个 getClassLoader() 方法(java.lang.Class 类的方法),可以用来获取加载该类的类加载器。对于系统类(由引导类加载器加载),此方法会返回 null
public class ClassLoaderExample {public static void main(String[] args) {// 获取当前类 ClassLoaderExample 的类加载器ClassLoader classLoader = ClassLoaderExample.class.getClassLoader();System.out.println("通过当前类获取的 ClassLoader: " + classLoader);} }
- 在上述代码中,ClassLoaderExample.class.getClassLoader() 调用获取了加载 ClassLoaderExample 类的类加载器,通常情况下,如果是自己编写的类,会是应用程序类加载器(AppClassLoader)
- 在 Java 中,每个类都有一个 getClassLoader() 方法(java.lang.Class 类的方法),可以用来获取加载该类的类加载器。对于系统类(由引导类加载器加载),此方法会返回 null
- 通过当前线程上下文获取 ClassLoader:
- 线程上下文类加载器(Thread Context ClassLoader)可以在运行时动态地改变类加载器。可以通过 Thread 类的 getContextClassLoader() 方法来获取
public class ClassLoaderExample {public static void main(String[] args) {Thread currentThread = Thread.currentThread();ClassLoader contextClassLoader = currentThread.getContextClassLoader();System.out.println("通过当前线程上下文获取的 ClassLoader: " + contextClassLoader);} }
- 在默认情况下,主线程的上下文类加载器是应用程序类加载器。如果在某些框架(如 Spring)中,可能会被设置为自定义的类加载器,以实现特定的类加载需求
- 线程上下文类加载器(Thread Context ClassLoader)可以在运行时动态地改变类加载器。可以通过 Thread 类的 getContextClassLoader() 方法来获取
- 通过系统获取 ClassLoader:
- 可以通过 ClassLoader 类的静态方法 getSystemClassLoader() 来获取系统类加载器,在 Java 中,系统类加载器一般就是应用程序类加载器(AppClassLoader)
public class ClassLoaderExample {public static void main(String[] args) {ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println("通过系统获取的 ClassLoader: " + systemClassLoader);} }
- 运行这段代码,会输出应用程序类加载器的相关信息,它负责加载应用程序的类路径(classpath)下的类
- 可以通过 ClassLoader 类的静态方法 getSystemClassLoader() 来获取系统类加载器,在 Java 中,系统类加载器一般就是应用程序类加载器(AppClassLoader)
五、双亲委派机制
- 双亲委派机制的工作原理是:当一个类加载器收到加载类的请求时,它会先将该请求向上委派给其父类加载器,也就是按照用户自定义类加载器→应用程序类加载器→扩展类加载器→引导类加载器的顺序,直至到达引导类加载器。引导类加载器会首先尝试加载该类,如果它无法完成加载,就把任务交还给扩展类加载器尝试加载;若扩展类加载器也无法完成,再交给应用程序类加载器;若应用程序类加载器还是不行,最后由用户自定义类加载器来尝试加载
- 其好处是可以保护核心 API,防止被篡改。例如,即便自定义了一个 String 类,当加载请求经过层层委派到引导类加载器时,它会加载 Java 基础类库中的 String 类,加载过程随即终止,自定义的 String 类不会被加载
六、其他相关知识
(1)字节码文件
源代码经编译生成字节码指令,这些字节码指令以二进制形式存储在.class文件里
- 字节码文件的存储形式:.class 文件是一种二进制文件,它包含了一系列的字节码指令和其他元数据信息。这些信息以二进制形式编码,这样可以高效地存储和传输。字节码文件中的每个字节都有特定的含义,例如,某些字节表示操作码(如 getstatic、invokevirtual 等指令),而其他字节可能表示操作数或常量池索引等。由于字节码文件是二进制的,人类无法直接阅读和理解,所以需要使用反编译工具将其转换为人类可读的形式,比如使用 javap 工具或其他反编译软件
- 类加载器加载的字节码文件:类加载器在加载字节码文件时,处理的确实是二进制形式的数据。类加载器的主要职责之一就是从文件系统、网络或其他来源获取 .class 文件的二进制数据,并将其加载到 JVM 中。例如,当使用应用程序类加载器加载一个自定义类时,它会根据类的全限定名找到对应的 .class 文件,然后读取该文件的二进制内容。之后,类加载器会将这些二进制数据转换为 JVM 内部的数据结构,存储在方法区中
- JVM 里的字节码:JVM 在运行过程中处理的字节码本质上也是二进制形式的。在类加载过程中,字节码被加载到 JVM 的方法区,以二进制形式存储。当 JVM 执行字节码时,解释器或 JIT 编译器会读取这些二进制的字节码指令,并将其转换为机器码或直接执行。例如,解释器会逐行读取二进制的字节码指令,根据指令的含义进行相应的操作,如操作数栈的压栈和出栈、方法调用等
(2)线程上下文类加载器
线程上下文类加载器(Thread Context ClassLoader)是 Java 里很实用的机制,打破了双亲委派模型的限制,让线程在运行时能使用指定的类加载器来加载类
6.2.1Java SPI 机制示例
Java SPI(Service Provider Interface)是一种服务发现机制,核心思想是将接口和实现分离,在 META - INF/services 目录下定义服务提供者的配置文件。通常,启动类和服务提供者的实现类可能由不同的类加载器加载,这时就需要使用线程上下文类加载器
- 定义服务接口
// 定义一个简单的服务接口 public interface MyService {void doSomething(); }
- 实现服务接口
// 实现服务接口 public class MyServiceImpl implements MyService {@Overridepublic void doSomething() {System.out.println("MyServiceImpl is doing something.");} }
- 创建服务配置文件:在 src/main/resources/META - INF/services 目录下创建一个名为 com.example.MyService 的文件(com.example 是 MyService 接口所在的包名),文件内容为服务实现类的全限定名:
com.example.MyServiceImpl
- 使用线程上下文类加载器加载服务
import java.util.ServiceLoader;public class SPIDemo {public static void main(String[] args) {// 获取当前线程的上下文类加载器ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();// 使用 ServiceLoader 加载服务ServiceLoader<MyService> serviceLoader = ServiceLoader.load(MyService.class, contextClassLoader);for (MyService service : serviceLoader) {service.doSomething();}} }
在这个例子中,ServiceLoader.load 方法使用线程上下文类加载器来加载 MyService 接口的实现类。由于服务提供者配置文件和实现类可能不在默认的类加载路径下,使用线程上下文类加载器可以确保正确加载这些类
6.2.2自定义类加载器示例
在某些情况下,你可能需要自定义类加载器来加载特定位置的类。这时可以通过线程上下文类加载器来使用自定义类加载器
- 自定义类加载器
import java.io.File; import java.io.FileInputStream; import java.io.IOException;public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] classData = getClassData(name);if (classData != null) {return defineClass(name, classData, 0, classData.length);}} catch (IOException e) {e.printStackTrace();}return super.findClass(name);}private byte[] getClassData(String className) throws IOException {String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";try (FileInputStream fis = new FileInputStream(path)) {byte[] buffer = new byte[fis.available()];fis.read(buffer);return buffer;}} }
- 使用自定义类加载器作为线程上下文类加载器
public class CustomClassLoaderDemo {public static void main(String[] args) throws Exception {// 创建自定义类加载器CustomClassLoader customClassLoader = new CustomClassLoader("/path/to/classes");// 设置当前线程的上下文类加载器为自定义类加载器Thread.currentThread().setContextClassLoader(customClassLoader);// 使用线程上下文类加载器加载类Class<?> clazz = customClassLoader.loadClass("com.example.MyClass");Object instance = clazz.getDeclaredConstructor().newInstance();// 假设 MyClass 有一个 doSomething 方法clazz.getMethod("doSomething").invoke(instance);} }
在这个例子中,我们创建了一个自定义类加载器 CustomClassLoader,并将其设置为当前线程的上下文类加载器。然后使用该类加载器加载指定的类并创建实例,调用其方法
(3)类的同一性判断
- 要判断两个类是否为同一个类,需要满足两个条件:(1)它们的全限定名(类名 + 包名)必须相同;(2)加载它们的类加载器实例必须相同
- 在双亲委派模型下,像 java.lang.String 这类 Java 基础类库中的类,会由引导类加载器负责加载。即使你自定义了一个 java.lang.String 类,当加载请求传递到引导类加载器时,它会优先加载 Java 基础类库中的 java.lang.String 类,自定义的 java.lang.String 类通常不会被加载,这保障了核心类库的安全性和唯一性
- 不过,通过线程上下文类加载器,确实可以打破双亲委派机制的限制。你可以指定应用程序类加载器或者自定义类加载器来加载自定义的 java.lang.String 类。这样一来,JVM 中可能会存在全限定名相同但由不同类加载器加载的两个类。由于类加载器不同,这两个类在 JVM 中会被视为不同的类
- 在 JVM 里,类的唯一性是由全限定名和加载它的类加载器共同确定的。只有当全限定名和类加载器都相同时,JVM 才会认为这两个类是同一个类。例如,在使用反射创建对象或进行类型比较时,这一规则就会发挥作用。如果两个类的全限定名相同但类加载器不同,对它们进行类型比较时会得到 false 的结果
(4)自定义类加载器
在 Java 中,自定义类加载器一般通过继承java.lang.ClassLoader类来实现,在 JDK 1.2 之后,通常推荐重写findClass方法而非loadClass方法,这样能避免破坏双亲委派机制。如果需求不复杂,还可以继承URLClassLoader类来简化编写。下面详细介绍这两种方式:
6.4.1继承ClassLoader类并重写findClass方法
// TestClass.java
public class TestClass {public void sayHello() {System.out.println("Hello from TestClass!");}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;// 自定义类加载器,继承自 ClassLoader
public class CustomClassLoader extends ClassLoader {// 类文件所在的路径private String classPath;// 构造函数,接收类文件所在路径public CustomClassLoader(String classPath) {this.classPath = classPath;}// 重写 findClass 方法,用于查找并加载类@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 从指定路径加载类的字节数据byte[] classData = loadClassData(name);if (classData == null) {// 如果未找到类数据,抛出类未找到异常throw new ClassNotFoundException();}// 使用 defineClass 方法将字节数据转换为 Class 对象return defineClass(name, classData, 0, classData.length);}// 从文件系统中读取类的字节数据private byte[] loadClassData(String name) {// 构建类文件的完整路径String filePath = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";try (FileInputStream fis = new FileInputStream(filePath)) {// 创建字节数组,大小为文件的长度byte[] buffer = new byte[fis.available()];// 从文件输入流中读取数据到字节数组fis.read(buffer);return buffer;} catch (IOException e) {// 处理读取文件时的异常e.printStackTrace();return null;}}
}
public class CustomClassLoaderTest {public static void main(String[] args) throws Exception {// 创建自定义类加载器实例,指定类文件所在路径CustomClassLoader customClassLoader = new CustomClassLoader("/path/to/classes");// 使用自定义类加载器加载 TestClassClass<?> clazz = customClassLoader.loadClass("TestClass");// 创建 TestClass 的实例Object instance = clazz.getDeclaredConstructor().newInstance();// 调用 TestClass 的 sayHello 方法clazz.getMethod("sayHello").invoke(instance);}
}
6.4.2继承URLClassLoader类
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;public class SimpleCustomClassLoader extends URLClassLoader {public SimpleCustomClassLoader(URL[] urls) {super(urls);}// 可以根据需要重写其他方法,若只是简单加载类,继承URLClassLoader即可满足基本需求
}
代码说明:SimpleCustomClassLoader继承了URLClassLoader类,通过构造函数传入包含类文件路径的URL数组。URLClassLoader已经实现了基本的类加载逻辑,包括从指定的URL中查找和加载类。如果只是需要从特定的URL路径加载类,继承URLClassLoader并传入正确的URL数组就可以满足需求,无需重写太多方法,简化了自定义类加载器的编写过程
(5)自定义类加载器实现热部署
- 自定义类加载器
- 首先,需要创建一个自定义类加载器,通常继承自 ClassLoader 类。自定义类加载器的主要作用是负责从指定的位置(如文件系统、网络等)加载类的字节码文件,并将其转换为 Class 对象
- 在自定义类加载器中,一般需要重写 findClass 方法,该方法用于查找并加载类的字节码。当调用 loadClass 方法时,会根据双亲委派机制先尝试让父类加载器加载类,如果父类加载器无法加载,才会调用自定义类加载器的 findClass 方法
import java.io.File; import java.io.FileInputStream; import java.io.IOException;public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String name) {String filePath = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";try (FileInputStream fis = new FileInputStream(filePath)) {byte[] buffer = new byte[fis.available()];fis.read(buffer);return buffer;} catch (IOException e) {e.printStackTrace();return null;}} }
当监测到 MyServiceImpl 类文件更新后,使用新的 CustomClassLoader 实例来加载新的 MyServiceImpl 类:
// 假设类文件在这个路径 String classPath = "/path/to/classes"; CustomClassLoader newClassLoader = new CustomClassLoader(classPath); Class<?> newClass = newClassLoader.loadClass("com.example.MyServiceImpl");
- 监测类文件的变化
- 为了实现热部署,需要实时监测类文件的变化。可以通过定时任务或者文件系统的监听机制来实现
- 定时任务:每隔一段时间(如几秒)检查类文件的修改时间,如果发现修改时间有变化,说明类文件已经被更新
- 文件系统监听:利用操作系统提供的文件系统监听功能,当类文件发生变化时,会触发相应的事件,通知程序进行处理
- 加载新的类文件
- 当监测到类文件发生变化时,需要使用自定义类加载器加载新的类文件。由于 Java 的类加载机制中,同一个类加载器只能加载同一个类一次,所以为了加载新的类定义,需要创建一个新的自定义类加载器实例
- 新的类加载器会重新从文件系统中读取类的字节码文件,并通过 defineClass 方法将字节码转换为 Class 对象
- 替换旧的类实例
- 加载新的类文件后,需要将旧的类实例替换为新的类实例。这可以通过反射机制来实现
- 首先,使用新的类加载器加载新的类,然后通过反射创建新的类实例
Object newInstance = newClass.getDeclaredConstructor().newInstance();
- 接着,将新的类实例替换掉旧的类实例,这样在后续的程序执行中,就会使用新的类定义(假设在某个服务管理类中持有 MyService 实例:)
public class ServiceManager {private static MyService service;public static void setService(MyService newService) {service = newService;}public static MyService getService() {return service;} }
在创建新实例后,把新实例设置到 ServiceManager 中:
// 假设 MyService 是接口,MyServiceImpl 实现了该接口 MyService newServiceInstance = (MyService) newInstance; ServiceManager.setService(newServiceInstance);
经过上述步骤,后续程序中调用 ServiceManager.getService() 获取到的就是新的 MyServiceImpl 实例,会使用新的类定义。例如:
MyService service = ServiceManager.getService(); service.doSomething(); // 调用新类的方法
- 注意事项
- 类加载器的隔离:为了避免类加载冲突,每个新的类加载器应该有自己独立的命名空间。不同版本的类由不同的类加载器加载,它们在 JVM 中被视为不同的类
- 资源管理:在替换类实例时,需要确保旧的类实例被正确地释放,避免内存泄漏
- 线程安全:热部署过程中可能会涉及到多线程环境,需要确保线程安全,避免出现并发问题
(6)类加载器引用
- 类加载器引用的保存位置:当一个类被类加载器加载到 JVM 中时,JVM 会在方法区为这个类创建一个 Class 对象,这个 Class 对象会持有加载它的类加载器的引用。引导类加载器是用本地代码实现的,它没有对应的 Java 对象,所以不存在保存引用到方法区的情况;扩展类加载器、应用程序类加载器和自定义类加载器在加载类时,对应的类的 Class 对象会记录加载它的类加载器
- 通过类加载器区分全类名相同的类:在 Java 中,类的唯一性由全限定名(包名 + 类名)和加载它的类加载器共同确定。即使两个类的全限定名相同,但如果它们是由不同的类加载器加载的,那么在 JVM 中它们就是不同的类。例如,当有两个全类名相同的类来自不同的文件路径时,可以使用自定义类加载器分别加载它们,加载后的这两个类在 JVM 中是不同的,不能相互替代
- 类型转换与类加载器的关系:只有当两个类是由同一个类加载器加载时,才会进一步判断它们是否具有父子关系以确定是否可以进行类型转换。例如,将一个由类加载器 A 加载的 MyClass 对象尝试转换为另一个由类加载器 B 加载的 MyClass 对象,会抛出 ClassCastException 异常
(7)类的五种主动使用方式
- 实例化对象时:当使用new关键字创建一个类的实例,比如MyClass myObject = new MyClass(); ,如果MyClass还没有被加载到 JVM 中,就会触发类加载过程,将类的字节码文件加载到内存,并生成对应的Class对象
- 访问类的静态成员时:包括访问类的静态变量,如int value = MyClass.staticVariable; ,或者调用类的静态方法,如MyClass.staticMethod(); 。如果此时类尚未被加载,就会触发类加载。但如果访问的是被final修饰且在编译期已确定值(如public static final int NUM = 10; )的静态常量,不会触发类加载,因为这些常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类。不过,如果静态常量的值是通过方法调用等动态方式确定的(如public static final int VALUE = getValue(); ,其中getValue()是方法),则会触发类加载
- 使用反射机制时:当通过反射访问类,例如Class<?> clazz = Class.forName("com.example.MyClass"); 或者使用反射创建实例、调用方法等操作时,如果类还没有被加载,就会触发类加载
- 初始化子类时:当初始化一个子类时,如果其父类还没有被加载和初始化,那么会先触发父类的加载和初始化
- JVM 启动时:虚拟机启动时,会加载一组特定的类,这些类是 JVM 启动过程中所必需的,比如包含main方法的主类。JVM 会首先加载并初始化这个主类,作为程序执行的起点
相关文章:
JVM:类加载子系统
一、类加载子系统概述 类加载子系统由多个类加载器组成,它们负责从文件系统或者网络中读取二进制形式的字节码(.class)文件,并将其加载进 JVM。字节码文件中关于类的定义、类中属性的定义、类中方法的定义以及类中方法的字节码等…...
独家!美团2025校招大数据题库
推荐阅读文章列表 2025最新大数据开发面试笔记V6.0——试读 我的大数据学习之路 面试聊数仓第一季 题库目录 Java 1.写一个多线程代码 2.写一个单例代码 3.LinkedBlockingQueue原理 4.模板设计模式 5.如何设计一个 生产者-消费者队列 6.堆内存和栈内存 7.ThreadLo…...
Angular 框架详解:从入门到进阶
Hi,我是布兰妮甜 !在当今快速发展的 Web 开发领域,Angular 作为 Google 主导的企业级前端框架,以其完整的解决方案、强大的类型系统和丰富的生态系统,成为构建大型复杂应用的首选。不同于其他渐进式框架,An…...
使用Vue 3与.NET 8.0通过SignalR实现实时通信,并结合JWT身份验证
实时通信是一个非常重要的功能。SignalR是一个强大的库,能够帮助我们轻松实现客户端和服务器之间的实时数据传输。本文将结合你的代码示例,向你展示如何使用Vue 3作为前端框架,ASP.NET Core作为后端框架,通过SignalR实现实时消息通…...
Harmonyos-Navigation路由跳转
Harmonyos-Navigation路由跳转 概述Navigation路由跳转模块内页面路由系统路由表测试页代码创建并配置路由表文件配置创建好的路由表文件跳转页面 自定义路由表 跨模块路由封装库模块路由跳转工具类 概述 Navigation是路由容器组件,一般作为首页的根容器࿰…...
《人工智能应用创新》5天出审稿意见!
期刊简介 《人工智能应用创新(Innovative Applications of AI)》 (ISSN:3078-2147)是由香港修墨信息工程研究院举办,经国际同行评审后收录的学术期刊。本刊共分三个栏目:综述分析、应用示范、前…...
Excel数据自动填充到Word自定义表格
上一份工作在一家国企做软件测试,需求变来变去(3天一小改,5天换版面),xmind要先整理一遍测试用例(版本迭代,该废的废,该加的加),完了细节在禅道里补充&#x…...
Spring Boot一次接口请求涉及的完整执行链路
Spring Boot一次接口请求涉及的完整执行链路 🔁 Spring 项目请求执行链路(简化视图) 客户端请求(浏览器、Postman)↓ Tomcat(Servlet 容器)↓ 【Listener 监听器】↓ 【Filter 过滤器】&#x…...
mapbox基础,加载视频到地图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️raster 栅格图层 api二、🍀加载视频到…...
Android动态化技术优化
Android动态化技术优化 一、WebView优化基础 1.1 WebView性能瓶颈 初始化耗时内存占用高页面加载慢白屏问题 1.2 WebView基本配置 class OptimizedWebView : WebView {init {// 开启硬件加速setLayerType(LAYER_TYPE_HARDWARE, null)// 配置WebSettingssettings.apply {//…...
Spring Boot 自定义定时任务组件深度解析:Quartz 集成与设计模式实战
一、组件设计目标 解决痛点: 简化 Quartz 原生 API 的复杂性统一任务调度管理(增删改查、日志、重试)与 Spring Boot 生态无缝整合 二、实现步骤详解 1. 组件初始化配置 1.1 初始化 Quartz 表结构 下载 SQL 脚本 🔗 官方表…...
Java Bean演进历程:从POJO到Spring Boot配置绑定
一、早期阶段:手动编写Java Bean 基本结构 私有属性:所有字段均为private,保证封装性。 公共构造方法:提供无参构造(JavaBean规范)或有参构造(POJO常见)。 Setter/Getter方法&…...
信息科技伦理与道德0:课程安排
1 课程安排 分组讨论的议题如下: 1.1 生成对抗网络(GAN) (1)GAN生成伪造人脸与身份冒用风险 算法原理: GAN通过生成器(Generator)和判别器(Discriminator)…...
STM32F103C8T6-基于FreeRTOS系统实现步进电机控制
引言 上一篇文章讲述了如何使用蓝牙连接stm32进行数据收发控制步进电机,这篇在之前的基础上通过移植操作系统(FreeRTOS或者其他的也可以,原理操作都类似)实现步进电机控制。 上篇博客指路:STM32蓝牙连接Android实现云…...
数字资产和交易解决方案
数字资产和交易解决方案 一、背景 (一)数字经济的蓬勃发展 随着信息技术的飞速发展,数字经济已成为全球经济增长的新引擎。数字资产作为数字经济的重要组成部分,其价值逐渐被人们所认识和重视。数字资产包括但不限于数字货币、…...
计算机网络 实验四 静态路由的配置与应用
一、实验目的 熟悉路由器的工作原理;熟悉静态路由的原理;熟悉华为网络模拟器的使用方法;掌握网络拓扑图的绘制;掌握路由器的配置。 二、实验设备 PC、华为模拟器ENSP。 三、实验步骤 知识准备:路由器和静态路由的…...
二进制求和 - 简单
************* C topic: 67. 二进制求和 - 力扣(LeetCode) ************* Give the topic an inspection. Too many works these days. And no spare time for code learning. However here I am gagin. This topic is an easy one and I want to pra…...
【C++】 —— 笔试刷题day_18
一、压缩字符串(一) 题目解析 题目给定一个字符str,让我们将这个字符串进行压缩; **压缩规则:**出现多次的字符压缩成字符数字;例如aaa压缩成a3。如果字符值出现一次,1不用写。 算法思路 这道题总的来说就非常简单了…...
LeetCode 热题 100_最长递增子序列(87_300_中等_C++)(动态规划)
LeetCode 热题 100_最长递增子序列(87_300) 题目描述:输入输出样例:题解:解题思路:思路一(动态规划): 代码实现代码实现(思路一(动态规划…...
asp-for等常用的HTML辅助标记?
在ASP.NET Core Razor Pages 和 MVC 中,除了asp-for之外,还有许多常用的 HTML 辅助标记,下面为你详细介绍: 表单与路由相关 asp-action 和 asp-controller 用途:这两个标记用于生成表单或链接的 URL,指定…...
map用法介绍
在 C 里,map是标准库提供的一种关联容器,它以键 - 值对的形式存储元素,并且按键的升序排列。下面为你展示如何在 C 用map。 如果没有用万能头的时候,需要加入#include 用法介绍: 映射[需要注意map的映射是1对1的不能出…...
AIGC-十款知识付费类智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)
Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…...
一页概览:桌面虚拟化方案
2010年左右手绘的,用的是公司的信纸,马克笔。当时在买VMware和Citrix的桌面虚拟化方案,以及Wyse的瘦客户端。...
通过导入 Excel 的方式复制文件或文件夹
在进行文件或文件夹的批量整理时,许多人都会遇到需要将大量文件或文件夹复制到另一个文件夹中的问题。传统的手动复制粘贴方法不仅繁琐,而且效率低下。今天给大家介绍一种方法,可以实现将多个不同文件夹中的文件复制到一个或者多个文件夹&…...
Python单例设计模式深度解析
目录 一、什么是单例设计模式 核心特点 二、为什么需要单例模式 典型应用场景 优势对比 三、Python实现单例的三种方式 1. 使用__new__方法(经典实现) 2. 使用装饰器实现 3. 使用模块实现(Python特有) 四、深入理解__new…...
WPF 图标原地旋转
如何使元素原地旋转 - WPF .NET Framework | Microsoft Learn <ButtonRenderTransformOrigin"0.5,0.5"HorizontalAlignment"Left">Hello,World<Button.RenderTransform><RotateTransform x:Name"MyAnimatedTransform" Angle"…...
深入解析Java日志框架Logback:从原理到最佳实践
Logback作为Java领域最主流的日志框架之一,由Log4j创始人Ceki Glc设计开发,凭借其卓越的性能、灵活的配置以及与SLF4J的无缝集成,成为企业级应用开发的首选日志组件。本文将从架构设计、核心机制、配置优化等维度全面剖析Logback的技术细节。…...
【设计模式——装饰器模式】
在 Unity 游戏开发中,装饰模式是一种非常灵活的设计模式,用于在运行时动态地为对象添加功能。以下是装饰模式的设计思路和实现步骤,以角色的装备系统为例进行说明。 设计思路 装饰模式的核心思想是通过创建一个装饰器类来包装原有的对象&am…...
在 macOS 上切换默认 Java 版本
下载javasdk 打开android studio -> setting -> build.execution,dep -> build tools -> gradle -> Gradle JDK -> download JDK… 点击下载,就下载到了 ~/Library/Java/JavaVirtualMachines/ 安装 jenv brew install jenv将 jenv 集成到 Shell …...
【Linux网络与网络编程】11.数据链路层mac帧协议ARP协议
前面在介绍网络层时我们提出来过一个问题:主机是怎么把数据交给路由器的?那里我们说这是由数据链路层来做的。 网络上的报文在物理结构上是以mac帧的形式流动的,但在逻辑上是以IP流动的,IP的流动是需要mac帧支持的。 数据链路层解…...
158页PPT | 某大型研发制造集团信息化IT规划整体方案
该文档是某大型研发制造集团信息化IT规划整体方案,涵盖项目过程回顾、信息平台分析、现状评估、规划及治理建议和下阶段工作计划。项目旨在理解集团战略目标,评估信息化应用现状,制定可扩展的蓝图,明确未来3年管理与IT建设子项目&…...
ON DUPLICATE KEY UPDATE 更底层解释它的优势
从更底层来看,ON DUPLICATE KEY UPDATE 的优势主要源于以下几个方面: 1. 减少网络往返次数 先查询再更新:这种方式需要客户端和数据库服务器之间进行多次网络通信。首先,客户端发送一个 SELECT 查询请求,然后等待服务…...
Python 赋能区块链金融——从零构建智能交易系统
Python 赋能区块链金融——从零构建智能交易系统 引言:区块链金融系统的崛起 区块链技术正在颠覆传统金融体系,带来去中心化、透明化和高效的交易模式。从 DeFi(去中心化金融)到 NFT 市场,区块链金融系统已成为 Web 3.0 生态的重要支柱。如何用 Python 构建一个区块链金…...
基础(测试用例设计方法:流程图法,等价类划分法,边界值分析法,判定表法,正交分析法,错误推测法,其他方法,案例)
目录 流程图法(场景法) 业务流程 流程图 流程图法设计测试用例 案例-退款泳道图 案例-刷视频流程 等价类划分法 等价类 等价类设计测试用例 案例1-验证电话号码 案例2-验证邮箱格式 边界值分析法 测试数据的选取 边界值法设计测试用例 案例…...
QT —— 信号和槽(槽函数)
QT —— 信号和槽 信号和槽信号(Signal)槽(Slot)声明方式工作原理连接方式1. 传统连接方式(Qt4风格)2. 新式连接方式(Qt5风格) 区分槽函数和信号通过QtCreator生成信号槽代码自动生成槽函数显式连接的优势命名约定自动连接的局限性最佳实践建议结论 我们之前对QT,有…...
ROS2模块库概览
一、核心通信与基础库(最常用) 客户端库 rclcpp (ROS Client Library for C) 核心API:create_node(), create_publisher(), create_subscription()高级特性: 生命周期节点:通过rclcpp_lifecycle实现configure/activate…...
HADOOP——序列化
1.创建一个data目录在主目录下,并且在data目录下新建log.txt文件 2.新建flow软件包,在example软件包下 FlowBean package com.example.flow;import org.apache.hadoop.io.Writable;import java.io.DataInput; import java.io.DataOutput; import java.i…...
第五章 5.2ESP32物联网应用:HTTP与Web服务器详细教学
本文将详细讲解如何在ESP32上搭建Web服务器,通过HTTP协议实现远程控制LED灯。每行代码均有详细注释,适合零基础学习。 一、HTTP协议基础 HTTP是客户端(浏览器)和服务器之间的通信协议,常用请求方法: GET&a…...
c++11 绑定器bind
文章目录 std::bind 使用总结(C11)1. 绑定普通函数2. 使用占位符 _1, _2,调用时传参数3. 绑定类的成员函数(类外)4. 绑定类的成员函数(类内)5. 占位符结合成员函数小结 std::bind 使用总结&…...
实现时间最优轨迹生成/轨迹规划方法(TOTG),不使用moveit,可用于ROS驱动机械臂FollowJointTrajectoryGoal()
前言 在我的这篇文章:https://blog.csdn.net/weixin_45702459/article/details/139293391?spm1011.2415.3001.5331中,写了不使用moveit来ros驱动机械臂的方法,也就是用FollowJointTrajectoryGoal()来进行一系列点的关节运动,其实…...
2025年推荐使用的开源大语言模型top20:核心特性、选择指标和开源优势
李升伟 编译 随着人工智能技术的持续发展,开源大型语言模型(LLMs)正变得愈发强大,使最先进的AI能力得以普及。到2025年,开源生态系统中涌现出多个关键模型,它们在各类应用场景中展现出独特优势。 大型语言…...
高并发多级缓存架构实现思路
目录 1.整体架构 3.安装环境 1.1 使用docket安装redis 1.2 配置redis缓存链接: 1.3 使用redisTemplate实现 1.4 缓存注解优化 1.4.1 常用缓存注解简绍 1.4.2 EnableCaching注解的使用 1.4.3使用Cacheable 1.4.4CachePut注解的使用 1.4.5 优化 2.安装Ngin…...
Qt 的 事件队列
Qt 的 事件队列 是其核心事件处理机制之一,用于管理和分发系统与用户生成的事件(如鼠标点击、键盘输入、定时器、信号槽中的队列连接等)。理解 Qt 的事件队列对多线程、界面响应以及异步处理尤为关键。 一、Qt 的事件处理模型概览 Qt 是基于…...
html-css样式
1. 所有类型为文本的 元素的样式 指定所有类型为文本的 元素的样式 /* 文本框的样式 */ input[type"text"] { font-size: 25px;width: 80px; /* 文本框的宽度 */ padding: 25px; } font-size:字体大小 width:文本框宽度 padding&#…...
Qemu-STM32(十五):STM32F103加入Flash控制器
概述 本文主要描述了在Qemu平台中,如何添加STM32F103的Flash控制器模拟代码。 参考资料 STM32F1XX TRM手册,手册编号:RM0008 添加步骤 1、在hw/arm/Kconfig文件中添加STM32F1XX_FLASH,如下所示: 号部分为新增加内容 diff -…...
设计模式(责任链模式)
责任链模式 模板模式、策略模式和责任链模式,这三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源…...
【Mac-ML-DL】深度学习使用MPS出现内存泄露(leaked semaphore)以及张量转换错误
MPS加速修改总结 先说设备:MacBook Pro M4 24GB 事情的起因是我在进行深度学习的时候想尝试用苹果自带的MPS进行训练加速,修改设备后准备开始训练,但是出现如下报错: UserWarning: resource_tracker: There appear to be 1 leak…...
Hadoop集群部署教程-P5
Hadoop集群部署教程-P5 Hadoop集群部署教程(续) 第十七章:安全增强配置 17.1 认证与授权 Kerberos认证集成: # 生成keytab文件 kadmin -q "addprinc -randkey hdfs/masterEXAMPLE.COM" kadmin -q "xst -k hdfs.…...
Github 2FA(Two-Factor Authentication/两因素认证)
Github 2FA认证 多因素用户认证(Multi-Factor Authentication),基本上各个大互联网平台,尤其是云平台厂商(如:阿里云的MFA、华为云、腾讯云/QQ安全中心等)都有启用了,Github算是搞得比较晚些了。 双因素身…...
Spark大数据分析与实战笔记(第四章 Spark SQL结构化数据文件处理-05)
文章目录 每日一句正能量第4章 Spark SQL结构化数据文件处理章节概要4.5 Spark SQL操作数据源4.5.1 Spark SQL操作MySQL4.5.2 操作Hive数据集 每日一句正能量 努力学习,勤奋工作,让青春更加光彩。 第4章 Spark SQL结构化数据文件处理 章节概要 在很多情…...