演示用GitHub地址:https://github.com/suyin58/dubbo-rest-example

1       Dubbo_rest介绍

Dubbo自2.6.0版本后,合并了dubbox的restful风格的接口暴露方式,其restful的处理采用的是jboss.resteasy框架。使用该功能可以简便的将dubbo服务直接通过http的方式发布,不需要再使用中转的http应用暴露服务。

如上图,原有结构中,HTTP访问需要通过API应用中转服务,RPC访问调用dubbo应用,使用dubbo_rest之后,HTTP和RPC访问都直接调用dubbo应用即可。

2       使用方法

    参考demo项目 https://github.com/suyin58/dubbo-rest-example,

由于dubbo 2.6依赖jar包javax.json.bind-api是JDK1.8版本,因此建议使用JDK1.8版本,但是使用JDK1.7版本也可以,不影响以下介绍的功能点。

2.1    POM依赖

2.1.1      Dubbo依赖

    

2.1.2      Resteasy依赖

    

    

    

2.2    接口暴露

2.2.1      服务协议配置

在dubbo_service.xml中增加dubbo:protocol name="rest"显式申明提供rest服务。

    

2.2.2      接口服务配置

同原有Dubbo服务一样,将Dubbo服务发布出来,需要使用<dubbo:service显式申明。

2.3    服务注解

所有在<dubbo:service显式申明提供服务的接口实现类(也可以加载接口中,设计原则建议放在实现类做具体操作)中,需要增加@Path注解。

如果某个dubbo服务显式进行了申明,但是没有增加@Path注解,否则会应用无法启动,并报错【RESTEASY003130: Class is not a root resource.  It, or one of its interfaces must be annotated with @Path】

其原因是在于resteasy中定义扫描并加载哪些类,是由dubbo提供的,dubbo将所有显式申明的<dubbo:service都被扫描,如果某个类没有@Path则会报错

参考:https://blog.csdn.net/wtopps/article/details/76919008

2.4    方法注解

    

    

2.5    参数注解

    

