问题初始
在dubbo2.6.0之前,如果你在Spring Boot的Main类上使用@EnableTransactionManagement
注解启用了事务,在需要事务的方法上注解@Transactional
:
@Transactional
public boolean addContract(ContractBO bo) {
...
}
在消费端会出现找不到对应的Service的错误
问题跟踪
分析源码,先找到spring-boot-starter-dubbo
项目中的DubboConfigurationApplicationContextInitializer.initialize()
:
Environment env = applicationContext.getEnvironment();
String scan = env.getProperty("spring.dubbo.scan");
if (scan != null) {
AnnotationBean scanner = BeanUtils.instantiate(AnnotationBean.class);
scanner.setPackage(scan);
scanner.setApplicationContext(applicationContext);
applicationContext.addBeanFactoryPostProcessor(scanner);
applicationContext.getBeanFactory().addBeanPostProcessor(scanner);
applicationContext.getBeanFactory().registerSingleton("annotationBean", scanner);
}
看到这里对Spring扩展了BeanPostProcessor
,扩展类是AnnotationBean
。AnnotationBean
主要继承了这两个接口:BeanFactoryPostProcessor
, BeanPostProcessor
。BeanFactoryPostProcessor
接口扩展了Bean的元数据读取,BeanPostProcessor
扩展了Bean的初始化。BeanFactoryPostProcessor
的接口方法中,我们看到他的逻辑是读入指定package的元数据,暂时不深入了解;
继续找到BeanPostProcessor的接口方法postProcessAfterInitialization
:
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (!isMatchPackage(bean)) {
return bean;
}
Service service = bean.getClass().getAnnotation(Service.class);
if (service != null) {
ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
serviceConfig.setRef(bean);
if (void.class.equals(service.interfaceClass())
&& "".equals(service.interfaceName())) {
if (bean.getClass().getInterfaces().length > 0) {
serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
} else {
throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
}
}
...
}
我们看到了dubbo中重要的类:ServiceConfig
,这个类是用来发布服务的,发布服务的时候要设置接口名,所以我们找到了问题可能出在serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
这一行。
问题解决
因为我们在实现类上用了事务注解,所以Spring必然会对这个类进行代理,而获得代理类的接口bean.getClass().getInterfaces()[0]
得到的应该是实现类本身。
比如如果用cglib代理的话,原先定义UserService
,然后有一个UserServiceImpl
实现类,UserServiceImpl
类中的方法使用了@Transactional
注解,UserServiceImpl
在Spring容器中初始化的时候,真正的UserServiceImpl
就会变成代理类,类似于UserServiceImpl$$357
之类。
这时候bean.getClass().getInterfaces()[0]
其实返回的是UserServiceImpl.class
。
需要对Spring AOP有深入理解,才可以理解这个问题。
最后的做法是修改源码:
if (bean.getClass().getInterfaces().length > 0) {
// 如果是代理类,则有特殊逻辑
if (AopUtils.isAopProxy(bean) && bean.getClass().getSuperclass().getAnnotation(Service.class) != null) {
serviceConfig.setInterface(bean.getClass().getSuperclass().getInterfaces()[0]);
} else {
serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
}
}
重新发布dubbo到本地maven仓库或私服即可解决这个问题。
注意
需要注意的是,Spring Boot的Main(启动)类上的注解最好这样写:
@EnableTransactionManagement(proxyTargetClass = true)
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
显式声明使用cglib代理!