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);
    }

}