(9)Dubbo2.6.x开发rest风格的服务接口

x33g5p2x  于2021-12-21 转载在 其他  
字(16.2k)|赞(0)|评价(0)|浏览(232)

本文介绍

本文将会给大家介绍一下在Dubbo中如何开发rest风格的服务接口,然后也会通过一个小demo做个演示案例。

前言

在之前的版本中如果想用Dubbo提供rest风格的接口,可以使用当当网的Dubbox,GitHub地址为:https://github.com/dangdangdotcom/dubbox, 不过在2015年更新到2.8.4版本之后已经停止更新,不过好消息是Dubbo官方在2.6.0版本已经合并了Dubbox的部分功能,目前已经支持提供rest风格的服务了。

rest的优点

以下摘自维基百科:

  •     可更高效利用缓存来提高响应速度
  •     通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
  •     浏览器即可作为客户端,简化软件需求
  •     相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
  •     不需要额外的资源发现机制
  •     在软件技术演进中的长期的兼容性更好

Dubbo提供rest服务的好处

个人感觉好处主要有以下几点(欢迎补充):

  1. 简化异构系统之间的(跨语言)调用
  2. 简化手机、PC等客户端的调用(之前都需要使用web应用进行中转)
  3. 简化浏览器AJAX调用,类似2

Dubbo中开发rest接口详细说明

标准Java REST API:JAX-RS简介

Dubbo基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持。

JAX-RS是JAVA EE6 引入的一个新技术。 JAX-RS即Java API for RESTful Web Services,是一个Java 编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了Java SE5引入的Java注解来简化Web服务的客户端和服务端的开发和部署。

JAX-RS得到了业界的广泛支持和应用,其著名的开源实现就有很多,包括Oracle的Jersey,RedHat的RestEasy,Apache的CXF和Wink,以及restlet等等。另外,所有支持JavaEE 6.0以上规范的商用JavaEE应用服务器都对JAX-RS提供了支持。

说明文档

如果对在Dubbo2.6.X中如何开发Rest形式接口不清楚的话,请参考官方用户文档 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/rest.html,官方文档已经写得非常详细了,我就不copy了。不过里面有些东西还是没有说明的,开发过程中也有一些坑,下面通过一个demo示例给大家演示一下。

Dubbo开发Rest形式接口案例

项目结构还是跟《(2)Dubbo入门使用案例》一样,我还是在上面做的改造。

公共接口工程dubbo-interface

包含暴露的服务接口,model类,枚举,自定义异常等信息,服务提供者、消费者都会依赖该工程。当前案例中添加了User类、暴露的服务接口UserService。

pom.xml如下:添加了JAX-RS中的注解所需依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.wkp</groupId>
  <artifactId>dubbo-interface</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>dubbo-interface</name>

	<dependencies>
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>2.0</version>
		</dependency>
	</dependencies>
</project>

User类如下:省略了set、get方法。

public class User implements Serializable {
	private static final long serialVersionUID = 1L;
	private long id;
	private int age;
	private String name;
	private String sex;

	public User() { 
		super();
	}

	public User(int age, String name, String sex) {
		this(0,age,name,sex);
	}
	public User(long id,int age, String name, String sex) {
		this.id=id;
		this.age = age;
		this.name = name;
		this.sex = sex;
	}
}

UserService接口如下:定义了两个方法,addUser为添加用户,getUser为按照userId查找用户。

package com.wkp.service.rest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.wkp.model.User;

@Path("/user")
public interface UserService {

	@Path("/add")
	@POST
	@Produces({MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER + "=UTF-8"})
	@Consumes({MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER + "=UTF-8"})
	public Long addUser(User user);
	
	@Path("/get/{id:\\d+}")
	@GET
	@Produces({MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER + "=UTF-8"})
	@Consumes({MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER + "=UTF-8"})
	public User getUser(@PathParam("id")Long id);
}

服务提供者dubbo-provider工程

