深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)

深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)

📌 引言

Spring 能靠一个 @Autowired 就把对象塞给你,MyBatis 能把数据库结果集自动映射成实体类,JUnit 能找到你写的 @Test 方法并调用它------这些框架背后都站着同一个东西:反射(Reflection)。

但反射也是最容易被误用的技术。有人面试能背出"反射就是运行期获取类的信息",却说不清 getMethod 和 getDeclaredMethod 的区别;有人图省事在循环里写 Class.forName(),把接口 RT 从 10ms 拖到 800ms;还有人不知道 JDK 9 之后 setAccessible(true) 对 JDK 内部类已经不管用了。

本文从 Class 对象讲起,把反射的 API 全貌、真实使用场景、实测性能数据、四条优化路径、以及五个必须知道的坑一次讲透,最后整理面试高频考点。


一、反射的本质:运行期的"自省"

1.1 编译期 vs 运行期

普通 Java 代码是静态的:调哪个方法、访问哪个字段,编译期就写死在字节码里了。

java 复制代码
User user = new User();
user.setName("张三");   // 编译期就知道要调 setName

反射是动态的:程序运行时才知道要操作哪个类的哪个成员。

java 复制代码
Class<?> clazz = Class.forName("com.example.User");
Object user = clazz.getDeclaredConstructor().newInstance();
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "张三");   // 运行期才确定调用目标

💡 一句话理解:反射让程序在运行时能够"看见"并"操作"自己的结构。编译期不可知的类名、方法名,都可以用字符串在运行时动态解析。

1.2 Class 对象:一切反射的入口

JVM 加载一个类时,会在方法区(JDK 8+ 是元空间)为它创建一个 java.lang.Class 对象。这个对象是反射的唯一入口 ------反射的几十个 API 全都挂在 Class 上。

Class 对象里存的不是你的业务数据,而是这个类的元信息:

  • 类名、包名、父类、实现的接口

  • 所有字段(Field)、方法(Method)、构造器(Constructor)

  • 注解、泛型签名、修饰符

    复制代码
        ┌──────────────────────────────┐
        │   Class<User>  (元空间)       │
        ├──────────────────────────────┤
        │  name       = "com.example.User"
        │  superclass = Object          │
        │  fields     = [name, age]     │──► Field 对象
        │  methods    = [setName, ...]  │──► Method 对象
        │  constructors = [User()]      │──► Constructor 对象
        │  annotations  = [@Entity]     │
        └──────────────────────────────┘

1.3 获取 Class 的四种方式(面试必问区别)

方式 代码 是否触发类初始化(执行 static 块)
类字面量 User.class 不会
对象获取 user.getClass() 对象已存在,类必然已初始化
Class.forName Class.forName("com.example.User") 会 (默认 initialize=true)
类加载器 loader.loadClass("com.example.User") 不会(只加载不初始化)
java 复制代码
public class ClassInitDemo {
    static class Foo {
        static { System.out.println("Foo 的 static 块执行了"); }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("--- Foo.class ---");
        Class<?> c1 = Foo.class;                 // 不打印

        System.out.println("--- Class.forName ---");
        Class<?> c2 = Class.forName("ClassInitDemo$Foo");  // 打印

        System.out.println("--- ClassLoader.loadClass ---");
        Class<?> c3 = ClassInitDemo.class.getClassLoader()
                .loadClass("ClassInitDemo$Foo");            // 不打印
    }
}

输出:

复制代码
--- Foo.class ---
--- Class.forName ---
Foo 的 static 块执行了
--- ClassLoader.loadClass ---

⚠️ 这个区别很实用 :如果只想读注解(比如扫描组件),用 Class.forName(name, false, loader) 或 loadClass 避免触发静态初始化,能避免很多"类初始化时序"的诡异 Bug。JDBC 驱动注册就是经典案例------所以 Class.forName("com.mysql.cj.jdbc.Driver") 必须用默认的 initialize=true,否则 static 块里的 DriverManager.registerDriver() 不会执行。


二、核心 API 全景

2.1 Constructor:创建实例

java 复制代码
Class<?> clazz = User.class;

// 无参构造
Constructor<?> noArg = clazz.getConstructor();

// 指定参数类型的构造(public)
Constructor<?> withArgs = clazz.getConstructor(String.class, int.class);

