如何实现统一返回

如题,为了返回格式的统一化,尤其是当出现异常时,我做了如下的尝试:

首先建立全局异常拦截器: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前端开发中考虑这一特殊情形。

评论区

北流家园网

2017-10-21 21:01

收藏下先,等大神回复

abvcb

2017-10-21 23:24

1. 写一个 AbstractController 继承 Controller, 新增 renderError(int code, String message) 方法;
2. 创建 CustomErrorRender 继承 Render, 构造方法为 CustomErrorRender(int code, String message) 重写 render 方法, response 构造方法传入的 code, message (转换 json 格式)
3. AbstractController 新增的 renderError 如下
public void renderError(int errorCode, String message) {
render = new CustomErrorRender(errorCode, message);
}
4. 自定义 Controller 继承 AbstractController 若需要提示错误消息, super.renderError(1234, "错误");




------
new CustomErrorRender 是不健康的用法, 没必要每次调用都创建一个新的类, 具体如何使用可参考 jfinal 的 render 实现方式.

abvcb

2017-10-21 23:31

同时也可以在 AbstractController 重写 renderError(int errorCode) 方法, 让其全部走到 renderError(int errorCode, String message) 实现全部统一处理

麻言

2017-10-24 15:58

@abvcb 看了您的思路,您是将renderError( int errorCode, String message) 完全替换掉原来的renderError( int errorCode, String view);这样固然可以返回json格式的,可是这里还有一个隐含要求“自动返回要求的格式”,即自动判断网页是要返回json还是html。因为:这个renderError是全局异常的返回,既有可能是ajax访问也有可能是普通访问。

abvcb

2017-10-24 22:40

@麻言 也是一样的昂, 这种方式, 也是自定义的 ErrorRender 类, 直接用写好的那个判断下就好

// XX 是我自定义的一个工具类,进行一些判断操作
if(XX.isAjax(request) && XX.isJsonRender(request))

热门反馈

扫码入社