Java反射获取@Encrypt注解失败主因是ClassLoader不一致,需确认注解RetentionPolicy.RUNTIME、避免混淆打包、比对类加载器;JDK17+需--add-opens开放模块;加密后保持字段类型、加前缀幂等控制、禁用setter自动加密。

Java 反射获取 @Encrypt 字段时 ClassLoader 不一致导致找不到注解
反射读不到自定义注解,大概率是注解类被不同 ClassLoader 加载了两次——比如你用 Spring Boot 打成 fat jar,而注解定义在某个被 spring-boot-loader 隔离的依赖里。此时 field.isAnnotationPresent(Encrypt.class) 永远返回 false,哪怕源码里明明写了。
实操建议:
- 确认
@Encrypt注解的@Retention(RetentionPolicy.RUNTIME)已声明,且没被 ProGuard 或 JVM 参数(如-XX:+UseSplitVerifier)意外剥离 - 检查注解类是否和目标字段类处于同一 classpath 路径;若注解在独立 module 中,确保它没被 shade、repackage 或多版本 JAR 冲突覆盖
- 调试时打印
Encrypt.class.getClassLoader()和targetObject.getClass().getClassLoader(),不相等就直接停在这一步——别往下写加密逻辑了
Field.setAccessible(true) 在 JDK 17+ 上被强限制,私有字段读写失败
JDK 17 默认启用强封装(--illegal-access=deny),setAccessible(true) 对非模块化代码会抛 InaccessibleObjectException,不是 SecurityException,老办法 catch 住没用。
实操建议:
- 启动参数加
--add-opens java.base/java.lang=ALL-UNNAMED(如果操作的是String等基础类型字段)或对应包名,例如--add-opens your.module/your.package=ALL-UNNAMED - 避免反射修改 final 字段:JDK 12+ 对
final字段的set()会静默失败或抛异常,即使setAccessible(true)成功 - 优先用
MethodHandles.privateLookupIn()替代传统反射(JDK 9+),它更安全且绕过部分模块检查,但要求目标类必须可访问
加密逻辑嵌入反射调用后,JSON 序列化出现循环引用或空值跳过
你用反射把字段值加密后塞回对象,但后续用 ObjectMapper.writeValueAsString() 输出 JSON 时,发现加密字段没出现,或者整个对象序列化卡死——常见原因是加密后字段类型变了(比如 String → byte[]),而 Jackson 默认不序列化非标准类型,或加密过程意外触发了 getter/setter 的副作用。
实操建议:
- 加密后保持字段原始类型:不要把
String password改成byte[] encryptedPassword,而是用 Base64 编码为String存回原字段 - 给加密字段加
@JsonIgnore,改用@JsonGetter/@JsonSetter控制序列化行为,避免反射和序列化逻辑耦合 - 别在反射加密时调用业务 getter:有些 getter 会懒加载关联对象,一触发就带出整棵树,JSON 序列化时直接栈溢出
同一个字段多次反射加密导致重复加盐或密文嵌套
没做幂等控制,比如 HTTP 请求重试、AOP 切面重复执行、或对象被多次传入加密工具类,结果一个 password 字段被 AES 加密了三次,解密时要套三层才能拿到明文,而且每次加盐不同,根本不可逆。
实操建议:
- 在字段值上做简单标记,比如加密后追加固定前缀
"ENC_v1_",解密前先value.startsWith("ENC_v1_")判断是否已处理 - 不依赖字段值判断?那就用
ThreadLocal记录当前线程已处理过的字段,适合单次请求生命周期内防重入> - 绝对不要在实体类的 setter 里自动加密——这会让调用方完全无法感知数据状态,debug 时字段看着是密文,以为是初始化问题,其实是 setter 悄悄干的
反射本身不关心业务语义,它只忠实地执行你写的每行代码。字段是不是该加密、加几次、用什么密钥、密文要不要持久化……这些决策点全在反射调用之外,但最容易被当成“反射的事”甩锅给反射。