// 包括 private 构造(getDeclared* 才能拿到非 public 的)
Constructor<?> priv = clazz.getDeclaredConstructor(String.class);
priv.setAccessible(true);          // 取消访问检查,否则 private 构造会抛 IllegalAccessException

Object u1 = noArg.newInstance();
Object u2 = withArgs.newInstance("张三", 18);
Object u3 = priv.newInstance("李四");

newInstance() 和 Class.newInstance() 的区别(重要):

Class.newInstance()(已废弃) Constructor.newInstance()(推荐)
异常包装 直接抛受检异常(绕过编译器检查) 统一包装成 InvocationTargetException
访问权限 只能调 public 无参构造 配合 setAccessible 可调任意构造
推荐度 ❌ JDK 9 起 @Deprecated ✅ 官方推荐

2.2 Field:读写字段

java 复制代码
Class<?> clazz = User.class;
Object user = clazz.getDeclaredConstructor().newInstance();

// 拿单个字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);              // private 字段必须
nameField.set(user, "张三");
Object v = nameField.get(user);

// 遍历所有字段(含 private)
for (Field f : clazz.getDeclaredFields()) {
    System.out.println(f.getName() + " : " + f.getType().getSimpleName()
            + " 修饰符=" + Modifier.toString(f.getModifiers()));
}

// 遍历所有 public 字段(含父类继承来的)
for (Field f : clazz.getFields()) {
    System.out.println("public 字段: " + f.getName());
}

⚠️ getFields() vs getDeclaredFields() 是最常见的混淆点:

  • getFields() / getMethods() / getConstructors() → 只返回 public ,且包含父类继承的;
  • getDeclaredFields() / getDeclaredMethods() → 返回本类声明的所有访问级别 (private 也能拿到),但不包含父类的。

2.3 Method:调用方法

java 复制代码
Class<?> clazz = User.class;
Object user = clazz.getDeclaredConstructor().newInstance();

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "张三");

Method getName = clazz.getMethod("getName");
Object name = getName.invoke(user);

// 调用 private 方法
Method secret = clazz.getDeclaredMethod("secretCompute", int.class);
secret.setAccessible(true);
Object result = secret.invoke(user, 42);

三个必须知道的细节:

  1. 参数类型要精确匹配 :getMethod("setAge", int.class),不能写 Integer.class,也不能省略(无参必须完全不传)。
  2. invoke 的异常会被包装 :目标方法内抛出的异常会被包进 InvocationTargetException,必须 getTargetException() 取原始异常。
  3. invoke 返回 Object:基本类型会自动装箱,取值时要拆箱。
java 复制代码
try {
    method.invoke(target, args);
} catch (InvocationTargetException e) {
    // 真正的业务异常在这里
    Throwable real = e.getTargetException();
    throw new RuntimeException("调用 " + method.getName() + " 失败", real);
}

2.4 泛型与数组:反射能拿到的"被擦除的信息"

泛型在运行时被擦除,但类/字段/方法签名上的泛型信息保留在字节码里 ,可以通过 getGenericType() 拿到:

java 复制代码
public class GenericDemo {
    private List<String> names;
    private Map<String, Integer> scores;
}

Field f = GenericDemo.class.getDeclaredField("scores");
ParameterizedType pt = (ParameterizedType) f.getGenericType();
System.out.println(pt);                                  // java.util.Map<java.lang.String, java.lang.Integer>
System.out.println(Arrays.toString(pt.getActualTypeArguments()));
// [class java.lang.String, class java.lang.Integer]

这就是 MyBatis、Jackson 能反序列化泛型集合的原理。

数组的反射要用 java.lang.reflect.Array:

java 复制代码
Object arr = Array.newInstance(int.class, 5);   // 相当于 new int[5]
Array.set(arr, 0, 100);
int v = Array.getInt(arr, 0);
System.out.println(arr.getClass().getName());   // [I
System.out.println(Array.getLength(arr));       // 5

三、实战:反射能干什么

3.1 案例一:通用 Bean 拷贝(不用 BeanUtils)

java 复制代码
public class BeanCopier {

