spring boot 提供了统一的异常处理机制,@ControllerAdvice, @ExceptionHandler两个注解,可以处理controller里抛出的异常,不要对每个controller方法进行try catch,如果你使用json或者返回固定的错误页面作为传输数据格式,普通的使用方法即可解决,由于我们服务端使用protobuf与客户端进行交互,每个controller方法的返回类型的泛型都是不固定的,于是做了一定的扩展,以适应每个controller的返回:
/**
* 全局异常处理
* Created by mxl on 2016/9/2.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static volatile Map<String, HandlerMapping> matchingBeans = null;
private static volatile ApplicationContext context = null;
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 获取map
* @return
*/
public static Map<String, HandlerMapping> getMatchingBeans (HttpServletRequest request) {
if (matchingBeans != null) {
return matchingBeans;
}
synchronized (GlobalExceptionHandler.class) {
if (matchingBeans == null) {
ApplicationContext context = getContext(request);
matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
}
return matchingBeans;
}
}
/**
* 获取context
* @param request
* @return
*/
public static ApplicationContext getContext (HttpServletRequest request) {
if (context != null) {
return context;
}
synchronized (GlobalExceptionHandler.class) {
if (context == null) {
context = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
}
return context;
}
}
@ExceptionHandler(value = GenericException.class)
public ResponseEntity<?> GenericExceptionHandler(HttpServletRequest request, GenericException genericException) {
try {
HandlerMethod handlerMethod = null;
//根据request请求解析出目标方法
Map<String, HandlerMapping> matchingBeans = getMatchingBeans(request);
for (HandlerMapping hm : matchingBeans.values()) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
handlerMethod = (HandlerMethod)handler.getHandler();
break;
}
}
//根据目标方法,根据反射,获取controller方法返回的protobuf对应的builder,并填装固定的异常返回格式
MessageOrBuilder builder = getBuilder(getReturnType(handlerMethod));
if (builder == null) {
return null;
}
return createResult(builder, genericException);
} catch (Exception ee) {
ee.printStackTrace();
}
return null;
}
@ExceptionHandler(value = Exception.class)
public ResponseEntity<?> defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
try {
HandlerMethod handlerMethod = null;
//根据request请求解析出目标方法
Map<String, HandlerMapping> matchingBeans = getMatchingBeans(request);
for (HandlerMapping hm : matchingBeans.values()) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
handlerMethod = (HandlerMethod)handler.getHandler();
break;
}
}
//根据目标方法,根据反射,获取controller方法返回的protobuf对应的builder,并填装固定的异常返回格式
MessageOrBuilder builder = getBuilder(getReturnType(handlerMethod));
return createResult(builder, new OperateFailureException("系统异常", Constants.Code.SERVER_ERROR));
} catch (Exception ee) {
ee.printStackTrace();
logger.error(ee.getMessage());
}
return null;
}
//利用反射获取填充builder
private ResponseEntity<?> createResult (MessageOrBuilder messageOrBuilder, GenericException e) {
try {
Method successMethod = messageOrBuilder.getClass().getDeclaredMethod("setSuccess", boolean.class);
successMethod.invoke(messageOrBuilder, false);
Method msgMethod = messageOrBuilder.getClass().getDeclaredMethod("setMsg", String.class);
msgMethod.invoke(messageOrBuilder, e.getMessage());
Method codeMethod = messageOrBuilder.getClass().getDeclaredMethod("setCode", int.class);
codeMethod.invoke(messageOrBuilder, e.getErrorCode());
Method builderMethod = messageOrBuilder.getClass().getDeclaredMethod("build");
return ResponseEntity.ok(builderMethod.invoke(messageOrBuilder));
} catch (Exception ee) {
e.printStackTrace();
logger.error(ee.getMessage());
}
return null;
}
/**
* 获取builder
* @param returnType
* @return
*/
public static MessageOrBuilder getBuilder (Type returnType) {
try {
Class returnClass = Class.forName(returnType.getTypeName());
Method builderMethod = returnClass.getDeclaredMethod("newBuilder");
return (MessageOrBuilder)builderMethod.invoke(returnClass);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 返回类型
* @param handlerMethod
* @return
*/
public static Type getReturnType (HandlerMethod handlerMethod) {
Type type = handlerMethod.getMethod().getAnnotatedReturnType().getType();
if (type instanceof ParameterizedType)/* 如果是泛型类型 */{
Type[] types = ((ParameterizedType) type)
.getActualTypeArguments();// 泛型类型列表
return types[0];
}
return null;
}
}
我们controller里是这样做返回的:
@RequestMapping(value = "creative/list", method = RequestMethod.POST)
public ResponseEntity<FeedResponse> listCreative(RequestEntity<FeedRequest> requestEntity) {
FeedResponse.Builder builder = FeedResponse.newBuilder();
FeedRequest feed = requestEntity.getBody();
PaginationParam paginationParam = new PaginationParam(feed.getStartCount(), feed.getMaxCount() + 1);
SortParam sortParam;
if (feed.hasOrderColumn() && feed.hasSort()) {
sortParam = PagiSortUtils.createSortParam(feed.getOrderColumn(), feed.getSort());
} else {
sortParam = new SortParam("update_time", "desc");
}
List<Creative> creativeList = creativeService.findByUser(feed.getUid(), sortParam, paginationParam, PubStatus.DELETE.value);
builder.addAllDataList(
convertCreativeFeedList(creativeList.subList(0, Math.min(creativeList.size(), feed.getMaxCount())), true,
null));
builder.setSuccess(true).setCode(Constants.Code.OK).setHasMore(creativeList.size() > feed.getMaxCount());
return ResponseEntity.ok(builder.build());
}
扩展的具体思路是, 异常捕捉方法中暴露了两个参数request, exception,扩展点在于如何获取每个controller方法返回值的泛型,以创建具体的protobuf返回到客户端, spring boot有一个SimpleMappingExceptionResolver可以扩展,重新此方法, 此方法暴露了HandlerMethod,但是返回格式固定为ModelAndView了,不符合我们的要求;使用主机的方式,去解析request,获取目标方法handlerMethod, 然后利用反射获取目标方法的返回值类型,然后创建返回值类型,做出返回值构建,返回