dubbo中的泛化调用

/ Dubbo / 没有评论 / 787浏览

我们知道在使用dubbo框架调用服务时,必须保证服务提供端和服务调用端实现同一个接口。这样才能保证我们调用远程接口时,才像调用本地接口一样。但有时我们并不能保证服务调用端能实现我们服务提供端的接口。例如向第三方提供接口服务。那如果遇到这种问题时,我们就可以使用dubbo中的泛化调用,在使用泛化调用时,是不需要服务调用端实现服务提供端的接口的。也就是服务调用端不会依赖任何服务提供端的服务。


下面我们先看一下服务提供端的代码,因为服务提供端和正常的dubbo调用没有任何区别,所以我们不在做过多的介绍,而是直接看源码。

UserInfo:

public class UserInfo {
  private String username;
  private String password;

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }
}

UserInfoService:

/**
 * 接口类
 */
public interface UserInfoService {

    /**
     * 获取UserInfo信息
     *
     * @return
     */
    UserInfo getUserInfo(String name);

}

UserInfoImpl:

/**
 * 接口实现类
 */
@Service
public class UserInfoImpl implements UserInfoService {

  public UserInfo getUserInfo(String name) {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername(name);
    userInfo.setPassword("jilinwula");
    return userInfo;
  }

}

provider.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 提供方信息-->
    <dubbo:application name="jilinwula"/>

    <!-- 使用zookeeper暴露服务地址 -->
    <dubbo:registry address="127.0.0.1:2181" protocol="zookeeper"/>

    <!-- 暴露服务端口 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 声明需要暴露的服务接⼝ -->
    <dubbo:service interface="com.jilinwula.api.UserInfoService" ref="userInfoImpl"/>

    <!-- component-scan自动扫描注解 -->
    <context:component-scan base-package="com.jilinwula.*"/>

</beans>

Provider:

/**
 * 启动Dubbo提供方
 */
public class Provider {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"provider.xml"});
        context.start();
        System.in.read();
    }
}

下面我们看一下服务调用端的配置。

consumer.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 调用方信息-->
    <dubbo:application name="jilinwula"/>

    <!-- 使用zookeeper查找服务地址 -->
    <dubbo:registry address="127.0.0.1:2181" protocol="zookeeper"/>

    <!-- Dubbo自动生成远程服务代理 -->

    <dubbo:reference id="helloWorld" interface="com.jilinwula.api.UserInfoService" generic="true"/>

</beans>

我们看上面的配置我们多了一个参数就是generic,当将generic参数设置为true时,则表示该服务支持泛化调用。下面我们看一下客户端具体的调用代码。

Consumer:

/**
 * 服务调用方
 */
public class Consumer {

  public static void main(String[] args) throws Exception {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
        new String[]{"consumer.xml"});
    context.start();

    GenericService genericService = (GenericService) context.getBean("helloWorld");
    Object result = genericService
        .$invoke("getUserInfo", new String[]{"java.lang.String"}, new Object[]{"吉林乌拉"});

    Map<String, Object> resultMap = (Map<String, Object>) result;

    Iterator<Map.Entry<String, Object>> iterator = resultMap.entrySet().iterator();
    while (iterator.hasNext()) {
      Map.Entry<String, Object> entry = iterator.next();
      System.out.print(entry.getKey() + "\t");
      System.out.println(entry.getValue());
    }
  }
}

当我们使用dubbo泛化调用时因为调用端没有服务端的接口所有我们不能直接用服务端的接口调用服务。我们可以通过dubbo会我们提供的GenericService接口来调用服务。下面我们看一下GenericService接口的源码:

public interface GenericService {
  Object $invoke(String var1, String[] var2, Object[] var3) throws GenericException;
}

该接口只有一个方法,作用就是返回客户端调用服务端方法的返回值。该方法有3个参数,下我们分别介绍一下这3个参数的作用。

下面我们看一下日志输出:

2018-08-01 22:03:35:193 INFO [ProductBossService,com.alibaba.dubbo.config.ReferenceConfig,425]:  [DUBBO] Refer dubbo service com.alibaba.dubbo.rpc.service.GenericService from url zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=jilinwula&check=false&dubbo=2.6.0&generic=true&interface=com.jilinwula.api.UserInfoService&methods=getUserInfo&pid=2372®ister.ip=192.168.0.4&remote.timestamp=1533126506573&side=consumer×tamp=1533132214110, dubbo version: 2.6.0, current host: 192.168.0.4
password	jilinwula
class	com.jilinwula.api.UserInfo
username	吉林乌拉
2018-08-01 22:03:35:513 INFO [ProductBossService,com.alibaba.dubbo.config.AbstractConfig$1,80]:  [DUBBO] Run shutdown hook now., dubbo version: 2.6.0, current host: 192.168.0.4

当我们将generic参数修改为false时,在执行上述代码时,dubbo就会抛出异常。

018-08-01 22:08:11:709 WARN [ProductBossService,org.springframework.beans.factory.support.FactoryBeanRegistrySupport,71]: FactoryBean threw exception from getObjectType, despite the contract saying that it should return null if the type of its object cannot be determined yet
java.lang.IllegalStateException: com.jilinwula.api.UserInfoService
	at com.alibaba.dubbo.config.ReferenceConfig.getInterfaceClass(ReferenceConfig.java:452)
	at com.alibaba.dubbo.config.spring.ReferenceBean.getObjectType(ReferenceBean.java:63)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getTypeForFactoryBean(FactoryBeanRegistrySupport.java:66)
	at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:490)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:432)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:395)
	at org.springframework.context.support.DefaultLifecycleProcessor.getLifecycleBeans(DefaultLifecycleProcessor.java:275)
	at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:133)
	at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:114)
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:880)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
	at com.jilinwula.consumer.Consumer.main(Consumer.java:14)