    /** 把 src 中同名同类型的字段拷贝到 dest */
    public static void copy(Object src, Object dest) {
        if (src == null || dest == null) return;
        Class<?> srcClass = src.getClass();
        Class<?> destClass = dest.getClass();

        for (Field srcField : srcClass.getDeclaredFields()) {
            if (Modifier.isStatic(srcField.getModifiers())) continue;   // 跳过 static

            Field destField;
            try {
                destField = destClass.getDeclaredField(srcField.getName());
            } catch (NoSuchFieldException e) {
                continue;   // dest 没有这个字段,跳过
            }

            if (!destField.getType().equals(srcField.getType())) continue;  // 类型不兼容

            try {
                srcField.setAccessible(true);
                destField.setAccessible(true);
                destField.set(dest, srcField.get(src));
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("拷贝字段失败: " + srcField.getName(), e);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        User src = new User();
        src.setName("张三");
        src.setAge(18);

        UserVO vo = new UserVO();
        copy(src, vo);
        System.out.println(vo.getName() + ", " + vo.getAge());
    }
}

💡 生产建议 :这个玩具版本只用于理解原理。真实项目请用 MapStruct(编译期生成代码,零反射)或 Spring BeanUtils(已做 Method 缓存)。自己写反射拷贝,性能会比 MapStruct 差一到两个数量级。

3.2 案例二:迷你 IOC 容器(注解 + 反射)

这是 Spring 的核心思路,用 50 行代码就能复刻:

java 复制代码
/** 自定义注入注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {}

/** 被管理的服务 */
public class UserRepository {}

public class UserService {
    @Inject
    private UserRepository userRepository;

    public void doWork() {
        System.out.println("注入结果: " + (userRepository != null ? "成功" : "失败"));
    }
}

/** 迷你容器 */
public class MiniContainer {
    private final Map<Class<?>, Object> beans = new HashMap<>();

    /** 注册:反射创建实例 */
    public void register(Class<?> clazz) throws Exception {
        Constructor<?> ctor = clazz.getDeclaredConstructor();
        ctor.setAccessible(true);
        beans.put(clazz, ctor.newInstance());
    }

    /** 注入:扫描 @Inject 字段,按类型从容器里取依赖塞进去 */
    public void inject() throws Exception {
        for (Object bean : beans.values()) {
            for (Field field : bean.getClass().getDeclaredFields()) {
                if (!field.isAnnotationPresent(Inject.class)) continue;

                Object dependency = beans.get(field.getType());
                if (dependency == null) {
                    throw new IllegalStateException("找不到依赖: " + field.getType().getName());
                }
                field.setAccessible(true);
                field.set(bean, dependency);
            }
        }
    }

    public <T> T get(Class<T> clazz) {
        return clazz.cast(beans.get(clazz));
    }

    public static void main(String[] args) throws Exception {
        MiniContainer container = new MiniContainer();
        container.register(UserRepository.class);
        container.register(UserService.class);
        container.inject();

        container.get(UserService.class).doWork();   // 注入结果: 成功
    }
}

Spring 的 @Autowired 本质上就是这三步:扫描注解 → 找到字段类型 → field.set(bean, dependency)。 差别只在于 Spring 处理了循环依赖、代理、多实现类的歧义、生命周期回调这些工程细节。

3.3 案例三:框架里的反射都在干什么

框架 反射用来做什么
Spring 扫描 @Component 创建 Bean;@Autowired 注入依赖;@Transactional 生成代理
MyBatis 把 ResultSet 的列按 setter 名映射到实体字段(getDeclaredMethod("set" + 列名))
Jackson 序列化时读字段/getter,反序列化时找构造器或 setter
JUnit 5 扫描 @Test 方法并调用;用 @BeforeEach 方法做前置
Tomcat 按 web.xml / 注解加载 Servlet 类并实例化

结论:凡是"约定优于配置"的框架,底层几乎都是反射 + 注解。


四、性能实测:反射到底慢多少

网上常见"反射慢 10 倍""慢 100 倍"的说法,但差异巨大取决于怎么用。我在本机 JDK 23 上做了实测(500 万次调用,预热充分):

java 复制代码
public class ReflectBench {
    public static class Target {
        public int add(int a, int b) { return a + b; }
    }

    static final int N = 5_000_000;

    public static void main(String[] args) throws Throwable {
        Target t = new Target();
        int sink = 0;
        long s, e;

        // 预热
        for (int i = 0; i < 3_000_000; i++) { sink += t.add(i, 1); }
        Method m = Target.class.getMethod("add", int.class, int.class);
        for (int i = 0; i < 500_000; i++) { sink += (int) m.invoke(t, i, 1); }

        // ① 直接调用
        s = System.nanoTime();
        int sum = 0;
        for (int i = 0; i < N; i++) { sum += t.add(i, 1); }
        e = System.nanoTime();
        System.out.printf("direct call             : %8.2f ns/op%n", (e - s) / (double) N);

        // ② 每次重新 getMethod(最坏情况)
        int M = N / 50;
        s = System.nanoTime();
        sum = 0;
        for (int i = 0; i < M; i++) {
            Method mm = Target.class.getMethod("add", int.class, int.class);
            sum += (int) mm.invoke(t, i, 1);
        }
        e = System.nanoTime();
        System.out.printf("getMethod + invoke      : %8.2f ns/op%n", (e - s) / (double) M);

        // ③ 缓存 Method 后调用
        s = System.nanoTime();
        sum = 0;
        for (int i = 0; i < N; i++) { sum += (int) m.invoke(t, i, 1); }
        e = System.nanoTime();
        System.out.printf("cached Method.invoke    : %8.2f ns/op%n", (e - s) / (double) N);

        // ④ MethodHandle
        MethodHandle mh = MethodHandles.lookup()
                .findVirtual(Target.class, "add",
                        MethodType.methodType(int.class, int.class, int.class));
        s = System.nanoTime();
        sum = 0;
        for (int i = 0; i < N; i++) { int v = (int) mh.invokeExact(t, i, 1); sum += v; }
        e = System.nanoTime();
        System.out.printf("MethodHandle.invokeExact: %8.2f ns/op%n", (e - s) / (double) N);
    }
}

实测结果:

复制代码
direct call             :     1.11 ns/op
getMethod + invoke      :   521.64 ns/op      ← 慢 470 倍
cached Method.invoke    :    19.62 ns/op      ← 慢 18 倍
MethodHandle.invokeExact:     4.34 ns/op      ← 慢 4 倍

4.1 数据怎么读

方式 ns/op 相对开销 结论
直接调用 1.11 1× 基准
每次 getMethod 521.64 470× ⚠️ 绝对不能出现在循环里
缓存 Method 19.62 18× 可用,但热路径要谨慎
MethodHandle 4.34 3.9× 高频场景的首选

三条结论:

  1. getMethod() 本身比 invoke() 贵得多 (找方法要在元数据里做字符串匹配)。循环里每次都 getMethod 是性能自杀------这是最常见的反射性能事故。
  2. 缓存 Method 后,开销降到 18 倍,绝大多数业务场景完全可接受。
  3. MethodHandle 只慢 4 倍,接近直接调用,适合框架底层。

4.2 为什么反射慢

开销来源 说明
访问检查 每次 invoke 都要校验调用方是否有权限(setAccessible(true) 可跳过)
参数数组 invoke(Object obj, Object... args) 每次都要新建 Object[],基本类型还要装箱
拆装箱 int → Integer → 目标方法的 int,两次转换
方法查找 getMethod 要做字符串匹配 + 遍历继承链
内联受阻 反射调用目标对 JIT 不透明,难以内联优化(这是最大的一笔账)

💡 关于 Method.invoke 的"inflation"机制 :JDK 并不是一直用 native 方式调用。前 15 次(sun.reflect.inflationThreshold)用 NativeMethodAccessorImpl,超过阈值后 JVM 会动态生成字节码 (GeneratedMethodAccessor)来加速。所以短时间压测会有"越来越快"的现象,这也是为什么基准测试必须充分预热。

4.3 四条优化路径

java 复制代码
// ① 缓存 Method ------ 收益最大、改动最小
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

Method method = METHOD_CACHE.computeIfAbsent(key,
        k -> clazz.getMethod(name, paramTypes));

// ② setAccessible(true) ------ 跳过访问检查,约省 20~30%
method.setAccessible(true);

// ③ MethodHandle ------ 高频调用场景,接近直接调用
MethodHandle mh = MethodHandles.lookup()
        .findVirtual(Target.class, "add", MethodType.methodType(int.class, int.class, int.class));

// ④ 终极方案:LambdaMetafactory 生成函数式接口(零反射开销)
//    这也是 Spring 6 / MyBatis 3.5+ / Jackson 的高性能路径

💡 最省事的方案其实是不用反射 :DTO 转换用 MapStruct,对象拷贝用编译期生成的代码。反射适合"启动期一次性工作"(扫描、注册、注入),不适合"运行期每次请求"。


五、五个必须知道的坑

5.1 setAccessible(true) 在 JDK 9+ 的模块系统下可能失效

JDK 9 引入模块系统后,对 JDK 内部类 (java.lang、java.util 等)的深度反射会被拒绝:

复制代码
java.lang.reflect.InaccessibleObjectException:
    Unable to make field private final byte[] java.lang.String.value accessible:
    module java.base does not "opens java.lang" to unnamed module

解决办法:启动参数加 --add-opens

bash 复制代码
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar

⚠️ 自己写的类(应用类路径上的类)不受影响,setAccessible(true) 依然有效。受限制的只有 JDK 自己的模块。

5.2 异常被包装成 InvocationTargetException

java 复制代码
try {
    method.invoke(target);
} catch (InvocationTargetException e) {
    Throwable real = e.getTargetException();   // 一定要取这个
    log.error("业务方法抛异常", real);
}

直接 catch (Exception e) 打印栈会看到一堆反射框架的调用栈,把真正的业务异常埋在中间------排查线上问题时这是噩梦。

5.3 反射能击穿单例、破坏封装

java 复制代码
// 反射击穿普通单例(私有构造 + 静态实例)
Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true);
Singleton another = ctor.newInstance();   // 拿到第二个实例!

防护方案 :用枚举实现单例 ------反射无法创建枚举实例(Constructor.newInstance 对枚举会直接抛 IllegalArgumentException),这是《Effective Java》推荐枚举单例的核心理由。

5.4 反射拿不到"运行时泛型"

java 复制代码
List<String> list = new ArrayList<>();
list.getClass() == ArrayList.class;        // true,泛型被擦除
// 拿不到 String 这个信息

只有在字段声明、方法签名、类继承 这些"写在字节码里"的位置才有泛型信息,getGenericType() 才能用。

5.5 反射让编译期检查失效

java 复制代码
Method m = clazz.getMethod("typoMethod");   // 编译通过,运行期才报错

建议 :反射调用的地方一定要包一层 try-catch 并打日志;能不用字符串写方法名就不用。


六、面试高频考点与标准答案

1. 反射是什么?有什么用?

答:反射是 Java 在运行期 动态获取类信息并操作类成员的能力。核心是 Class 对象。用途:框架的依赖注入、ORM 映射、JSON 序列化、注解处理、动态代理、单元测试框架。

2. 获取 Class 对象的四种方式,有什么区别?

答:User.class(不初始化)、user.getClass()(已初始化)、Class.forName("...")(会初始化 ,执行 static 块)、classLoader.loadClass("...")(只加载不初始化)。

3. getMethod 和 getDeclaredMethod 的区别?

答:getMethod 只返回 public 方法(含父类继承的);getDeclaredMethod 返回本类声明的所有访问级别 方法(private 也能拿),但不含父类 。Field、Constructor 同理。

4. 反射为什么慢?慢在哪?

答:① 每次 invoke 要做访问权限检查;② invoke 的参数是 Object[],有数组创建和基本类型装箱;③ getMethod 要做字符串匹配;④ 反射调用对 JIT 不透明,无法内联------这是最大开销。实测:直接调用 1.11ns,缓存 Method 后 19.62ns,每次重新 getMethod 达 521ns。

5. 怎么优化反射性能?

答:① 缓存 Method/Field/Constructor 对象 (收益最大);② setAccessible(true) 跳过访问检查;③ 高频路径用 MethodHandle(实测只慢 4 倍);④ 极致性能用 LambdaMetafactory 生成函数式接口;⑤ 根本上:不要在循环/热路径用反射。

6. Class.newInstance() 和 Constructor.newInstance() 有什么区别?

答:Class.newInstance() 已废弃,只能调 public 无参构造,且会直接抛受检异常(绕过编译器检查)。Constructor.newInstance() 支持任意构造(配合 setAccessible),异常统一包装成 InvocationTargetException。

7. 反射能修改 final 字段吗?

答:分情况,而且有个反直觉的坑:

