Dubbo 使用rest協議發佈http服務

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

1       Dubbo_rest介紹

Dubbo2.6.0版本後,合併了dubboxrestful風格的接口暴露方式,其restful的處理採用的是jboss.resteasy框架。使用該功能能夠簡便的將dubbo服務直接經過http的方式發佈,不須要再使用中轉的http應用暴露服務。html

 

如上圖,原有結構中,HTTP訪問須要經過API應用中轉服務,RPC訪問調用dubbo應用,使用dubbo_rest以後,HTTPRPC訪問都直接調用dubbo應用便可。前端

2       使用方法

    參考demo項目 https://github.com/suyin58/dubbo-rest-examplejava

因爲dubbo 2.6依賴jarjavax.json.bind-apiJDK1.8版本,所以建議使用JDK1.8版本,可是使用JDK1.7版本也能夠,不影響如下介紹的功能點。git

2.1    POM依賴

2.1.1      Dubbo依賴

    

 


2.1.2      Resteasy依賴

    

     

     

 


2.2    接口暴露

2.2.1      服務協議配置

dubbo_service.xml中增長dubbo:protocol name="rest"顯式申明提供rest服務。github

    

 


2.2.2      接口服務配置

同原有Dubbo服務同樣,將Dubbo服務發佈出來,須要使用<dubbo:service顯式申明。web

 


2.3    服務註解

全部在<dubbo:service顯式申明提供服務的接口實現類(也能夠加載接口中,設計原則建議放在實現類作具體操做)中,須要增長@Path註解。json

 


 


若是某個dubbo服務顯式進行了申明,可是沒有增長@Path註解,不然會應用沒法啓動,並報錯【RESTEASY003130: Class is not a root resource.  It, or one of its interfaces must be annotated with @Pathapi

其緣由是在於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    權限攔截

  攔截器的配置,在Dubboprotocol協議中的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」

相關文章
相關標籤/搜索