Spring自动配置下EntryService代理导致类型不匹配问题排查
这个问题我之前也碰到过,结合你给出的异常信息、代码和日志,核心原因是Spring通过JDK动态代理生成了EntryService的代理对象,但你用类类型(EntryService)注入时,JDK代理对象并不属于该类的实例,导致类型不匹配。咱们一步步来排查和解决:
一、为什么Spring会创建代理?
从你提供的日志可以明确看到:
2018-03-23 11:50:22.421 DEBUG 15483 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
2018-03-23 11:50:22.425 DEBUG 15483 --- [ main] o.s.aop.framework.JdkDynamicAopProxy : Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [....EntryService@51dae791]
这说明是事务切面(internalTransactionAdvisor)触发了AOP代理。虽然你的EntryService没有标注@Transactional,但要注意:
- 检查父类
AbstractGuidEntityService是否包含@Transactional注解(类级或方法级)——Spring会识别父类继承下来的事务注解,只要有符合条件的事务方法,就会为子类创建代理。
二、JDK动态代理为什么会导致类型不匹配?
Spring默认的AOP代理规则:
- 如果目标类实现了至少一个接口,Spring会用JDK动态代理:代理对象是接口的实现类,不是目标类的子类,所以它的类型是
com.sun.proxy.$ProxyXX,而非EntryService。 - 如果目标类没有实现任何接口,Spring会用CGLIB代理:代理对象是目标类的子类,类型匹配。
你的EntryService继承了AbstractGuidEntityService但没实现接口,大概率是父类实现了某个接口,或者Spring的自动代理逻辑触发了JDK代理,这就是类型不匹配的根本原因。
三、排查与解决步骤
1. 先确认代理触发的根源
- 检查
AbstractGuidEntityService的代码,看是否有@Transactional注解(类级或方法级)。如果有,那就是事务触发了代理。 - 如果父类也没有事务注解,再检查项目中是否有其他AOP切面(比如缓存、日志切面)会匹配到
EntryService的方法。
2. 解决类型不匹配的方案
方案一:改用接口类型注入(推荐的标准做法)
给EntryService定义一个接口,比如:
public interface EntryServiceInterface { Entry get(String guid); }
然后让EntryService实现这个接口:
@Service public class EntryService extends AbstractGuidEntityService<Entry> implements EntryServiceInterface { @Override public Entry get(String guid) { .... } }
注入时用接口类型:
@Autowired private EntryServiceInterface entryService;
这样JDK代理对象就能匹配接口类型,不会再抛出异常。
方案二:强制Spring使用CGLIB代理
如果你不想改接口,可以让Spring用CGLIB生成类代理,代理对象是EntryService的子类,类型就能匹配。根据你的环境选择配置方式:
- Spring Boot环境:在
application.properties中添加:spring.aop.proxy-target-class=true - 注解驱动配置:在配置类上修改事务注解:
@EnableTransactionManagement(proxyTargetClass = true) - XML配置:修改事务驱动标签:
<tx:annotation-driven proxy-target-class="true"/>
方案三:移除不必要的代理触发点
如果确认父类的@Transactional是多余的,可以移除该注解,这样Spring就不会为EntryService创建事务代理,自然也就不会出现类型问题。
四、额外验证方法
可以临时修改注入代码,打印代理对象的类型,帮助你确认问题:
@Autowired private Object entryService; @PostConstruct public void checkProxyType() { System.out.println("代理对象类型:" + entryService.getClass()); System.out.println("是否是EntryService实例:" + (entryService instanceof EntryService)); }
如果是JDK代理,instanceof会返回false;如果是CGLIB代理,会返回true。
内容的提问来源于stack exchange,提问作者rmalchow