  • 普通类的 final 实例字段 :可以改(setAccessible(true) + Field.set);
  • record 的 final 字段 :不行 ,会抛 IllegalAccessException: Can not set final int field ...(JDK 16 引入的 record 对此限制更严);
  • 改了也未必"生效" :直接访问该字段的代码可能已被常量折叠或 JIT 优化,读到的仍是旧值。我实测把 final int v = 1 改成 999 后,holder.v 仍返回 1,只有 field.get(holder) 返回 999。
java 复制代码
final class Holder { final int v = 1; }

Holder h = new Holder();
Field f = Holder.class.getDeclaredField("v");
f.setAccessible(true);
f.set(h, 999);

System.out.println(h.v);        // 1     ← 直接访问被优化,读到的还是旧值
System.out.println(f.get(h));   // 999   ← 反射读才是真实值

所以"反射能改 final"只对普通类的实例字段 成立,而且改完不一定能被普通代码看到 。这也是很多框架改用 Unsafe、或者干脆要求目标字段非 final 的原因。

8. JDK 9 之后反射有什么变化?

答:模块系统(JPMS)限制了对 JDK 内部类的深度反射 ,会抛 InaccessibleObjectException。解决方法是启动参数加 --add-opens java.base/java.lang=ALL-UNNAMED。自己的类不受影响。

9. 反射能拿到泛型信息吗?

答:运行时的对象上拿不到 (泛型被擦除),但字段声明、方法签名、类继承上的泛型 保留在字节码里,可以用 Field.getGenericType() 拿到 ParameterizedType 再取 getActualTypeArguments()。Jackson/MyBatis 的泛型反序列化就靠这个。

10. 枚举单例为什么比普通单例安全?

答:Constructor.newInstance() 对枚举类型会直接抛 IllegalArgumentException: Cannot reflectively create enum objects,所以反射无法创建第二个枚举实例 ;同时枚举的序列化由 JVM 保证反序列化后仍是同一个实例(不需要写 readResolve)。这两点让枚举成为最安全的单例实现。


七、总结

反射的定位一句话说清:

反射是"框架的地基",不是"业务的手段"。

把它记成四条:

