Dubbo--Spring注解事务结合使用的bug

问题初始

在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代理!

转载于:https://my.oschina.net/lizaizhong/blog/1592488

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值