一、全局异常处理
代码示例如下:
/**
* @author qinxun
* @date 2023-06-14
* @Descripion: 业务层异常枚举
*/
public enum ServiceExceptionEnum {
SUCCESS(0, "成功"),
ERROR(1, "失败"),
SYS_ERROR(1000, "服务端发生异常"),
MISSING_REQUEST_PARAM_ERROR(1001, "参数缺失"),
INVALID_REQUEST_PARAM_ERROR(1002, "请求参数不合法");
private final String message;
private final int code;
ServiceExceptionEnum(int code, String message) {
this.code = code;
this.message = message;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
}
import com.example.quartzdemo.enums.ServiceExceptionEnum;
import java.io.Serializable;
/**
* @author qinxun
* @date 2023-06-14
* @Descripion: 统一返回结果实体类
*/
public class CommonResult<T> implements Serializable {
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> CommonResult<T> success(T data) {
CommonResult<T> commonResult = new CommonResult<>();
commonResult.setCode(ServiceExceptionEnum.SUCCESS.getCode());
commonResult.setMessage(ServiceExceptionEnum.SUCCESS.getMessage());
commonResult.setData(data);
return commonResult;
}
/**
* 失败
*
* @param message
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(String message) {
CommonResult<T> commonResult = new CommonResult<>();
commonResult.setCode(ServiceExceptionEnum.ERROR.getCode());
commonResult.setMessage(message);
return commonResult;
}
/**
* 失败
*
* @param message
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(int code, String message) {
CommonResult<T> commonResult = new CommonResult<>();
commonResult.setCode(code);
commonResult.setMessage(message);
return commonResult;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "CommonResult{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
全局异常处理类:
import com.example.quartzdemo.common.CommonResult;
import com.example.quartzdemo.enums.ServiceExceptionEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
/**
* @author qinxun
* @date 2023-06-14
* @Descripion: 全局异常处理
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理 MissingServletRequestParameterException 异常
* <p>
* SpringMVC 参数不正确
*/
@ResponseBody
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult<String> missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
log.error("[missingServletRequestParameterExceptionHandler]", ex);
// 包装 CommonResult 结果
return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
}
@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult<String> constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
log.error("[constraintViolationExceptionHandler]", ex);
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
// 使用 ; 分隔多个错误
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
// 拼接内容到其中
detailMessage.append(constraintViolation.getMessage());
}
// 包装 CommonResult 结果
return CommonResult.error(
ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
}
/**
* 处理参数校验异常
*
* @param req
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = BindException.class)
public CommonResult<String> bindExceptionHandler(HttpServletRequest req, BindException ex) {
log.info("========进入了 bindException======");
log.error("[bindExceptionHandler]", ex);
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ObjectError objectError : ex.getAllErrors()) {
// 使用 ; 分隔多个错误
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
// 拼接内容到其中
detailMessage.append(objectError.getDefaultMessage());
}
// 包装 CommonResult 结果
return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
}
/**
* 处理参数校验异常
*
* @param req
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public CommonResult<String> MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
log.info("-----------------进入了 MethodArgumentNotValidException-----------------");
log.error("[MethodArgumentNotValidException]", ex);
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
// 使用 ; 分隔多个错误
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
// 拼接内容到其中
detailMessage.append(objectError.getDefaultMessage());
}
// 包装 CommonResult 结果
return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
}
/**
* 处理其它 Exception 异常
*
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
public CommonResult<String> exceptionHandler(HttpServletRequest req, Exception e) {
// 记录异常日志
log.error("[exceptionHandler]", e);
// 返回 ERROR CommonResult
return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
ServiceExceptionEnum.SYS_ERROR.getMessage());
}
}
二、全局数据绑定
全局数据绑定可以用来做一些初始化数据的操作,我们可以将一些公共的数据定义到添加了@ControllerAdvice注解的类中,这样我们就可以在每一个Controller中可以访问这些数据。
1.定义全局数据绑定类
import java.util.HashMap;
import java.util.Map;
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: 全局数据绑定
*/
@ControllerAdvice
public class GlobalDataBindHandler {
@ModelAttribute(name = "gd")
public Map<String, Object> globalData() {
Map<String, Object> map = new HashMap<>();
map.put("website", "https://www.xx.com/");
map.put("email", "xxxx@qq.com");
return map;
}
}
使用@ModelAttribute注解标记该方法返回的是一个全局数据。
2.我们在Controller类中使用这个全局数据
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: 全局数据绑定测试
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String toHello(Model model) {
Map<String, Object> map = model.asMap();
// 输出 {map={website=https://www.xx.com/, email=xxxx@qq.com}}
System.out.println(map);
return "hello";
}
}
三、全局数据预处理
1.我们准备两个实体类
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: Author实体
*/
public class Author {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Author{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: Book实体
*/
public class Book {
private String name;
private Integer price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
我们定义一个访问接口
import com.example.quartzdemo.bean.Author;
import com.example.quartzdemo.bean.Book;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: 测试
*/
@RestController
public class TestController {
/**
* 测试
*
* @param book Book对象
* @param author Author对象
*/
@PostMapping("/book")
public void addBook(Book book, Author author) {
System.out.println(book);
System.out.println(author);
}
}
这个时候,我们的添加操作就会有问题,因为这两个实体类中都有一个name的属性,前端传递数据的时候,无法区分是哪个实体的name属性,这种情况我们可以通过@ControllerAdvice的全局数据预处理来解决这个问题。
我们在postman上进行测试
控制台打印返回
Book{name='书名,作者', price=20}
Author{name='书名,作者', age=20}
发现数据混乱了,这不是我们需要的结果。
如何解决?
1.给接口中的变量取别名
import com.example.quartzdemo.bean.Author;
import com.example.quartzdemo.bean.Book;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author qinxun
* @date 2023-06-02
* @Descripion: 测试
*/
@RestController
public class TestController {
/**
* 测试
*
* @param book Book对象
* @param author Author对象
*/
@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
System.out.println(book);
System.out.println(author);
}
}
2.全局数据处理类
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.HashMap;
import java.util.Map;
/**
* @author qinxun
* @date 2023-06-15
* @Descripion: 全局数据处理
*/
@ControllerAdvice
public class GlobalDataBindHandler {
// 全局数据绑定
@ModelAttribute
public Map<String, Object> globalData() {
Map<String, Object> map = new HashMap<>();
map.put("website", "https://www.xx.com/");
map.put("email", "xxxx@qq.com");
return map;
}
// 对命名为b的全局数据预处理
@InitBinder("b")
public void b(WebDataBinder binder) {
binder.setFieldDefaultPrefix("b.");
}
// 对命名为a的全局数据预处理
@InitBinder("a")
public void a(WebDataBinder binder) {
binder.setFieldDefaultPrefix("a.");
}
}
@InitBinder("b")表示该方法用来处理和Book相关的参数,给参数添加一个b前缀,要求参数必须有b前缀。
3.postman测试
控制台打印返回
Book{name='书名', price=20}
Author{name='作者', age=20}