pom.xml如下:用到的rest支持的jar包不会自动依赖,需要手动添加。

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wkp</groupId>
	<artifactId>dubbo-provider</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>com.wkp</groupId>
			<artifactId>dubbo-interface</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dubbo</artifactId>
			<version>2.6.4</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>2.12.0</version>
		</dependency>
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
			<version>4.0.35.Final</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>
		<!-- Dubbo的rest支持 -->
		<!-- 如果要使用tomcat server -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>8.0.11</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-logging-juli</artifactId>
			<version>8.0.11</version>
		</dependency>
		
		<!-- rest支持 -->
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
                    <groupId>org.jboss.resteasy</groupId>
                    <artifactId>resteasy-jaxrs</artifactId>
                    <version>3.0.19.Final</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-client</artifactId>
			<version>3.0.19.Final</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		
		 <!-- 如果要使用json序列化 -->
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson-provider</artifactId>
            <version>3.0.19.Final</version>
        </dependency>

        <!-- 如果要使用xml序列化 -->
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxb-provider</artifactId>
            <version>3.0.19.Final</version>
        </dependency>

        <!-- 如果要使用netty server -->
        <!-- <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-netty</artifactId>
            <version>3.0.19.Final</version>
        </dependency> -->

        <!-- 如果要使用Sun HTTP server -->
        <!-- <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jdk-http</artifactId>
            <version>3.0.19.Final</version>
		</dependency> -->
	</dependencies>
</project>

UserService接口的实现类UserServiceImpl如下:没有添加数据库持久化,为了方便用的全局map保存来代替的,使用AtomicLong来生成全局自增id。

package com.wkp.service.rest.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import com.wkp.model.User;
import com.wkp.service.rest.UserService;

public class UserServiceImpl implements UserService {
	
	//存放添加的user
	private static final Map<Long, User> userMap=new ConcurrentHashMap<Long, User>();
	//生成自增ID
	private static final AtomicLong userIdGenerator=new AtomicLong(0);

	@Override
	public User getUser(Long id) {
		return userMap.get(id);
	}

	@Override
	public Long addUser(User user) {
		user.setId(userIdGenerator.incrementAndGet());
		userMap.put(user.getId(), user);
		return user.getId();
	}
}

服务提供者配置文件rest-provider.xml如下:对接口暴露了两种协议,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
	
	<!-- 具体的实现bean -->
	<bean id="userService" class="com.wkp.service.rest.impl.UserServiceImpl" />

	<!-- 提供方应用信息,用于计算依赖关系 -->
	<dubbo:application name="simple-provider">
		<dubbo:parameter key="qos.enable" value="true" />
		<dubbo:parameter key="qos.accept.foreign.ip" value="true" />
		<dubbo:parameter key="qos.port" value="22222" />
	</dubbo:application>

	<!-- 使用zookeeper注册中心暴露服务地址(zookeeper单节点时,address的值例如:zookeeper://192.168.74.4:2181) -->
	<dubbo:registry address="zookeeper://192.168.74.4:2181?backup=192.168.74.5:2181,192.168.74.6:2181" />

	<!-- 多协议 -->
	<!-- rest服务端口8080,server为tomcat -->
	<dubbo:protocol name="rest" port="8080" contextpath="restApi" server="tomcat"/>
	<!-- 用dubbo协议在20880端口暴露服务 -->
	<dubbo:protocol name="dubbo" port="20880"/>

	<!-- 声明需要暴露的服务接口  写操作可以设置retries=0 避免重复调用SOA服务 -->
	<dubbo:service retries="0" interface="com.wkp.service.rest.UserService" ref="userService" />

</beans>

服务提供者启动类如下:

package com.wkp.service.rest.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RestProvider {

	@SuppressWarnings("resource")
	public static void main(String[] args) throws Exception {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "rest-provider.xml" });
		context.start(); 
		System.in.read(); // 为保证服务一直开着,利用输入流的阻塞来模拟
	}
}

服务消费者dubbo-consumer工程

pom.xml如下:消费者也要手动添加rest支持

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wkp</groupId>
	<artifactId>dubbo-consumer</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>com.wkp</groupId>
			<artifactId>dubbo-interface</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dubbo</artifactId>
			<version>2.6.4</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>2.12.0</version>
		</dependency>
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
			<version>4.0.35.Final</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>
		
		<!-- rest支持 -->
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.0.19.Final</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-client</artifactId>
			<version>3.0.19.Final</version>
		</dependency>
		
		<dependency>
		    <groupId>org.apache.httpcomponents</groupId>
		    <artifactId>httpclient</artifactId>
		    <version>4.5.2</version>
		</dependency>
		<dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>fastjson</artifactId>
		    <version>1.2.7</version>
		</dependency>
	</dependencies>
