必须加 setaccessible(true),因 jvm 默认禁止反射访问非 public 成员;需对所有字段统一调用,注意 securitymanager 限制、jdk 12+ 的 --illegal-access 策略及嵌套 null 值处理。

Java 反射拷贝对象时 setAccessible(true) 为什么必须加
不加会直接抛 IllegalAccessException,尤其对 private 字段或无参构造器缺失的类。JVM 默认禁止反射访问非 public 成员,哪怕你只是读写自己写的类。
实操建议:
- 所有字段赋值前必须调用
field.setAccessible(true),不能只在读取时加、写入时漏掉 - 如果目标类有 SecurityManager(如某些容器环境),
setAccessible可能被拒绝,需提前校验或降级为浅拷贝 - 注意 JDK 12+ 对反射限制收紧,
--illegal-access=deny下会直接失败,需配合--add-opens启动参数
复制嵌套对象时 Field.get() 返回 null 怎么办
不是 bug,是字段本身值为 null —— 反射不会自动实例化对象。比如源对象中 private Address address; 没初始化,反射拿到的就是 null,直接 clone(address) 就 NPE。
实操建议:
- 先判断
value == null,再决定跳过、设默认值,还是递归调用拷贝逻辑(需防循环引用) - 对集合类型(
List、Map)要单独处理:不能直接set原引用,得新建实例并逐个元素拷贝 - 避免无脑递归:遇到
java.lang.*、java.time.*等不可变类型,直接赋值即可,不用反射深入
为什么 BeanUtils.copyProperties() 有时不生效
Apache Commons BeanUtils 和 Spring 的 BeanUtils.copyProperties() 都依赖 getter/setter,且要求方法名严格匹配。字段叫 isDeleted,但 getter 是 getDeleted(),它就找不到对应 setter。
实操建议:
- 检查字段是否真有 public setter,且方法签名是
void setXxx(Type),返回值和参数都不能错 - 忽略大小写敏感问题:Spring 版本低于 5.2.8 时,
userId和setUserid()不匹配(注意 id 大小写) - 字段类型不一致时静默跳过,不报错也不赋值 —— 要验证结果就得手动比对字段值,不能只看是否抛异常
自定义拷贝工具里要不要缓存 Field[] 和 Method 引用
要,否则每次拷贝都 clazz.getDeclaredFields() + 遍历 + getMethod(),性能差一个数量级,尤其高频调用场景(如 RPC 参数转换)。
实操建议:
- 用
ConcurrentHashMap<class>, List<fieldinfo>></fieldinfo></class>缓存解析结果,FieldInfo包含 field、getter、setter 三元组 - 缓存 key 必须用
Class对象本身,别用类名字符串 —— 同名但不同 ClassLoader 加载的类要视为不同类型 - 注意泛型擦除:
List<string></string>和List<integer></integer>运行时都是List,缓存结构里不需要存泛型信息,但深拷贝时得靠运行时类型判断是否需要递归
最麻烦的其实是 final 字段和 native 方法控制的对象(比如 String.value 数组),反射改不了,强行设会抛 IllegalAccessException 或 JVM 直接崩溃。这种得提前识别并跳过,别指望通用工具兜底。










