微服务架构实际上是一种将具有不同功能、不同部署位置的服务协调起来共同工作的分布式系统。由于分布式系统的复杂性,对这些服务之间的协调产生的异常处理就显得尤为重要了,好的异常处理不仅便于在异常发生的时候定位跟踪问题,还有利于保证系统的稳定性并可提高系统交互体验的友好性。所以,微服务开发过程中考虑异常情况的处理与考虑正常逻辑同等重要。
本文简单串联Java异常的基础知识和Spring提供的异常处理机制以及Spring Cloud服务间的异常处理机制,以实际项目实践为例说明在Spring Cloud微服务架构下自定义异常及统一处理异常的要点。
Throwable 类是 Java 语言中所有错误或异常的超类,其子类分为Error、Exception。
Error是合理的应用程序不应该试图捕获的严重问题
Exception是合理的应用程序想要捕获的问题,Exception又分为CheckedException和UnCheckedException。
[ ] CheckedException是对于可恢复的条件使用被检查的异常,需要用try…catch…显式的捕获并且进行处理,例如IOException。
[ ] UncheckedException又叫做RuntimeException不是必须捕获的,对于导致程序无法执行的异常,必要时转化成有具体业务描述说明的异常。
@ExceptionHandlerannotation 实现在配置的Controller中拦截并处理异常@Controller
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
@RequestMapping(value="/user/{id}", method=RequestMethod.GET)
public String getUser(@PathVariable("id") int id, Model model) throws Exception{
if(id==1){
throw new UserNotFoundException(id);
}
}
@ExceptionHandler(UserNotFoundException.class)
public ModelAndView handleUserNotFoundException(HttpServletRequest request, Exception ex){
logger.error("Requested URL="+request.getRequestURL());
logger.error("Exception Raised="+ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", request.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
/** 输入参数仅有exception
@ExceptionHandler
public String handleException(UserNotFoundException exception) {
return "errorView";
}
*/
}
实现HandlerExceptionResolver接口,进行全局的异常拦截,使用时需要在MVCConfiguration中初始化异常处理器
public class ServiceHandlerExceptionResolver implements HandlerExceptionResolver {
Logger logger = LoggerFactory.getLogger(ServiceHandlerExceptionResolver.class);
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) {
e.printStackTrace();
Throwable rex = e;
if (rex instanceof CIBaseException) {
this.writeError(((CIBaseException) rex).getErrCode(), rex.getMessage(), httpServletResponse);
} else {
this.writeError("", "系统运行出错", httpServletResponse);
}
return new ModelAndView();
}
}
@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
//需要在MVCConfiguration中初始化异常处理器
@Override
public void configureHandlerExceptionResolvers(List exceptionResolvers) {
exceptionResolvers.add(new ServiceHandlerExceptionResolver());
}
}
使用@ControllerAdviceannotation类里定义@ExceptionHandler, 对项目中所有Controller中带有@ExceptionHandler注释的方法拦截并进行处理异常
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(SQLException.class)
public String handleSQLException(HttpServletRequest request, Exception ex){
logger.info("SQLException Occured:: URL="+request.getRequestURL());
return "database_error";
}
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="IOException occured")
@ExceptionHandler(IOException.class)
public void handleIOException(){
logger.error("IOException handler executed");
//returning 404 error code
}
/** 输入参数仅有exception
@ExceptionHandler
public String handleException(UserNotFoundException exception) {
return "errorView";
}
*/
}
本小节以实际项目为例,说明在Spring Cloud项目架构下自定义异常并统一处理异常的机制。本项目的服务结构如图所示:
首先,项目统一定义了基础异常类,并根据异常类型及业务分类定义了错误代码编号。所有的Internal Service层的服务对于可预见的异常都包装成系统基础异常,其他异常可以原样抛出。具体的异常类定义如下:
public class CIBaseException extends RuntimeException {
private static final long serialVersionUID = *****;
public static final int UNKNOWN_EXCEPTION = 0;
public static final int BIZ_EXCEPTION = 1;
public static final int TIMEOUT_EXCEPTION = 2;
public static final int FORBIDDEN_EXCEPTION = 3;
public static final int CISYS_EXCEPTION = 4;
private int typecode = BIZ_EXCEPTION;// CIBaseException,异常类型用tpecode表示
private String errCode;
public CIBaseException() {
super();
}
public CIBaseException(String message, Throwable cause) {
super(message, cause);
}
public CIBaseException(String message) {
super(message);
}
public CIBaseException(Throwable cause) {
super(cause);
}
public CIBaseException(int type, String errCode) {
super();
this.typecode = type;
this.errCode = errCode;
}
public CIBaseException(int type, String errCode, String message, Throwable cause) {
super(message, cause);
this.typecode = type;
this.errCode = errCode;
}
public CIBaseException(int type, String errCode, String message) {
super(message);
this.typecode = type;
this.errCode = errCode;
}
public CIBaseException(int type, String errCode, Throwable cause) {
super(cause);
this.typecode = type;
this.errCode = errCode;
}
...
}
系统在Open Service层的服务中定义了一个CustomerErrorDecoder实现了ErrorDecoder接口处理调用远程服务产生的异常(若在应用中配置了ErrorDecoder的Bean,则Spring Cloud Netflix会在创建服务客户端时在应用上下文里查找该bean以处理服务调用异常获取到的异常)。具体来说ErrorDecoder Bean主要是在Decode方法中从response中获取到异常信息,将所有远程服务产生的业务异常都还原成CIBaseException的业务异常,并把其他类型的异常也包装成提示信息友好异常。
public Exception decode(String s, Response response) {
CIBaseException ex = null;
try {
if(response.body() != null) {
String body = Util.toString(response.body().asReader());
logger.error(body);
ExceptionInfo ei = this.objectMapper.readValue(body.getBytes("UTF-8"), ExceptionInfo.class);
String message = ei.getMessage();
String code = "";
if (message.matches(this.errCodePattern)) {
String [] cm = this.parseCodeMessage(message);
code = cm[0];
message = cm[1];
ex = new CIBaseException(CIBaseException.BIZ_EXCEPTION, code, message);
}
}
} catch (IOException var4) {
var4.printStackTrace();
ex = new CIBaseException(CIBaseException.UNKNOWN_EXCEPTION, "", "系统运行异常");
}
return null != ex ? ex : new CIBaseException(CIBaseException.UNKNOWN_EXCEPTION, "", "系统运行异常");
}
在Open Service层的服务中定义统一的Controller层错误处理类,解析异常对象,转化成友好的异常提示写入Response返回给前端页面进行展示,resoveException方法如下:
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
e.printStackTrace();
Throwable rex = e;
if (e instanceof HystrixRuntimeException) {
rex = e.getCause();
}
if (rex instanceof CIBaseException) {
CIBaseException cbe = (CIBaseException) rex;
if (StringUtils.isBlank(cbe.getErrCode())
&& cbe.getMessage().matches(this.errCodePattern)) {
String [] cm = this.parseCodeMessage(cbe.getMessage());
this.writeError(cm[0], cm[1], httpServletResponse);
} else {
this.writeError(cbe.getErrCode(), rex.getMessage(), httpServletResponse);
}
} else {
this.writeError("", "系统运行出错", httpServletResponse);
}
return new ModelAndView();
}
系统统一异常处理框架会捕获异常,对于需要处理的异常。 程序开发过程中根据逻辑可以创建异常,必要时转换成可描述清楚异常发生时的业务场景的异常抛出。 需要注意的是不要打印异常,统一异常处理框架会打印异常,避免重复打印。 自定义异常类的使用需要注意以下三点:
private int countHighScore(String score){
int counter = 0;
try{
float fscore = Float.valueOf(score);
....
}catch(NumberFormatException nex){
//注意:此处传入原生异常
throw new CIBaseException(ExceptionConstants.DEMO_SCORE_FORMAT_INVALID,nex);//输入参数格式错误!
}
return counter;
}
未传入原生异常,会丢失具体异常信息,如下所示:
com.isoftstone.cityinsight.exception.CIBaseException: 0X001001=输入参数格式错误!
at com.isoftstone.ci.user.web.controller.DemoUserController.countHighScore(DemoUserController.java:73)
at com.isoftstone.ci.user.web.controller.DemoUserController.userTest(DemoUserController.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
包含原生异常的完整信息,如下所示:
java.lang.NumberFormatException: For input string: "ww"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseFloat(FloatingDecimal.java:122)
at java.lang.Float.parseFloat(Float.java:451)
at java.lang.Float.valueOf(Float.java:416)
at com.isoftstone.ci.user.web.controller.DemoUserController.countHighScore(DemoUserController.java:71)
at com.isoftstone.ci.user.web.controller.DemoUserController.userTest(DemoUserController.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
/*找到某人的基本信息,并且根据基本信息计算分析其专业能力分值*/
public Score countScoreByUserId(String userid){
CIUser user = userservice.getById(userid);
if (user == null) {
//注意:此处加入了运行时数据信息
throw new CIBaseException(ExceptionConstants.DEMO_USER_NOT_FOUND+", userid:"+userid);//该用户不存在
}
Score score = scoreservice.countScore(user);
return score;
}
public String readfile(String filePath) {
FileInputStream fileInputStream = null;
StringBuffer sb=new StringBuffer();
String tempstr=null;
try{
File file = new File(filePath);
fileInputStream = new FileInputStream(file);
BufferedReader br=new BufferedReader(new InputStreamReader(fis));
while((tempstr=br.readLine())!=null)
sb.append(tempstr);
}catch(FileNotFoundException fex){
throw new CIBaseException("ExceptionConstants.DEMO_LICENCEFILE_NOT_FOUND"),e);//文件不存在
}catch(IOException e){
throw new CIBaseException("ExceptionConstants.DEMO_LICENCEFILE_IOEXCEPTION),e);//IO异常
}finally{
try{
//关闭资源()
if(fileInputStream!=null){
fileInputStream.close();
}
}catch(IOException ioex){
throw new CIBaseException("0X01C001=".concat("读Licens文件失败(关闭资源失败..)!"),e);
}
}
return sb.toString();
}