Dubbo封装rest服务返回结果
由于Dubbo服务考虑到一个是给其他系统通过RPC调用,另外一个是提供HTTP协议本身系统的后台管理页面,因此Dubbo返回参数在rest返回的时候配置拦截器进行处理。
在拦截器中,对返回参数封装成如下对象,并统一输出到前端。
1 package com.wjs.common.web; 2 3 import org.apache.commons.lang.builder.ReflectionToStringBuilder; 4 5 /** 6 * 服务返回给客户端的json对象包装 7 */ 8 public class JsonResult<T> { 9 10 private boolean success = false; 11 12 private String resultMsg = ""; 13 14 private T data = null; 15 16 public JsonResult(boolean status, String resultMsg) { 17 this.success = status; 18 this.resultMsg = resultMsg; 19 } 20 21 public JsonResult(boolean status, String resultMsg, T data) { 22 this.success = status; 23 this.resultMsg = resultMsg; 24 this.data = data; 25 } 26 27 28 29 public boolean isSuccess() { 30 31 return success; 32 } 33 34 35 public void setSuccess(boolean success) { 36 37 this.success = success; 38 } 39 40 41 public String getResultMsg() { 42 43 return resultMsg; 44 } 45 46 47 public void setResultMsg(String resultMsg) { 48 49 this.resultMsg = resultMsg; 50 } 51 52 53 public T getData() { 54 55 return data; 56 } 57 58 59 public void setData(T data) { 60 61 this.data = data; 62 } 63 64 /* 65 * (non-Javadoc) 66 * 67 * @see java.lang.Object#toString() 68 */ 69 @Override 70 public String toString() { 71 return ReflectionToStringBuilder.toString(this); 72 } 73 }
需要继承的服务处理类有(按照实际调用顺序)ExceptionMapper<Exception>, ContainerResponseFilter, WriterInterceptor 。
1. ExceptionMapper<Exception> 用于后台返回异常结果的封装处理,需要对异常类进行区别对待,并返回错误提示信息。
/** * 异常拦截 */ @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()); return ex.getResponse(); }
if(e instanceof BaseException){ // 后台自定义异常,用于传递异常参数 BaseException ex = (BaseException) e; result.setData(ex.getErrorParams()); } LOGGER.error(errMsg, e); return Response.status(200).entity(result).build(); }
2.ContainerResponseFilter 可用于服务端返回状态码进行处理,由于方法返回类型是 void,或者某个资源类型返回是 null 的情况,JAX-RS 框架一般会返回状态204,表示请求处理成功但没有响应内容。因此在此处我们对返回值封装成200-处理成功,数据为空的情况。
@Override public void 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.WriterInterceptor 用于服务端返回值的写入处理,进入到此处的一般都是执行成功的返回值。
@Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { // System.err.println("进入结果处理——aroundWriteTo"); // 针对需要封装的请求对结构进行封装处理。这里需要注意的是对返回类型已经是封装类(比如:异常处理器的响应或者204处理可能已经是封装类型)时要忽略掉。 Object originalObj = context.getEntity(); String wrapTag = context.getProperty("Not-Wrap-Result") == null ? "" : context.getProperty("Not-Wrap-Result").toString(); // 客户端通过head参数显示提醒不要对返回值进行封装 Boolean wraped = originalObj instanceof 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(); }
以上代码即完成了返回值的封装,如果有其他异常类型还需要特殊处理,在ExceptionMapper增加异常判断。
完成代码如下:
package com.wjs.member.plugin.intercepter; import java.io.IOException; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.ClientErrorException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; import org.apache.commons.lang.StringUtils; import org.jboss.resteasy.plugins.providers.multipart.InputPart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.wjs.common.exception.BaseException; import com.wjs.common.web.JsonResult; @Provider public class RestContextInteceptor implements ContainerRequestFilter, WriterInterceptor, ContainerResponseFilter, ExceptionMapper<Exception> { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExecutionInterceptor.class); private static final String ENCODING_UTF_8 = "UTF-8"; @Context private HttpServletRequest request; @Context private HttpServletResponse response; @Override public void 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); } @Override public void 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 = originalObj instanceof 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 public void 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); } } /** * 异常拦截 */ @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()); return ex.getResponse(); } if(e instanceof BaseException){ BaseException ex = (BaseException) e; result.setData(ex.getErrorParams()); } LOGGER.error(errMsg, e); return Response.status(200).entity(result).build(); } private void logRequest(HttpServletRequest request) { StringBuffer logBuffer = new StringBuffer(128); // // refer_url // logBuffer.append("referUrl:"); // // @SuppressWarnings("rawtypes") // Enumeration e = request.getHeaders("Referer"); // String referUrl; // if (e.hasMoreElements()) { // referUrl = (String) e.nextElement(); // } else { // referUrl = "直接访问"; // } // logBuffer.append(referUrl); // 获取url logBuffer.append(";URL:"); StringBuffer url = request.getRequestURL(); if (url != null) { StringUtils.replaceOnce(url.toString(), "http://", "https://"); } logBuffer.append(url.toString()); // 判断用户请求方式是否为ajax logBuffer.append(";ISAJAX:"); String requestType = request.getHeader("X-Requested-With"); if (StringUtils.isNotBlank(requestType) && requestType.equals("XMLHttpRequest")) { logBuffer.append("true"); } else { logBuffer.append("false"); } //获取所有参数 StringBuffer paramBuffer = new StringBuffer(64); Enumeration<?> enu = request.getParameterNames(); while (enu.hasMoreElements()) { String paraName = (String) enu.nextElement(); paramBuffer.append(paraName); paramBuffer.append(": "); paramBuffer.append(request.getParameter(paraName)); paramBuffer.append(", "); } logBuffer.append(";Parameters:"); logBuffer.append(paramBuffer.toString()); // 记录本次请求耗时: // Long requestEndTime = System.currentTimeMillis(); // Long requestStartTime = StringUtils.isEmpty(MDC.get(REQUEST_STARTTIME)) ? requestEndTime : Long.valueOf(MDC.get(REQUEST_STARTTIME)); // logBuffer.append(";COST:"); // logBuffer.append(requestEndTime - requestStartTime); // logBuffer.append(" ms"); if (!"HEAD".equals(request.getMethod())) { LOGGER.info("requestLog:" + logBuffer.toString()); } } }
异常处理的时候,比较容易出现异常
org.jboss.resteasy.spi.UnhandledException: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: com.wjs.common.web.JsonResult of media type: text/html;charset=UTF-8
org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209) org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:230) org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:139)
这种情况一般是有什么特殊异常,在toResponse中没有进行特殊处理,Response中.entity(result)放入了返回对象无法解析造成。处理方式参考(e instanceof javax.ws.rs.NotFoundException)增加异常的特殊处理即可。