  1. 入口是 Class :四种获取方式的关键差异在是否触发类初始化。
  2. API 分两套 :getXxx 只拿 public + 含继承;getDeclaredXxx 拿全部但不含继承。
  3. 性能差在用法 :缓存 Method 后只慢 18 倍(可接受);每次 getMethod 慢 470 倍(不可接受)。
  4. 边界要知道:JDK 9+ 模块限制、异常包装、泛型擦除、final 字段内联、封装被击穿。

实践建议:

  • 启动期做一次的事(扫描注解、注册 Bean、建立映射)→ 放心用反射;
  • 每次请求都要走的路(字段拷贝、参数绑定)→ 加缓存,或用 MethodHandle / MapStruct;
  • 循环里写 Class.forName 或 getMethod → 立刻改掉。

如果本文对你有帮助,欢迎点赞 + 收藏 + 关注!如果疑问或补充,欢迎在评论区交流~

关注我,后续分享更多 Java 核心知识点和实战技巧!

相关推荐
骑着蜗牛撵大象3271 小时前
2026 后端 AI 工程化:Spring AI 管调用、MCP 管工具、Agent 内嵌管自治
java·人工智能·后端·spring·ai·可观测性·mcp
tryxr1 小时前
JVM 垃圾回收机制
java·jvm·gc·垃圾回收
摸爬滚打的小李2 小时前
Spring XML + WAR + Tomcat 项目启动
后端
揣一尾游鱼yu2 小时前
Spring上(IOC)笔记
java·笔记·spring·ioc
荣码3 小时前
长上下文与大文档处理:200页PDF怎么喂给AI,我踩了4个坑
java·python
樱花树下的数学喵3 小时前
手写简易 STL string|C++ 字符串底层实现
开发语言·c++
IT_陈寒3 小时前
SpringBoot自动配置的坑,这次终于爬出来了
前端·人工智能·后端
极限挑战哥3 小时前
共享电单车联营平台分账架构实践:二清合规与自动分账零人工方案
java·大数据·人工智能·微信小程序·架构·微信公众平台
jimy13 小时前
C++ 报错的 qualifiers 的意思
开发语言·c++