Caused by: java.lang.ClassNotFoundException: com.jilinwula.api.UserInfoService
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at com.alibaba.dubbo.config.ReferenceConfig.getInterfaceClass(ReferenceConfig.java:448)
	... 13 more

但如果我们将服务调用方的代码修改成普通dubbo方法调用时,则我依然可以正确调用服务。

Consumer:

/**
 * 服务调用方
 */
public class Consumer {

  public static void main(String[] args) throws Exception {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
        new String[]{"consumer.xml"});
    context.start();

    UserInfoService userInfoService = context.getBean("helloWorld", UserInfoService.class);

    System.out.println(userInfoService.getUserInfo("吉林乌拉"));
  }
}

日志:

2018-08-01 22:21:45:450 INFO [ProductBossService,com.alibaba.dubbo.config.ReferenceConfig,425]:  [DUBBO] Refer dubbo service com.jilinwula.api.UserInfoService from url zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=jilinwula&check=false&dubbo=2.6.0&generic=false&interface=com.jilinwula.api.UserInfoService&methods=getUserInfo&pid=2443®ister.ip=192.168.0.4&remote.timestamp=1533133125791&side=consumer×tamp=1533133304269, dubbo version: 2.6.0, current host: 192.168.0.4
UserInfo{username='吉林乌拉', password='jilinwula'}
2018-08-01 22:21:45:835 INFO [ProductBossService,com.alibaba.dubbo.config.AbstractConfig$1,80]:  [DUBBO] Run shutdown hook now., dubbo version: 2.6.0, current host: 192.168.0.4

但这种调用方式有一个前提就是返回的对象类型必须实现Serializable接口,否者dubbo会报序列化错误。也就是下面异常信息。

2018-08-01 22:56:54:809 INFO [ProductBossService,com.alibaba.dubbo.config.ReferenceConfig,425]:  [DUBBO] Refer dubbo service com.jilinwula.api.UserInfoService from url zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=jilinwula&check=false&dubbo=2.6.0&generic=false&interface=com.jilinwula.api.UserInfoService&methods=getUserInfo&pid=2536®ister.ip=192.168.0.4&remote.timestamp=1533135400379&side=consumer×tamp=1533135413785, dubbo version: 2.6.0, current host: 192.168.0.4
Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method getUserInfo in the service com.jilinwula.api.UserInfoService. Tried 3 times of the providers [192.168.0.4:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.0.4 using the dubbo version 2.6.0. Last error is: Failed to invoke remote method: getUserInfo, provider: dubbo://192.168.0.4:20880/com.jilinwula.api.UserInfoService?anyhost=true&application=jilinwula&check=false&dubbo=2.6.0&generic=false&interface=com.jilinwula.api.UserInfoService&methods=getUserInfo&pid=2536®ister.ip=192.168.0.4&remote.timestamp=1533135400379&side=consumer×tamp=1533135413785, cause: Failed to send response: Response [id=2, version=2.0.0, status=20, event=false, error=null, result=RpcResult [result=UserInfo{username='吉林乌拉', password='jilinwula'}, exception=null]], cause: java.lang.IllegalStateException: Serialized class com.jilinwula.api.UserInfo must implement java.io.Serializable
java.lang.IllegalStateException: Serialized class com.jilinwula.api.UserInfo must implement java.io.Serializable
	at com.alibaba.com.caucho.hessian.io.SerializerFactory.getDefaultSerializer(SerializerFactory.java:400)
	at com.alibaba.com.caucho.hessian.io.SerializerFactory.getSerializer(SerializerFactory.java:374)
	at com.alibaba.com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:381)
	at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectOutput.writeObject(Hessian2ObjectOutput.java:77)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.encodeResponseData(DubboCodec.java:191)
	at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeResponse(ExchangeCodec.java:274)
	at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:72)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:37)
	at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalEncoder.encode(NettyCodecAdapter.java:80)
	at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.handleDownstream(OneToOneEncoder.java:66)
	at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)
	at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendDownstream(DefaultChannelPipeline.java:776)
	at org.jboss.netty.channel.SimpleChannelHandler.writeRequested(SimpleChannelHandler.java:304)
	at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.writeRequested(NettyHandler.java:98)
	at org.jboss.netty.channel.SimpleChannelHandler.handleDownstream(SimpleChannelHandler.java:266)
	at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)
	at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:582)
	at org.jboss.netty.channel.Channels.write(Channels.java:611)
	at org.jboss.netty.channel.Channels.write(Channels.java:578)
	at org.jboss.netty.channel.AbstractChannel.write(AbstractChannel.java:251)
	at com.alibaba.dubbo.remoting.transport.netty.NettyChannel.send(NettyChannel.java:96)
	at com.alibaba.dubbo.remoting.transport.AbstractPeer.send(AbstractPeer.java:52)
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:169)
	at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:50)
	at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:79)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)

这就是dubbo中的泛化调用。下面链接为项目源码: 项目源码