spring的异常处理 | 全局异常处理
好处:代码中不必写大量的try…catch,可以 throw 直接抛出异常,由全局异常统一处理。
严格来说不算全局异常处理,只是拦截处理controller抛出的异常。
@RestControllerAdvice // =@ControllerAdvice+@ResponseBody,如果使用模板引擎、需要跳转到错误页面,换成@ControllerAdvice、返回String或者ModeAndView即可
public class GlobalExceptionHandler {
@ExceptionHandler(AxxException.class) //指定要捕获处理的异常种类,Exception.class即处理所有种类的异常
public Map<String, String> handler(AxxException e) { //会自动传入异常对象
Map<String, String> map = new HashMap<>(4);
map.put("code", "xxx");
map.put("msg", e.getMessage());
return map;
}
@ExceptionHandler(BxxException.class)
public Map<String, String> handler(BxxException e) {
Map<String, String> map = new HashMap<>(4);
map.put("code", "xxx");
map.put("msg", e.getMessage());
return map;
}
//.....
}
json转换
java中常见的json类库
- json官方:使用十分麻烦
- gson:谷歌开源的,使用略微麻烦
- fastjson:阿里开源的,使用方便,性能极高,但容易出bug
- jackson:springmvc内置的json转换器,使用方便,成熟稳定
如果controller的方法要返回json数据,可以使用
- @ResponseBody:可以加在方法上,也可以直接加在类上(所有方法都返回json数据)
- @RestController:等于@Controller+@ResponseBody
jackson常用注解
@Data
@AllArgsConstructor
@NoArgsConstructor
// @JsonInclude(JsonInclude.Include.NON_NULL) //不序列化实例中值为null的字段
public class User implements Serializable {
private Integer id;
@JsonProperty("nickname") //序列化时使用的字段名
private String username;
@JsonIgnore //序列化时忽略此字段
private String password;
private String tel;
private String address;
@JsonFormat(pattern = "yyyy-MM-dd") //指定序列化格式
private Date birthday;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
- @DateTimeFormat有一些毛病,尽量用@JsonFormat代替@DateTimeFormat。
- @JsonFormat用得较多,但只能指定一种格式,如果系统要和多个日期时间格式不同的系统对接,可以编写自定义的时间日期类型的json解码器,按照自定义的解码规则将网络IO中的二进制json数据解析为指定类型的数据,在Date类型的字段上用@JsonDeserialize指定要使用的json解码器。
jackson自定义序列化、反序列化
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* jackson工具类
*/
public class JacksonUtils {
/**
* jackson的核心操作类
*/
private static ObjectMapper mapper;
/**
* 初始化mapper,可以在此统一设置序列化配置,eg. 不序列化值为null的字段
*/
static {
mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
/**
* 将java对象序列化为json字符串
*
* @param object java对象
* @return json字符串
* @throws JsonProcessingException
*/
public static String object2JsonStr(Object object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
/**
* 将json字符串反序列化得到java对象
*
* @param jsonStr json字符串
* @param clazz 目标类的Class对象。数组示例 User[].class。如果要转为List,可以先转为数组,再用Arrays.asList()转为List
* @return 目标类型的java对象
* @throws JsonProcessingException
*/
public static <T> T jsonStr2Object(String jsonStr, Class<T> clazz) throws JsonProcessingException {
return mapper.readValue(jsonStr, clazz);
}
}
ObjectMapper是线程安全的,初始化时很耗时,尽量让ObjectMapper只有一个实例。
文件的上传、下载
上传
pom.xml
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
前端表单
<form action="${pageContext.request.contextPath}/fileUpload" method="post" enctype="multipart/form-data">
选择文件:<input name="uploadFile" type="file" multiple /><br />
<button type="submit">上传</button>
</form>
multiple用于文件多选,不使用multiple则只能选择一个文件
后端controller
/**
* 处理上传文件
*/
@org.springframework.stereotype.Controller
public class FileUploadController{
@RequestMapping("/fileUpload")
public String fileUpload(@RequestParam("uploadFile") List<MultipartFile> fileList, HttpServletRequest request) {
//如果用户上传了文件
if (!fileList.isEmpty() && fileList.size()>0){
System.out.println(fileList.isEmpty());
System.out.println(fileList.size());
//设置保存路径为项目根目录下的upload文件夹
String savePath = request.getServletContext().getRealPath("/upload");
//不存在就新建
File saveDir = new File(savePath);
if (!saveDir.exists()){
saveDir.mkdirs();
}
//循环读取上传文件并保存
for (MultipartFile file:fileList){
//原文件名
String originalFilename = file.getOriginalFilename();
//使用uuid防止文件重名,因为原文件名中包含扩展名,只能放最后面
String newFilename= UUID.randomUUID()+"_"+originalFilename;
System.out.println(originalFilename);
//将临时文件保存至指定目录
try {
file.transferTo(new File(saveDir+"/"+newFilename));
} catch (IOException e) {
e.printStackTrace();
return "error";
}
}
return "success";
}
//如果用户未上传文件,返回error
return "error";
}
}
springmvc用MultipartFile来封装上传文件,一个MultipartFile对应一个上传文件
如果是springboot,在yml中配置允许的请求最大体积、上传文件的最大体积;如果是ssm可以在spring-mvc.xml进行配置
<!-- 配置MultipartResolver,bean的id或name必须为multipartResolver -->
<bean name="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 上传文件使用的编码字符集-->
<property name="defaultEncoding" value="utf-8" />
<!-- 所允许上传文件的最大尺寸,单位字节-->
<property name="maxUploadSize" value="10485760" />
</bean>
下载
前端传递文件名
<a href="/download?filename=xxxxxxx.jpg">下载文件</a>
后端controller
/**
* 处理文件下载
*/
@org.springframework.stereotype.Controller
public class DownloadController{
@RequestMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest request, String filename) throws IOException {
//指定存放文件的路径,/表示项目根目录
String dir=request.getServletContext().getRealPath("/files");
File file = new File(dir + "/" + filename);
//设置响应头
HttpHeaders httpHeaders = new HttpHeaders();
//通知浏览器以下载的方式处理,第二个参数指定保存为的文件名。文件名需要utf-8编码,不然中文文件名往往会乱码
httpHeaders.setContentDispositionFormData("attachment", URLEncoder.encode(filename,"utf-8"));
//以流的形式返回
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//读取目标文件
byte[] arr = FileUtils.readFileToByteArray(file);
//创建ResponseEntity对象并返回。目标文件的byte[]、HttpHeaders、Http状态码
return new ResponseEntity<>(arr, httpHeaders, HttpStatus.OK);
}
}
后端响应封装
枚举封装错误码、提示信息
public enum ResultCodeMsgEnum {
操作成功("0000", "操作成功"),
未登录("1001", "未登录"),
无访问权限("1002", "无访问权限"),
请求参数错误("1003", "请求参数错误"),
验证码错误("2001", "验证码错误"),
用户名不存在("2002", "用户名不存在"),
用户名或密码错误("2003", "用户名或密码错误"),
操作超时("3001", "服务端操作超时"),
服务端错误("3002", "服务端错误"),
未知错误("9999", "未知错误");
private String code;
private String msg;
ResultCodeMsgEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return this.code;
}
public String getMsg() {
return this.msg;
}
}
可以继承HashMap
public class Result extends HashMap<String, Object> {
public Result() {
super();
}
public Result(String code, String msg) {
this.put("code", code);
this.put("msg", msg);
}
public Result(String code, String msg, Object data) {
this.put("code", code);
this.put("msg", msg);
this.put("data", data);
}
public static Result success() {
return new Result("200", "操作成功");
}
public static Result success(String msg) {
Result result = new Result("200", msg);
return result;
}
public static Result success(String msg, Object data) {
return new Result("200", msg, data);
}
public static Result error() {
return new Result("500", "未知异常,请联系管理员");
}
public static Result error(String msg) {
return new Result("500", msg);
}
public static Result error(String msg, Object data) {
return new Result("500", msg, data);
}
}
也可以自己写
@Data
public class Result {
private String code;
private String msg;
private Object data;
public Result() {
}
public Result(String code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(String code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static Result success() {
return new Result("200", "操作成功");
}
public static Result success(String msg) {
Result result = new Result("200", msg);
return result;
}
public static Result success(String msg, Object data) {
return new Result("200", msg, data);
}
public static Result error() {
return new Result("500", "未知异常,请联系管理员");
}
public static Result error(String msg) {
return new Result("500", msg);
}
public static Result error(String msg, Object data) {
return new Result("500", msg, data);
}
}