SSM 统一异常处理机制:从 SpringMVC 到业务层的异常捕获与返回
各位看官,大家好!今天咱们来聊聊一个在 SSM 项目中至关重要,但又容易被忽视的话题:统一异常处理。想象一下,你的用户正在兴致勃勃地使用你的应用,突然,页面上蹦出一个丑陋的、难以理解的错误信息,或者更糟糕,直接白屏了。这感觉是不是像在约会时,对方突然打了个惊天动地的饱嗝一样尴尬?
所以,一个好的异常处理机制,就像一位优雅的绅士,能够妥善地处理各种突发情况,给用户一个友好的提示,而不是让他们丈二和尚摸不着头脑。
今天,我们就来深入探讨如何在 SSM(SpringMVC + Spring + MyBatis)项目中构建一个统一的、优雅的异常处理机制,让你的应用在面对错误时也能保持体面。
为什么要统一异常处理?
在没有统一异常处理的情况下,通常会有以下问题:
- 代码冗余: 每个 Controller 方法都可能需要 try-catch 块来处理异常,导致代码重复。
- 错误信息不一致: 不同地方的异常处理方式可能不同,导致用户看到的错误信息格式不一致,体验糟糕。
- 难以维护: 如果需要修改错误处理逻辑,需要在多个地方进行修改,容易出错。
- 安全性问题: 某些未处理的异常可能会暴露敏感信息,例如数据库连接字符串等。
统一异常处理就像一个消防队,负责处理整个应用中出现的各种“火情”,将错误信息统一格式化,并返回给用户,从而提高应用的健壮性和用户体验。
如何实现统一异常处理?
在 SSM 项目中,我们可以通过以下几种方式实现统一异常处理:
- SpringMVC 的 HandlerExceptionResolver
- @ControllerAdvice 和 @ExceptionHandler
- 自定义异常类
下面我们来逐一介绍这些方法,并结合代码示例进行讲解。
1. SpringMVC 的 HandlerExceptionResolver
HandlerExceptionResolver
是 SpringMVC 提供的一个接口,允许我们自定义异常解析器,将异常转换为 ModelAndView 对象,从而控制错误页面的展示。
实现步骤:
- 创建一个类实现
HandlerExceptionResolver
接口。 - 实现
resolveException
方法,在该方法中处理异常,并返回ModelAndView
对象。 - 在 SpringMVC 的配置文件中注册该异常解析器。
代码示例:
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GlobalExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
// 记录日志
System.err.println("发生异常: " + ex.getMessage());
// 根据不同的异常类型,返回不同的错误页面
if (ex instanceof NullPointerException) {
mv.setViewName("error/500");
mv.addObject("errorMessage", "空指针异常,请联系管理员");
} else if (ex instanceof IllegalArgumentException) {
mv.setViewName("error/400");
mv.addObject("errorMessage", "参数错误,请检查输入");
} else {
mv.setViewName("error/default");
mv.addObject("errorMessage", "服务器发生未知错误,请稍后再试");
}
return mv;
}
}
SpringMVC 配置文件 (applicationContext.xml):
<bean id="globalExceptionHandler" class="com.example.GlobalExceptionHandler"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="handlerExceptionResolvers">
<list>
<ref bean="globalExceptionHandler"/>
</list>
</property>
</bean>
优点:
- 可以自定义错误页面,方便用户体验。
缺点:
- 需要配置 XML 文件,相对繁琐。
- 无法直接返回 JSON 格式的错误信息。
- 需要手动处理各种异常类型,代码可读性较差。
2. @ControllerAdvice 和 @ExceptionHandler
@ControllerAdvice
和 @ExceptionHandler
是 Spring 3.2 引入的注解,提供了一种更简洁、更灵活的异常处理方式。
实现步骤:
- 创建一个类,使用
@ControllerAdvice
注解标记。 - 在该类中创建方法,使用
@ExceptionHandler
注解标记,并指定要处理的异常类型。 - 在
@ExceptionHandler
注解标记的方法中处理异常,并返回合适的响应。
代码示例:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleNullPointerException(NullPointerException ex) {
System.err.println("空指针异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 500);
response.put("message", "空指针异常,请联系管理员");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex) {
System.err.println("参数错误: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", "参数错误,请检查输入");
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
System.err.println("未知异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 500);
response.put("message", "服务器发生未知错误,请稍后再试");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
优点:
- 代码简洁,易于阅读和维护。
- 可以灵活地返回不同格式的响应,例如 JSON、XML 等。
- 无需配置 XML 文件。
缺点:
- 需要手动处理各种异常类型,仍然存在代码冗余。
3. 自定义异常类
为了更好地组织和管理异常,我们可以自定义异常类,将异常信息封装到自定义异常中。
实现步骤:
- 创建一个自定义异常类,继承自
RuntimeException
或Exception
。 - 在自定义异常类中定义错误码和错误信息。
- 在业务层抛出自定义异常。
- 在
@ControllerAdvice
中处理自定义异常。
代码示例:
自定义异常类:
public class BusinessException extends RuntimeException {
private int code;
private String message;
public BusinessException(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
业务层代码:
public void doSomething(String input) {
if (input == null || input.isEmpty()) {
throw new BusinessException(1001, "输入不能为空");
}
// ... 其他业务逻辑
}
@ControllerAdvice
代码:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleBusinessException(BusinessException ex) {
System.err.println("业务异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", ex.getCode());
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
System.err.println("未知异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 500);
response.put("message", "服务器发生未知错误,请稍后再试");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
优点:
- 代码结构清晰,易于维护。
- 可以自定义错误码和错误信息,方便前端进行处理。
- 可以将异常处理逻辑与业务逻辑分离,提高代码的可读性。
缺点:
- 需要定义更多的类。
异常处理的最佳实践
结合以上三种方式,我们可以总结出一些异常处理的最佳实践:
- 使用
@ControllerAdvice
和@ExceptionHandler
进行统一异常处理。 - 自定义异常类,封装错误码和错误信息。
- 在业务层抛出自定义异常。
- 在
@ControllerAdvice
中处理自定义异常和通用异常。 - 记录异常日志,方便排查问题。
- 返回统一格式的响应,方便前端进行处理。
- 对用户友好的错误提示,避免暴露敏感信息。
完整的示例代码
下面是一个完整的示例代码,展示了如何使用 @ControllerAdvice
、@ExceptionHandler
和自定义异常类来实现统一异常处理。
1. 自定义异常类:
public class BusinessException extends RuntimeException {
private int code;
private String message;
public BusinessException(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
2. Service 层:
import org.springframework.stereotype.Service;
@Service
public class MyService {
public String getData(String id) {
if (id == null || id.isEmpty()) {
throw new BusinessException(1001, "ID 不能为空");
}
if (id.equals("123")) {
return "Data for ID 123";
} else {
throw new BusinessException(1002, "找不到对应的数据");
}
}
}
3. Controller 层:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
private MyService myService;
@GetMapping("/data/{id}")
public String getData(@PathVariable String id) {
return myService.getData(id);
}
}
4. GlobalExceptionHandler:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleBusinessException(BusinessException ex) {
System.err.println("业务异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", ex.getCode());
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
System.err.println("未知异常: " + ex.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("code", 500);
response.put("message", "服务器发生未知错误,请稍后再试");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
5. 效果:
- 如果访问
/data/
(没有 ID),会返回:
{
"code": 1001,
"message": "ID 不能为空"
}
- 如果访问
/data/abc
(ID 不存在),会返回:
{
"code": 1002,
"message": "找不到对应的数据"
}
- 如果访问
/data/123
(ID 存在),会返回:
Data for ID 123
总结
统一异常处理是构建健壮、用户友好的应用的关键。通过使用 SpringMVC 的 HandlerExceptionResolver
、@ControllerAdvice
和 @ExceptionHandler
,以及自定义异常类,我们可以实现一个优雅、可维护的异常处理机制。
希望这篇文章能帮助你更好地理解和应用统一异常处理,让你的应用在面对错误时也能保持体面和优雅。
记住,一个好的异常处理机制,就像一位优秀的管家,能够妥善地处理各种突发情况,让你的用户感到安心和舒适。
好了,今天的分享就到这里,我们下期再见!