l   @PathParam  -->url路径问号前的参数

    请求路径:http://www.wjs.com/product/111/detail

    Path路径:@Path(“product/{productCode}/detail”)

    参数注解:detail(@PathParam(“productCode”) code){

l   @QueryParam -->url路径问号后中的参数

    请求路径:http://www.wjs.com/product/detail?productCode=111

    Path路径:@Path(“product/detail”)

    参数注解:detail(@QueryParam(“productCode”) code)

l   @FormParam  -->x-www.form-urlencoded参数

描述

application/x-www-form-urlencoded

在发送前编码所有字符(默认)

multipart/form-data

不对字符编码。

在使用包含文件上传控件的表单时,必须使用该值。

  通过Form表单提交的请求,需要区分是普通form表单(enctype=application/x-www-form-urlencoded),还是文件上传表单(enctype=multipart/form-data)。

  普通表单可以在方法体中使用@FormParam注解,也可以在类的属性中使用@FormParam注解。文件表单,由于服务端获取到的是个文件六,不能在方法体中使用@FormParam注解,但是可以在MultipartForm注解的类中使用@FormParam注解。

l   @BeanParam – 对象属性赋值

  如果接收参数处理是个对象的话,可以使用@BeanParam注解对象获取参数

  参数注解:pageListTemplate(@BeanParam DeductTplQry qry)

  对象属性注解:

  

l   @MultipartForm -- multipart/form-data表单参数

  如果是文件上传,那么需要通过@MultipartForm注解获取对象参数

  参数注解:upload(@MultipartForm DiskFile diskFile,

  对象属性注解:

  

  在这里需要注意的是,文件上传由于resteasy框架的缺陷,无法自动获取流中的文件名称,需要通过前端的form表单提供并传给后台。

l   @Context

如果需要部分HTTP上下环境参数的话,例如request或者response的话,可以通过@Context注解获取。

参数注解:httparg(@Context HttpServletRequest request, @Context HttpServletResponse){

2.6    文件上传/下载

2.6.1      单个文件上传

    单个文件上传,参考@ MultipartForm注解说明

2.6.2      多个文件上传

    @MultipartForm不支持,使用MultipartFormDataInput的方式处理。

    示例代码:

@POST

@Path("/uploadmulti")

@Consumes(MediaType.MULTIPART_FORM_DATA)

@Override

public Object uploadmulti(MultipartFormDataInput input) {

System.out.println("进入业务逻辑");

//      MultipartFormDataReader

Map<String, List<InputPart>> uploadForm = input.getFormDataMap();

InputStream inputStream = null;

OutputStream outStream = null;

final String DIRCTORY = "D:/temp/datainputmulti/";

//取得文件表单名

try {

for (Iterator<Entry<String, List<InputPart>>> it = uploadForm.entrySet().iterator() ; it.hasNext() ;) {

Entry<String, List<InputPart>> entry = it.next();

List<InputPart> inputParts = entry.getValue();

initDirectory(DIRCTORY);

for (InputPart inputPart : inputParts) {

// 文件名称

String fileName = getFileName(inputPart.getHeaders());

inputStream = inputPart.getBody(InputStream.class, null);

//把文件流保存;

File file = new File(DIRCTORY + fileName);

intindex;

byte[] bytes = newbyte[1024];

outStream = new FileOutputStream(file);

while ((index = inputStream.read(bytes)) != -1) {

outStream.write(bytes, 0, index);

outStream.flush();

}

}

}

} catch (Exception e) {

e.printStackTrace();

}finally {

if(null != inputStream){

try {

inputStream.close();

} catch (IOException e) {

}

}

if(null != outStream){

try {

outStream.close();

} catch (IOException e) {

}

}

}

return Response.ok().build();

}

异常处理:文件名称获取乱码问题

MultipartFormDataInput的方式获取文件名称存在字符集乱码的问题,需要通过重新编译代码的方式解决。解决方式参考:https://www.cnblogs.com/loveyou/p/9529856.html

异常处理:

2.6.3      文件下载

文件下载,通过参数的@Context获取http Response,然后直接通过Response.outputstream往外面写流即可。

示例代码:

@GET

@Path("/download")

@Produces("application/json; charset=UTF-8")

@Override

publicvoid download(@QueryParam(value = "fileName") String fileName, @Context HttpServletRequest request, @Context HttpServletResponse response) {

InputStream in = null;

OutputStream out = null;

try {

fileName = "app.log";

String filePath = "D:\\logs\\manageplat\\" + fileName;

response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

in = new FileInputStream(filePath); //获取文件的流

intlen = 0;

bytebuf[] = newbyte[1024];//缓存作用

out = response.getOutputStream();//输出流

while ((len = in.read(buf)) > 0) //切忌这后面不能加分号 ”;“

{

out.write(buf, 0, len);//向客户端输出,实际是把数据存放在response中,然后web服务器再去response中读取

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if (in != null) {

try {

in.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if (out != null) {

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

3       封装

3.1    权限拦截

  拦截器的配置,在Dubbo的protocol协议中的extension显式申明。

  

3.2    编码拦截

  编码拦截在获得请求的时候进行处理,需要继承接口ContainerRequestFilter。

  @Override

publicvoid filter(ContainerRequestContext requestContext) throws IOException {

System.err.println("进入请求拦截——filter");

// 编码处理

request.setCharacterEncoding(ENCODING_UTF_8);

response.setCharacterEncoding(ENCODING_UTF_8);

request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, ENCODING_UTF_8);

requestContext.setProperty(InputPart.DEFAULT_CHARSET_PROPERTY, ENCODING_UTF_8);

// 客户端head显示提醒不要对返回值进行封装

requestContext.setProperty("Not-Wrap-Result", requestContext.getHeaderString("Not-Wrap-Result") == null ? "" : requestContext.getHeaderString("Not-Wrap-Result"));

// 请求参数打印

logRequest(request);

}

3.3    异常处理

  系统异常情况对异常结果进行封装,需要继承接口ExceptionMapper。

/**

* 异常拦截

*/

@Override

public Response toResponse(Exception e) {

//      System.err.println("进入结果处理——toResponse");

String errMsg = e.getMessage();

JsonResult<Object> result = new JsonResult<>(false, StringUtils.isEmpty(errMsg)? "系统异常" : errMsg);

if(javax.ws.rs.ClientErrorException.class.isAssignableFrom(e.getClass())){

ClientErrorException ex = (ClientErrorException) e;

LOGGER.error("请求错误:" + e.getMessage());

returnex.getResponse();

}

if(einstanceof BaseException){

BaseException  ex = (BaseException) e;

result.setData(ex.getErrorParams());

}

LOGGER.error(errMsg, e);

return Response.status(200).entity(result).build();

}

3.4    结果封装

对结果封装,需要继承WriterInterceptor, ContainerResponseFilter,对200状态码的结果进行封装处理,以及对异常状态码的结果进行封装处理。

@Override

publicvoid aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {

System.err.println("进入结果处理——aroundWriteTo");

//     针对需要封装的请求对结构进行封装处理。这里需要注意的是对返回类型已经是封装类(比如:异常处理器的响应可能已经是封装类型)时要忽略掉。

Object originalObj = context.getEntity();

String wrapTag = context.getProperty("Not-Wrap-Result") == null ? "" : context.getProperty("Not-Wrap-Result").toString(); // 客户端显示提醒不要对返回值进行封装

Boolean wraped = originalObjinstanceof JsonResult; // 已经被封装过了的,不用再次封装

if (StringUtils.isBlank(wrapTag) && !wraped){

JsonResult<Object> result = new JsonResult<>(true, "执行成功");

result.setData(context.getEntity());

context.setEntity(result);

//        以下两处set避免出现Json序列化的时候,对象类型不符的错误

context.setType(result.getClass());

context.setGenericType(result.getClass().getGenericSuperclass());

}

context.proceed();

}

@Override

publicvoid filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {

System.err.println("进入结果处理——filter");

//     它的目的是专门处理方法返回类型是 void,或者某个资源类型返回是 null 的情况,

//     这种情况下JAX-RS 框架一般会返回状态204,表示请求处理成功但没有响应内容。我们对这种情况也重新处理改为操作成功

String wrapTag = requestContext.getProperty("Not-Wrap-Result") == null ? "" : requestContext.getProperty("Not-Wrap-Result").toString(); // 客户端显示提醒不要对返回值进行封装

if (StringUtils.isBlank(wrapTag) &&responseContext.getStatus() == 204 && !responseContext.hasEntity()){

responseContext.setStatus(200);

responseContext.setEntity(new JsonResult<>(true, "执行成功"));

responseContext.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);

}

3.5    客户端申明不对结果做封装

  requestContext.getHeaderString("Not-Wrap-Result")

  客户端请求的时候,增加header”Not-Wrap-Result”

Dubbo 使用rest协议发布http服务的更多相关文章

  1. [转载] 基于Dubbo的Hessian协议实现远程调用

    转载自http://shiyanjun.cn/archives/349.html Dubbo基于Hessian实现了自己Hessian协议,可以直接通过配置的Dubbo内置的其他协议,在服务消费方进行 ...

  2. 基于Dubbo的Hessian协议实现远程调用

    Dubbo基于Hessian实现了自己Hessian协议,可以直接通过配置的Dubbo内置的其他协议,在服务消费方进行远程调用,也就是说,服务调用方需要使用Java语言来基于Dubbo调用提供方服务, ...

  3. dubbo发布web服务实例

    dubbo角色与调用执行过程 dubbo节点角色说明:provider: 暴露服务的服务提供方consumer: 调用远程服务的服务消费方registry: 服务注册于发现的注册中心monitor: ...

  4. dubbo 发布 RPC 服务

    Dubbo 发布 RPC 服务 建立服务提供者项目 pom.xml <?xml version="1.0" encoding="UTF-8"?> & ...

  5. 搞懂Dubbo服务发布与服务注册

    一.前言 本文讲服务发布与服务注册,服务提供者本地发布服务,然后向注册中心注册服务,将服务实现类以服务接口的形式提供出去,以便服务消费者从注册中心查阅并调用服务. 本文源码分析基于org.apache ...

  6. Dubbo(三):深入理解Dubbo源码之如何将服务发布到注册中心

    一.前言 前面有说到Dubbo的服务发现机制,也就是SPI,那既然Dubbo内部实现了更加强大的服务发现机制,现在我们就来一起看看Dubbo在发现服务后需要做什么才能将服务注册到注册中心中. 二.Du ...

  7. dubbo发布webservice服务

    dubbo发布webservice服务 学习了:https://blog.csdn.net/zhangyunpengchang/article/details/51567127 https://blo ...

  8. 【转】Dubbo是Alibaba开源的分布式服务框架

    Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合).从服务模型的角度来看,Dubbo采用的是一种非常简单的模 ...

  9. Dubbo阿里Alibaba开源的分布式服务框架

    [获奖公布]"我的2016"主题征文活动    程序猿全指南,让[移动开发]更简单!      [观点]移动原生App开发和HTML 5开发,你更看好哪个?   博客的神秘功能 D ...

随机推荐

  1. IE滚动条

    之前一直没留意过IE下面的滚动条样式,今天碰到一个优化需求,需要去掉横向的滚动条,只保留竖的滚动条. 实现方式很简单,设置宽度,overflow-x:hiddle:overflow-y:scroll ...

  2. AtCoder Grand Contest 009 D:Uninity

    题目传送门:https://agc009.contest.atcoder.jp/tasks/agc009_d 题目翻译 定义只有一个点的树权值为\(0\),若干棵(可以是\(0\)棵)权值为\(k\) ...

  3. AtCoder Regular Contest 074 E:RGB Sequence

    题目传送门:https://arc074.contest.atcoder.jp/tasks/arc074_c 题目翻译 给你一行\(n\)个格子,你需要给每个格子填红绿蓝三色之一,并且同时满足\(m\ ...

  4. POJ 3258 最小值最大化 二分搜索

    题意:牛要到河对岸,在与河岸垂直的一条线上,河中有N块石头,给定河岸宽度L,以及每一块石头离牛所在河岸的距离, 现在去掉M块石头,要求去掉M块石头后,剩下的石头之间以及石头与河岸的最小距离的最大值. ...

  5. Object中的方法

    1.equals() 2.toString() package com_package1; public class Person44 { private int age; public int ge ...

  6. css水平居中方式

    1. text-align:center 这种方式只适合于内联元素或者文字处于块元素当中是,给块元素设置这个,那么块元素当中的文字或者内联元素则居中.兼容各种浏览器 <div class=&qu ...

  7. Gym - 100801H Hash Code Hacker (构造)

    题意:求 n 个哈希值相同的串. 析:直接构造,通过取模来查找相同的串. 代码如下: #pragma comment(linker, "/STACK:1024000000,102400000 ...

  8. POJ 2311 Cutting Game (博弈)

    题意:给定一个长方形纸张,每次只能水平或者垂直切,如果切到1*1的方格就胜,问先手胜还是负. 析:根据Nim游戏可知,我们可以分别求出每个子游戏的和,就是答案,所以我们就枚举每一种切法,然后求出SG函 ...

  9. InstelliJ IDEA使用js+servlet+ajax入门

    对于Ajax,我们先了解三点(完整的JS代码在后面) 一.Ajax的出现对javascript的影响. Ajax是微软提出的一种允许客户端脚本发送HTTP请求的技术(XMLHTTP),拯救了大多数ja ...

  10. 去除vue路由跳转地址栏后的哈希值#

    去除vue路由跳转地址栏后的哈希值#,我们只需要在路由跳转的管理文件router目录下的index.js中加上一句代码即可去掉哈希值# mode:"history" import ...