前言:
异常分为运行时异常和非运行时异常,所谓的运行时异常是指那些不需要异常捕获的异常,总是交由虚拟机接管,如:ArrayIndexOutOfBoundsException,我们在写程序时,并没有使用try..catch来捕获它。
以前,我们进行项目开发时,习惯性的喜欢使用大量的try...catch...finally方法来进行异常处理,并且,只是将异常信息保存到log日志中即可,并没有将一些异常信息以可读性的方式返回给前端用户。而在一些比较大的项目中,进行异常统一处理是架构师或项目经理必须考虑的问题之一。
Jersey 提供了统一异常处理机制,使得在发生运行时异常时,自动跳转到相应的异常处理资源中,并将处理结果返回给前端用户。对于程序开发人员而言,不需要编写太多的try...catch块,只需要使用throw关键字将自定义的异常抛出即可。
一、Jersey异常处理统一机制
1、演示项目结构及技术
(1)技术:Spring4.1.4+Jersey2.21+JPA+PostMan,使用Spring Data JPA技术,其中PostMan是测试工具,可以在chrome浏览器中安装该插件,模拟前端向服务端发送请求。
(2)演示案例构建采用Maven
(3)案例结构如下:
(4)案例异常处理结构图
2、业务场景说明
本项目中,只是以分页查询中的page页码不能为负数为例,来演示Jersey的统一异常处理机制。当page页码为负数时,抛出UserException运行时异常,交由Jersey统一异常处理资源类ExceptionMappingResource处理,该类解析抛出的异常信息,解析ExceptionKeys定义的常量key,读取配置文件messages_zh_CN.properties定义的中文异常Unicode码,并能够实现参数配置。
3、代码片段说明
A、messages_zh_CN.properties异常配置内容
11110=\u67E5\u8BE2\u8BF7\u6C42\u8D77\u59CB\u9875 {0} \u662F\u8D1F\u6570其中,{0},表示需要传递一个参数值来为其赋值,“11110”为key,等于号后面的unicode码为返回给前端用户显示的中文异常提示信息
B、ExceptionKeys异常key常量定义内容
package com.spring.jersy.jpa.hibernate.constants; public class ExceptionKeys { // 所有的异常key字符串开头都加了E标识,在后面的解析过程中会截取掉E字符,取E字符后面的编号 // 如下:“E11110”,变成“11110”,才能与messages_zh_CN.properties中的key一一对应 public final static String page_number_greater_zero = "E11110"; }C、MessageUtil异常信息解析工具类
package com.spring.jersy.jpa.hibernate.util; import java.text.MessageFormat; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import com.spring.jersy.jpa.hibernate.exception.BaseException; public final class MessageUtil { private static MessageUtil instance = new MessageUtil(); // 用于读取资源属性文件(properties) private ResourceBundle resourceBoudle = null; public static MessageUtil getInstance() { return instance; } /** * 根据异常key获取对应的异常信息 * * @param exceptionId * @return */ public String getMessage(String exceptionId) { String message = resourceBoudle.getString(getErrorID(exceptionId)); return message; } /** * 根据异常获取对应的中文异常 * * @param e * @return */ public String getMessage(BaseException e) { String message = resourceBoudle.getString(getErrorID(e.getMessage())); Object[] arguments = e.getValues(); if (arguments != null) { message = MessageFormat.format(message, arguments); } return message; } private MessageUtil() { init(); } /** * 读取、加载存放key/value形式,并被Unicode编码的properties配置信息 */ private void init() { try { resourceBoudle = new PropertyResourceBundle( getClass() .getClassLoader() .getResourceAsStream( "com/spring/jersy/jpa/hibernate/message/messages_zh_CN.properties")); } catch (Exception ex) { // LOGGER.error("Error loading messages properties", ex); } } /** * 根据抛出的异常编号截取与配置文件对应的异常编号 编号E11110——>变成11110 * * @param exceptionID * :example 编号E11110 * @return:变成11110 */ private String getErrorID(String exceptionID) { exceptionID = exceptionID.substring(1); return exceptionID; } }其中,使用java.util.ResourceBundle类来读取、解析properties文件,使用java.text.MessageFormat类来格式化消息,即将配置文件中的{0}参数自动替换为传递过来的值。
D、BaseException异常基类,继承RuntimeException
package com.spring.jersy.jpa.hibernate.exception; public class BaseException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; private Object[] values; private int code = 500; public BaseException() { } public BaseException(String msg) { super(msg); } public BaseException(String msg, String... params) { super(msg); if (null != params) { values = new Object[params.length]; for (int i = 0; i < params.length; i++) { values[i] = params[i]; } } } public BaseException(String msg, int code, String... params) { this(msg, params); this.code = code; } public BaseException(String msg, int code) { super(msg); this.code = code; } public BaseException(String message, Throwable cause, String... params) { super(message, cause); if (null != params) { values = new Object[params.length]; for (int i = 0; i < params.length; i++) { values[i] = params[i]; } } } public BaseException(int code, String message, Throwable cause, String... params) { this(message, cause, params); this.code = code; } public Object[] getValues() { return values; } public void setValues(Object[] values) { this.values = values; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }E、用户异常处理类UserException
package com.spring.jersy.jpa.hibernate.exception; import com.spring.jersy.jpa.hibernate.constants.ExceptionKeys; public class UserException extends BaseException{ /** * */ private static final long serialVersionUID = 1L; public UserException(){ } public UserException(String... params){ super(ExceptionKeys.page_number_greater_zero,params); } }F、异常统一处理类ExceptionMappingResource
package com.spring.jersy.jpa.hibernate.resource; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import com.spring.jersy.jpa.hibernate.bean.ExceptionResponse; import com.spring.jersy.jpa.hibernate.exception.BaseException; import com.spring.jersy.jpa.hibernate.util.MessageUtil; /** * 必须添加该注解使得在程序的任何地方发生运行时异常时,自动的进行异常统一处理 * 同时该类要能够被Jersey扫描到 */ @Provider public class ExceptionMappingResource implements ExceptionMapper<Exception> { @Override public Response toResponse(Exception exception) { ResponseBuilder responseBuilder = null; // 用户自定义的运行时异常处理 if (exception instanceof BaseException) { //获取用户抛出的异常信息 String code = exception.getMessage(); //根据异常key获取对应的中文异常信息 String message = MessageUtil.getInstance().getMessage( (BaseException) exception); Throwable cause = exception.getCause(); if (cause != null) { String realReason = cause.getMessage(); message += " 可能的原因是:" + realReason + ""; } //自定义异常返回实体bean类 ExceptionResponse error = new ExceptionResponse(); error.setCode(code); error.setMessage(message); error.setStatus("error"); responseBuilder = Response.ok(error).status( ((BaseException) exception).getCode()); } // 其他异常 else { ExceptionResponse error = new ExceptionResponse(); error.setCode("E000000"); error.setMessage(exception.getMessage()); error.setStatus("error "); responseBuilder = Response.ok(error).status( Response.Status.INTERNAL_SERVER_ERROR); } return responseBuilder.build(); } }注意:该类必须被@Provider注解,且要实现JAX-RS提供的ExceptionMapper才能时刻跟踪处理项目中抛出的运行时异常,同时,该类需要被放在能够被JPA扫描器扫描到的包中。
G、用户资源类UserResource中的分页查询测试方法
// 分页查询 @GET @Path("/findByPage") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response findByPage( @DefaultValue("0") @QueryParam(value = "page") Integer page, @DefaultValue("10") @QueryParam(value = "pageSize") Integer pageSize) { if (page >= 0) { List<User> listUser = userSpringDataJpaService.findUserByPage(page, pageSize); ResponseBuilder rb = null; if (listUser == null) { rb = Response.serverError().status(500); } else { rb = Response.ok(listUser).status(200); } return rb.build(); } else { //如果page为负数,则抛出该异常 throw new UserException(page.toString()); } }
其中,“page.toString”参数值是用来为messages_zh_CN.properties中的{0}参数赋值的,提示该值是负数。
二、PostMan测试
postman是一个很好的模拟前端发送请求,测试后端代码正确性的测试工具,可以通过chrome浏览器安装插件,界面如下:
接下来,我们准备测试Jersey rest统一异常处理机制是否搭建成功,发送如下请求连接:
附:
测试源码:Spring Data JPA+Jersey+TestNG用户CRUD操作案例