问题描述
使用@Transactional来对业务层进行事务管理,并使用@Service注册提供方时,提供方无法注册。
原因分析
在使用Dubbo注解(@Service)发布提供发时,Dubbo会根据配置把所有扫描到的类放入Spring容器中进行匹配,并遍历这个包中每一个Bean对象,判断这个Bean对象是不是想要发布的那个Bean(含有@Service注解的类),如果符合,则将这个Bean对象发布到zookeeper中,如果不符合,则不会发布。
<dubbo:annotation package="com.ruiqin.service.impl" />
在使用@Transactional注解对业务类进行事务管理时,Spring容器会为这个业务类创建一个代理类,Spring容器中只有代理类,业务类并不在容器中。又因为Spring容器默认使用JDK动态代理的方式创建代理对象,所以此代理对象的包名是com.sun.proxy,这就导致了Dubbo在发布服务的时候无法正确的匹配包名,从而没有发布服务。
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
//如果得到的是false就不会发布服务
if (!isMatchPackage(bean)) {
return bean;
}
//获取Bean的字节码对象,并获得其中为Service的注解
Service service = bean.getClass().getAnnotation(Service.class);
//如果类中存在注解
if (service != null) {
ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
//设置Bean对象的引用
serviceConfig.setRef(bean);
if (void.class.equals(service.interfaceClass())
&& "".equals(service.interfaceName())) {
if (bean.getClass().getInterfaces().length > 0) {
//设置接口类型,为Bean对象实现的所有接口中的第一个,加上上面的引用设置,相当于把注册方放入XML文件中
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.");
}
}
if (applicationContext != null) {
serviceConfig.setApplicationContext(applicationContext);
if (service.registry() != null && service.registry().length > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (String registryId : service.registry()) {
if (registryId != null && registryId.length() > 0) {
registryConfigs.add((RegistryConfig) applicationContext.getBean(registryId, RegistryConfig.class));
}
}
serviceConfig.setRegistries(registryConfigs);
}
if (service.provider() != null && service.provider().length() > 0) {
serviceConfig.setProvider((ProviderConfig) applicationContext.getBean(service.provider(), ProviderConfig.class));
}
if (service.monitor() != null && service.monitor().length() > 0) {
serviceConfig.setMonitor((MonitorConfig) applicationContext.getBean(service.monitor(), MonitorConfig.class));
}
if (service.application() != null && service.application().length() > 0) {
serviceConfig.setApplication((ApplicationConfig) applicationContext.getBean(service.application(), ApplicationConfig.class));
}
if (service.module() != null && service.module().length() > 0) {
serviceConfig.setModule((ModuleConfig) applicationContext.getBean(service.module(), ModuleConfig.class));
}
if (service.provider() != null && service.provider().length() > 0) {
serviceConfig.setProvider((ProviderConfig) applicationContext.getBean(service.provider(), ProviderConfig.class));
} else {
}
if (service.protocol() != null && service.protocol().length > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (String protocolId : service.protocol()) {
if (protocolId != null && protocolId.length() > 0) {
protocolConfigs.add((ProtocolConfig) applicationContext.getBean(protocolId, ProtocolConfig.class));
}
}
serviceConfig.setProtocols(protocolConfigs);
}
try {
serviceConfig.afterPropertiesSet();
} catch (RuntimeException e) {
throw (RuntimeException) e;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
serviceConfigs.add(serviceConfig);
serviceConfig.export();
}
return bean;
}
private boolean isMatchPackage(Object bean) {
if (annotationPackages == null || annotationPackages.length == 0) {
return true;
}
//业务类生成代理对象的包名是 com.sun.proxy 与 要扫描的包名不匹配,所以返回false
String beanClassName = bean.getClass().getName();
for (String pkg : annotationPackages) {
if (beanClassName.startsWith(pkg)) {
return true;
}
}
return false;
}
解决方法
方法1.直接在xml中配置
<bean class="com.ruiqin.service.impl.OrderServiceImpl" id="orderService"/>
<dubbo:service interface="com.ruiqin.service.OrderService" ref="orderService"/>
方法2.将动态代理类的包名改成我们想要扫描的包名
先强制使用CGLIB的方式进行代理对象的生成。
tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
因为Dubbo在发布服务时,接口类型为发布对象实现的第一个接口类型
serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
所以要再指定一下我们的接口类型,确保我们发布的是正确的接口类型
@Service(interfaceClass = OrderService.class)