问题
昨天晚上,在线上调试时, 发现在一个spring的bean里, 只要加上@Transactional
注解后, 就会在spring启动时报错
报错信息有点多, 需要注意的点只有一点
Cannot convert value of type [com.sun.proxy.$Proxy21 implementing gambol.examples.spring.service.Console,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [gambol.examples.spring.service.AbstractConsole] for property 'abstractConsole': no matching editors or conversion strategy found
解决方法
先说解决方案, 增加一行配置, 解决
<aop:aspectj-autoproxy proxy-target-class="true"/>
强制使用cglib进行代理.
解决过程
- 早上看完报错信息之后, liuyue猜测是因为使用了transactional之后, 生成的是jdk的proxy, jdk的proxy不能注入成AbstractConsole.
- 我有疑问, 为什么使用了transactional注解之后才生成代理? 难道不是所有spring管理的service都会生成一个代理么?
- 我把线上的代码层次拷贝到了本地,生成的代码结构是
其中, consoleService会通过spring 注入到contanerService里.
<bean id="containerService" class="gambol.examples.spring.service.ContainerService">
<property name="abstractConsole" ref="consoleService"/>
</bean>
ConsoleService的代码是
@Service
public class ConsoleService extends AbstractConsole {
@Transactional
public void test() {
System.out.println(" test in consoleService");
// console();
}
}
- 在本地启动, 问题无法重现. debug时发现, 当去掉
@Transactional
注解后, ConsoleService并没有生成任何代理. 加上@Transactional
注解后, ConsoleService使用CGLib生成代理. - 突然发现, 我的Console这个接口里并没有任何方法, 在Console加上方法, 再重启tomcat, 问题复现.
public interface Console {
// 这个方法很关键
void console();
}
- 到现在为止, 线上问题的原因已经确认. jdk的proxy不能注入成AbstractConsole.
- 解决方案是: 强制使用CGlib来生成代理.(Spring官方的说法是, JDK生成的代理, 比cglib的代理稍微快一点, 所以会默认使用jdk生成的基于接口的代理)
- 引入了一个新的问题: 为什么Console里没有方法时, spring就会使用cglib进行代理, spring是怎么判断的?
- debug spring的启动过程, 最后在
ProxyProcessorSupport.java
里找到原因.
protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());
boolean hasReasonableProxyInterface = false;
for (Class<?> ifc : targetInterfaces) {
if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&
// Spring会在这段代码里, 判断需要代理的接口里的方法数量
ifc.getMethods().length > 0) {
hasReasonableProxyInterface = true;
break;
}
}
if (hasReasonableProxyInterface) {
// Must allow for introductions; can't just set interfaces to the target's interfaces only.
for (Class<?> ifc : targetInterfaces) {
proxyFactory.addInterface(ifc);
}
}
else {
proxyFactory.setProxyTargetClass(true);
}
}
结论/收获
- spring并不会为每个bean,都生成代理类
- 只有使用AOP时, spring才会为他生成代理类(proxy class)
- 生成代理类的规则
方式 | proxy-target-class=”false”(默认情况) | proxy-target-class=”true” |
---|---|---|
bean实现了一个非空接口 | 使用JDK proxy 生成接口的代理 | CGLIB |
bean未实现一个非空接口 | CGLIB代理生成实体bean | CGLIB |