如题,为了返回格式的统一化,尤其是当出现异常时,我做了如下的尝试:
首先建立全局异常拦截器:GlobalExceptionInterger
public class JfastGlobeExceptionInterceptor implements Interceptor{ @Override public void intercept(Invocation inv) { try{ inv.invoke(); }catch (Exception e) { e.printStackTrace(); String errName = e.getClass().getName(); if(errName.endsWith("NotAuthException")){ //ErrRender 是我自定义的一个类,用来识别是否ajax请求和获取网页要求的返回格式是否为json ErrRender.auto(inv.getController(), 1000, e.getMessage()); return; }else if(errName.endsWith("ActionException")){ //???? renderError抛出的异常 return; }else{ ErrRender.auto(inv.getController(), 1001, e.getMessage()); return; } } }
问题来了:Jfinal中有一类renderError,它的本质也是抛出异常,当然会被捕捉到。貌似Jfinal正常情况下,运行时发生的异常都会通过它来抛出。我该如何处理呢?
思路1、我在拦截器里对数据进行判断,再进行相应的返回。复杂!这里有我自己在控制器中执行的,还有Jfinal产生的。更深层的是:
renderError只有两个重载方法renderError(int errCode)和renderError(int errCode, String view)。这意味着,我在控制器中不能自定义文字、说明错误(或者在view 中写,但这实现起来也不容易吧!)。
结论:不便在拦截器中对这类异常进一步处理。
思路2、对renderError及相关的类进行修改,令其自行处理返回内容的格式。可是发现不能传入自定义的信息。
首先,我建立了MyErrorRender并继承ErrorRender。
public class MyErrorRender extends ErrorRender { protected String msg ; public MyErrorRender(int errorCode, String view) { super(errorCode, view); } /** * JFinal 的Controller限制了发挥,下面的构造方法用不上! * <br>期盼以后可以用上吧 * @param msg * @param errorCode */ public MyErrorRender(String msg, int errorCode) { super(errorCode, null); this.msg = msg; } @Override public void render() { response.setStatus(getErrorCode()); // HttpServletResponse.SC_XXX_XXX PrintWriter writer = null; try { if(XX.isAjax(request) && XX.isJsonRender(request)){ RenderManager.me().getRenderFactory().getJsonRender(getErrorJson()).setContext(request, response).render();; return; } // render with view String view = getView(); if (view != null) { RenderManager.me().getRenderFactory().getRender(view).setContext(request, response).render(); return; } // render with html content response.setContentType(contentType); writer = response.getWriter(); writer.write(getErrorHtml()); writer.flush(); } catch (IOException e) { throw new RenderException(e); } } /** * 获得返回的json格式数据 * @author WangWei * @created 2017-10-19 下午2:28:16 * @return */ private String getErrorJson() { int errorCode = super.getErrorCode(); msg = this.msg == null?errorCode + " 系统错误":this.msg; return JsonKit.toJson(Kv.by("errCode", errorCode).set("msg", msg)); } /** * 尝试通过过滤HTTP状态码,进行自定义错误输出,以便拦截器处理 */ public int getErrorCode(){ int myCode = super.getErrorCode(); ArrayList<Integer> sysCode = new ArrayList<>(Arrays.asList(201,202,203,204, 301,302,303,304,305,306, 400,401,402,403,404,407,415, 500,501,502,503)); if(sysCode.contains(myCode)){ return myCode; }else{ return 200; } } }
按照教程设置了MyRenderFactory extends RenderFactory,并进行了配置。
然而,似乎上述工作是徒劳的。因为,renderError是要抛出异常的,必然还是被全局异常拦截器拦截。这就又回到思路1中。
那么,只能退而求其次,在errCode和view上下功夫了:即通过errCode将错误分类。然后在全局异常拦截器中直接将ActionException异常抛出,而不做其他任何处理。
现在,就剩下一个问题没有解决了:如何传入自定义的错误信息。
控制器中可以setAttr("errMsg","错误信息"),然后在MyErrorRender类中通过request.getAttribute("errMsg").toString()获取。
不知道有没有更好的解决方案?
附:我对json格式返回统一为:{“errCode”: 1001,"msg":"自定义的错误信息"}
2017.10.24更新
经过进一步思考,基本实现了“智能”地统一返回。基本思路还是用了上述的“思路1”(本以为复杂,但深入思考后化繁为简了!)
对全局异常拦截优化如下:
public class JfastGlobeExceptionInterceptor implements Interceptor{ @Override public void intercept(Invocation inv) { try{ inv.invoke(); }catch (Exception e) { e.printStackTrace(); String errName = e.getClass().getName(); if(errName.endsWith("NotAuthException")){ String errMsg = e.getMessage(); inv.getController().setAttr("errMsg", StrKit.isBlank(errMsg)?"系统错误":errMsg); inv.getController().renderError(1000); return; }else if(errName.endsWith("ActionException")){ // renderError抛出的异常 throw e; }else{ String errMsg = e.getMessage(); inv.getController().setAttr("errMsg", StrKit.isBlank(errMsg)?"系统错误":errMsg); inv.getController().renderError(2); return; } } }
并将MyErrorRender改为继承Render,而不再继承ErrorRender,以便深度扩展,具体如下:
package com.jfast.core.config; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import com.jfast.util.XX; import com.jfinal.config.Constants; import com.jfinal.kit.JsonKit; import com.jfinal.kit.Kv; import com.jfinal.kit.StrKit; import com.jfinal.render.Render; import com.jfinal.render.RenderException; import com.jfinal.render.RenderManager; import com.jfinal.template.Engine; public class MyErrorRender extends Render { protected static final String contentType = "text/html; charset=" + getEncoding(); protected static final String version = "<center><a href='http://www.jfinal.com?f=ev-" + Const.JFINAL_VERSION + "' target='_blank'><b>Powered by JFinal " + Const.JFINAL_VERSION + "</b></a></center>"; protected static final String html404 = "<html><head><title>404 Not Found</title></head><body bgcolor='white'><center><h1>404 Not Found</h1></center><hr>" + version + "</body></html>"; protected static final String html500 = "<html><head><title>500 Internal Server Error</title></head><body bgcolor='white'><center><h1>500 Internal Server Error</h1></center><hr>" + version + "</body></html>"; protected static final String html401 = "<html><head><title>401 Unauthorized</title></head><body bgcolor='white'><center><h1>401 Unauthorized</h1></center><hr>" + version + "</body></html>"; protected static final String html403 = "<html><head><title>403 Forbidden</title></head><body bgcolor='white'><center><h1>403 Forbidden</h1></center><hr>" + version + "</body></html>"; protected String msg ; protected int errorCode; public MyErrorRender(){ } public MyErrorRender(int errorCode){ this.errorCode = errorCode; } public MyErrorRender(int errorCode, String view) { this.errorCode = errorCode; this.view = view; } /** * JFinal 的Controller限制了发挥,不能直接使用下面的构造方法 * <br>可以在控制其中这样使用: * <br>this.render(new MyErrorRender(msg, errorCode)); * @param msg * @param errorCode */ public MyErrorRender(String msg, int errorCode) { this.errorCode = errorCode; this.msg = msg; } @Override public void render() { response.setStatus(getErrorCode()); // HttpServletResponse.SC_XXX_XXX PrintWriter writer = null; try { // XX 是我自定义的一个工具类,进行一些判断操作 if(XX.isAjax(request) && XX.isJsonRender(request)){ RenderManager.me().getRenderFactory().getJsonRender(getErrorJson()).setContext(request, response).render();; return; } // render with view String view = getView(); if (view != null) { RenderManager.me().getRenderFactory().getRender(view).setContext(request, response).render(); return; } // render with html content response.setContentType(contentType); String template = getErrorHtml(); Kv k = new Kv(); if(StrKit.isBlank(msg)){ k= Kv.by("errMsg", request.getAttribute("errMsg")); }else{ k = Kv.by("errMsg", msg); } template = Engine.use().getTemplateByString(template).renderToString(k); writer = response.getWriter(); writer.write(template); writer.flush(); } catch (IOException e) { throw new RenderException(e); } } /** * 获得返回的json格式数据 * @author WangWei * @created 2017-10-19 下午2:28:16 * @return */ private String getErrorJson() { int errCode = this.errorCode; Object tempMsg = request.getAttribute("errMsg"); String errMsg = null; if(!XX.isEmpty(tempMsg)){ errMsg = tempMsg.toString(); } msg =errCode + " " + (this.msg == null ? ( null == errMsg ? "系统错误": errMsg):this.msg); return JsonKit.toJson(Kv.by("errCode", errCode).set("msg", msg)); } public int getErrorCode(){ int myCode = this.errorCode; ArrayList<Integer> sysCode = new ArrayList<>(Arrays.asList(201,202,203,204, 301,302,303,304,305,306, 400,401,402,403,404,407,415, 500,501,502,503)); if(sysCode.contains(myCode)){ return myCode; }else{ return 200; } } public String getErrorHtml() { int errorCode = this.errorCode; String html = new Constants().getErrorView(errorCode); if(StrKit.isBlank(html)){ if (errorCode == 404) return html404; if (errorCode == 500) return html500; if (errorCode == 401) return html401; if (errorCode == 403) return html403; }else{ return html; } return "<html><head><title>" + errorCode + " #(errMsg??'Error')</title></head><body bgcolor='white'><center><h1>" + errorCode + " #(errMsg??'Error')</h1></center><hr><center><a href='http://www.51xlxy.com?v="+JfastConst.VERSION+"' target='_blank'><b>Powered by JFast "+JfastConst.VERSION+"</b></a></center></body></html>"; } }
使用方法:
1、系统配置
在Jfinal的配置类中设置:
public void configConstant(Constants me) { …… me.setRenderFactory(new MyRenderFactory()); }
2、MyRenderFactory
public class MyRenderFactory extends RenderFactory{ @Override public Render getErrorRender(int errCode){ return new MyErrorRender(errCode,constants.getErrorView(errCode)); } @Override public Render getErrorRender(int errCode, String view){ return new MyErrorRender(errCode,view); } // }
3、使用
在控制器中需要返回错误或者异常的地方:
this.renderError(errCode); this.renderError(errCode, view);//或者这样。 /* * this.renderError(errCode, errRender); * 这个用法要小心: * errRender变量的类型最好还是自定义的MyErrorRender; * 如果是其他类型,返回值就跳出了我们辛苦设置的范围。 */ this.renderError(errCode, errRender); //上述方式其实是要抛出异常的,不用写return; //下面的方式,不会主动抛出异常: this.render(new MyErrorRender(errCode)); this.render(new MyErrorRender(errCode, view)); this.render(new MyErrorRender(errMsg, errCode));
写到这里,我就有点小激动,因为,我“朝思暮想”的自定义错误信息,可以通过上述最后一个方法实现了!
方案缺陷:
未能实现Controller.renderError(errCode, errRender)这一方法的自定义。
因此,如果我们或者Jfinal系统内部使用了这一方法,就需要web前端开发中考虑这一特殊情形。