Spring BeanUtils复制请求作用域Bean报错:无法复制channel属性排查
在Java 17、Spring Boot 3.2.0环境下,使用org.springframework.beans.BeanUtils.copyProperties将请求作用域的RequestContext Bean复制到同作用域的Transaction Bean时,抛出Could not copy property 'channel' from source to target错误,底层异常为java.lang.IllegalArgumentException: object is not an instance of declaring class。相关日志显示,调用Transaction的方法时,目标实例竟为RequestContext类型,存在明显的实例类型不匹配。
根因分析
- 两个Bean均使用
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS),Spring会生成CGLIB动态代理类包装真实Bean实例。 Transaction继承自TransactionBase,channel等属性定义在父类中。当BeanUtils通过反射调用代理类的父类属性setter方法时,CGLIB代理的方法转发逻辑出现异常,导致代理内部的目标实例被错误混淆(如日志中调用Transaction.toString()时目标为RequestContext)。- 非代理场景(移除@Scope、直接new实例)复制正常,进一步验证问题源于代理机制与父类属性的交互冲突。
解决方案
1. 获取代理背后的真实Bean实例后复制
通过Spring的AopUtils工具类获取代理对象的真实目标实例,绕过代理直接操作真实Bean:
import org.springframework.aop.support.AopUtils; // 在复制逻辑中添加: RequestContext realRequestContext = AopUtils.isAopProxy(requestContext) ? (RequestContext) AopUtils.getTargetObject(requestContext) : requestContext; Transaction realTransaction = AopUtils.isAopProxy(transaction) ? (Transaction) AopUtils.getTargetObject(transaction) : transaction; copyPropertyUtils.copy(realRequestContext, realTransaction);
注意:若代理无法获取目标实例(如特殊代理场景),可尝试通过ApplicationContext直接获取真实Bean。
2. 调整代理模式为JDK动态代理
若RequestContext和Transaction可实现对应接口,将proxyMode改为ScopedProxyMode.INTERFACES,使用JDK代理替代CGLIB,避免父类属性的代理转发问题:
@Component @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) public class RequestContext implements Serializable, RequestContextInterface { // ... 属性和方法 } @Component @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) public class Transaction extends TransactionBase implements Serializable, TransactionInterface { // ... 属性和方法 }
3. 使用编译期属性复制工具替代BeanUtils
改用MapStruct等编译期生成复制代码的工具,避免反射调用代理类的方法:
- 引入MapStruct依赖(Maven):
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
- 定义复制Mapper:
@Mapper(componentModel = "spring") public interface RequestContextTransactionMapper { RequestContextTransactionMapper INSTANCE = Mappers.getMapper(RequestContextTransactionMapper.class); // 忽略不需要复制的属性,或显式指定映射关系 @Mapping(target = "actors", ignore = true) @Mapping(target = "senderPartner", ignore = true) // ... 其他忽略的属性 void copyRequestContextToTransaction(RequestContext source, Transaction target); }
- 代码中调用:
RequestContextTransactionMapper.INSTANCE.copyRequestContextToTransaction(requestContext, transaction);
4. 调整Bean的获取方式
若业务允许,避免直接注入请求作用域的Transaction Bean,而是通过Provider获取新实例(非代理):
import jakarta.inject.Provider; @Component @Slf4j public class RequestInterceptor implements HandlerInterceptor { @Autowired private RequestContext requestContext; @Autowired private Provider<Transaction> transactionProvider; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Transaction transaction = transactionProvider.get(); copyPropertyUtils.copy(requestContext, transaction); // ... 后续逻辑 return true; } }
内容的提问来源于stack exchange,提问作者Arun Sudhakaran