</project>

服务消费者配置文件rest-consumer.xml如下:

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

	<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
	<dubbo:application name="simple-consumer">
		<dubbo:parameter key="qos.enable" value="true" />
		<dubbo:parameter key="qos.accept.foreign.ip" value="true" />
		<dubbo:parameter key="qos.port" value="33333" />
	</dubbo:application>

	<!-- 注册中心地址 -->
	<dubbo:registry address="zookeeper://192.168.74.4:2181?backup=192.168.74.5:2181,192.168.74.6:2181" />
	
	<!-- 生成远程服务代理,可以像使用本地bean一样使用demoService 检查级联依赖关系 默认为true 当有依赖服务的时候,需要根据需求进行设置 -->
	<dubbo:reference id="userService" check="false" interface="com.wkp.service.rest.UserService" />
</beans>

服务消费者启动调用类如下:里面分别演示了两种服务接口的调用方式

package com.wkp.service.rest.consumer;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.alibaba.fastjson.JSONObject;
import com.wkp.model.User;
import com.wkp.service.rest.UserService;

public class RestConsumer {
	
	private static String baseUri="http://localhost:8080/restApi";

	@SuppressWarnings("resource")
	public static void main(String[] args) throws IOException, InterruptedException {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"rest-consumer.xml"});
		context.start();
		
		//RPC调用
		UserService userService = (UserService) context.getBean("userService");
		Long userOneId = userService.addUser(new User(20, "张三","男"));
		System.out.println("[dubbo调用] 添加用户成功,id:"+userOneId);
		User userOne = userService.getUser(userOneId);
		System.out.println("[dubbo调用]查找用户成功,user=:"+JSONObject.toJSONString(userOne));
		
		//Rest调用
		String userTwoId = post(new User(30, "李四","女"));
		System.out.println("[rest调用]添加用户成功,id:"+userTwoId);
		String userTwo = get(userTwoId);
		System.out.println("[rest调用]查找用户成功,user=:"+userTwo);
		
		System.in.read(); // 为保证服务一直开着,利用输入流的阻塞来模拟
	}
	
	public static String post(User user) throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClientBuilder.create().build();
		HttpPost request=new HttpPost(baseUri+"/user/add");
		request.setHeader("Content-Type", "application/json;charset=UTF-8");
		String params = JSONObject.toJSONString(user);
		HttpEntity entity=new StringEntity(params, "utf-8");
		request.setEntity(entity);
		CloseableHttpResponse response = httpClient.execute(request);
		int statusCode = response.getStatusLine().getStatusCode();
		String resp=null;
		if(statusCode==200) {
			resp= EntityUtils.toString(response.getEntity());
		}
		return resp;
	}
	
	public static String get(String userId) throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClientBuilder.create().build();
		HttpGet request=new HttpGet(baseUri+"/user/get/"+userId);
		request.setHeader("Content-Type", "application/json;charset=UTF-8");
		CloseableHttpResponse response = httpClient.execute(request);
		String resp = parseResponse(response);
		return resp;
	}

	private static String parseResponse(CloseableHttpResponse response) throws IOException {
		int statusCode = response.getStatusLine().getStatusCode();
		String resp=null;
		if(statusCode==200) {
			resp= EntityUtils.toString(response.getEntity());
		}
		return resp;
	}
}

调用结果如下所示:

[dubbo调用] 添加用户成功,id:1
[dubbo调用]查找用户成功,user=:{"age":20,"id":1,"name":"张三","sex":"男"}
[rest调用]添加用户成功,id:2
[rest调用]查找用户成功,user=:{"id":2,"age":30,"name":"李四","sex":"女"}

相信看到这里,你也可以用Dubbo开发自己的Rest形式的接口了。如果你想了解更多这方面的案例,请参考Dubbo官方给出的案例工程 incubator-dubbo-samples 中的 dubbo-sample-rest,项目GitHub地址为:https://github.com/apache/incubator-dubbo-samples

笔者在做上面案例的时候还是遇到了好几个坑的,下一节跟大家分享一下,欢迎继续关注呦!

相关文章