前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >优雅的异常处理方式

优雅的异常处理方式

作者头像
周三不加班
发布2019-09-03 10:26:08
7070
发布2019-09-03 10:26:08
举报
文章被收录于专栏:程序猿杂货铺程序猿杂货铺

问题

文章的开始先探讨两个问题:

1、在springboot项目里我们一般都希望调用接口时返回的数据包含至少以下三个属性:

  • code:请求接口的返回码,成功或者异常等返回编码,例如定义请求成功,code = "0000",查询结果为null,code = "0001";
  • msg:请求接口的描述,也就是对返回编码的描述,"0000":就表示请求成功,"0001":表示结果为null;
  • data:请求接口成功,返回的结果。

如下代码所示:

代码语言:javascript
复制
{
    "data": {
        "id": ,
        "studentId": "13240115",
        "name": "Tiger",
        "age": ,
        "famillyAddress": "北京",
        "createdDate": "2018-10-08T05:45:49.000+0000",
        "updatedDate": "2018-10-09T03:15:33.000+0000"
},
"code": "0000",
"msg": "请求成功"

2、在springboot项目里请求结果失败之后,我们希望可以通过返回的错误码返回描述来告诉前端接口请求异常。

如下代码所示:

代码语言:javascript
复制
{
    "code": "0001",
    "msg": "学号不存在"
}

案例

建表

建一张学生信息表,包含学生的学号、姓名、年龄、家庭住址等

代码语言:javascript
复制
CREATE TABLE student_info (
  id bigint() NOT NULL AUTO_INCREMENT COMMENT '自增',
  student_id varchar() NOT NULL COMMENT '学号',
  name varchar() NOT NULL COMMENT '姓名',
  age int() NOT NULL COMMENT '年龄',
  familly_address varchar() NOT NULL COMMENT '家庭地址',
  created_date datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  updated_date datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (student_id),
  KEY id (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
引入包

pom文件如下:

代码语言:javascript
复制
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.43</version>
        </dependency>


        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

    </dependencies>
加入缓存(可以不需要)

使用redis进行缓存服务,关于redis的相关安装问题,鉴于篇幅,不在此进行赘述。可以参考这篇文章

https://blog.csdn.net/qq_31289187/article/details/82970960 进行安装使用。

创建实体类
代码语言:javascript
复制
package com.dl.cn.message.bean;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 * Created by Tiger on 2018/10/8.
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentInfo implements Serializable{

    private static final long serialVersionUID = 2597547944454691103L;

    private Long id;
    private String studentId;
    private String name;
    private Integer age;
    private String famillyAddress;
    private Date createdDate;
    private Date updatedDate;
}
创建Mapper
代码语言:javascript
复制
package com.dl.cn.message.mapper;

import com.dl.cn.message.bean.StudentInfo;
import org.apache.ibatis.annotations.*;

/**
 * Created by Tiger on 2018/10/8.
 */
@Mapper
public interface StudentInfoMapper {
    @Insert("insert into student_info(student_id,name,age,familly_address)" +
            " values(#{studentId},#{name},#{age},#{famillyAddress})")
    /**
     * 通过bean保存实体类是,建议不要通过@Param注解,负责实体类的属性都在@Param中找
     * */
    void saveStudentInfo(StudentInfo studentInfo);


    @Select("select * from student_info where student_id = #{studentId}")
    StudentInfo findByStudentId(@Param("studentId") String studentId);


    @Update("update student_info set familly_address = #{famillyAddress},updated_date = now() ")
    void updateFamillyAddress(@Param("studentId") String studentId,@Param("famillyAddress") String famillyAddress);
}
创建service
代码语言:javascript
复制
package com.dl.cn.message.service;

import com.dl.cn.message.bean.StudentInfo;
import com.dl.cn.message.mapper.StudentInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * Created by Tiger on 2018/10/8.
 */
@Service
@CacheConfig(cacheNames = "studentInfo")
@Slf4j
public class StudentInfoService {

    @Autowired
    StudentInfoMapper studentInfoMapper;

    /**
     * 保存学生信息
     * @param studentInfo
     * */
    public void saveStudentInfo(StudentInfo studentInfo){
        studentInfoMapper.saveStudentInfo(studentInfo);
    }

    /**
     * 根据学号查学生信息
     * @param studentId
     * @return
     * */
    @Cacheable(key = "#studentId",unless = "#result == null")
    public StudentInfo findByStudentId(String studentId){
        log.info("查找信息:{}",studentId);
        return studentInfoMapper.findByStudentId(studentId);
    }

    /**
     * 根据学号更新家庭地址
     * @param studentId
     * @param famillyAddress
     * */
    //删除对应key的缓存
    @CacheEvict(key = "#studentId")
    public void updateFamillyAddress(String studentId,String famillyAddress){
        studentInfoMapper.updateFamillyAddress(studentId,famillyAddress);
    }
}
创建统一返回结果实体类
代码语言:javascript
复制
package com.dl.cn.message.response;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
 * 请求返回实体
 * Created by Tiger on 2018/10/9.
 */
@Getter
@Setter
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class Response<T> implements Serializable {

    private static final long serialVersionUID = -4505655308965878999L;

    //请求成功返回码为:0000
    private static final String successCode = "0000";
    //返回数据
    private T data;
    //返回码
    private String code;
    //返回描述
    private String msg;

    public Response(){
        this.code = successCode;
        this.msg = "请求成功";
    }

    public Response(String code,String msg){
        this();
        this.code = code;
        this.msg = msg;
    }
    public Response(String code,String msg,T data){
        this();
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public Response(T data){
        this();
        this.data = data;
    }
}
创建异常编码和描述类:`StudentException`
代码语言:javascript
复制
package com.dl.cn.message.exception;

import com.dl.cn.message.enums.ErrorCodeAndMsg;

import java.io.Serializable;

/**
 * 异常编码和描述类
 * Created by Tiger on 2018/10/9.
 */
public class StudentException extends RuntimeException{

    private static final long serialVersionUID = -6370612186038915645L;

    private final ErrorCodeAndMsg response;

    public StudentException(ErrorCodeAndMsg response) {
        this.response = response;
    }
    public ErrorCodeAndMsg getResponse() {
        return response;
    }
}
创建异常处理的全局配置类:`ExceptionHandler`
代码语言:javascript
复制
package com.dl.cn.message.exception;

import com.dl.cn.message.enums.ErrorCodeAndMsg;

import java.io.Serializable;

/**
 * 异常处理的全局配置类
 * Created by Tiger on 2018/10/9.
 */
public class StudentException extends RuntimeException{

    private static final long serialVersionUID = -6370612186038915645L;

    private final ErrorCodeAndMsg response;

    public StudentException(ErrorCodeAndMsg response) {
        this.response = response;
    }
    public ErrorCodeAndMsg getResponse() {
        return response;
    }
}
创建controler:`StudentInofController`
代码语言:javascript
复制
package com.dl.cn.message.controller;

import com.dl.cn.message.enums.ErrorCodeAndMsg;
import com.dl.cn.message.exception.StudentException;
import com.dl.cn.message.response.Response;
import com.dl.cn.message.service.StudentInfoService;
import com.dl.cn.message.bean.StudentInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by Tiger on 2018/10/8.
 */
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentInofController {
    @Autowired
    StudentInfoService studentInfoService;

    /**
     * 保存学生信息
     * @param studentId
     * @param name
     * @param age
     * @param famillyAddress
     * */
    @PostMapping("/save")
    public void saveStudentInfo(@RequestParam("student_id") String studentId,
                                @RequestParam("name") String name,
                                @RequestParam("age") Integer age,
                                @RequestParam("familly_address") String famillyAddress){
        StudentInfo studentInfo = StudentInfo.builder()
                .studentId(studentId)
                .name(name)
                .age(age)
                .famillyAddress(famillyAddress)
                .build();
        studentInfoService.saveStudentInfo(studentInfo);
    }

    /**
     * 根据学号查学生信息
     * @param studentId
     * @return
     * */
    @PostMapping("/findByStudentId")
    public Response findByStudentId(@RequestParam("student_id") String studentId){
        try{
            log.info("Get student information based on student number:{}",studentId);
            if(studentId == null){
                throw new StudentException(ErrorCodeAndMsg.Student_number_is_empty);
            }
            //学号固定为8位
            if(studentId.length() != ){
                throw new StudentException(ErrorCodeAndMsg.Insufficient_student_number);
            }
            StudentInfo studentInfo = studentInfoService.findByStudentId(studentId);
            if(studentInfo == null){
                throw new StudentException(ErrorCodeAndMsg.Student_number_does_not_exist);
            }
            return new Response(studentInfo);
        }catch (Exception e){
            if(e instanceof StudentException){
                throw e;
            }else {
                log.error("findByStudentId error:",e);
                throw new StudentException(ErrorCodeAndMsg.Network_error);
            }
        }
    }

    @PostMapping("/updateFamillyAddress")
    public Response updateFamillyAddress(@RequestParam("student_id") String studentId,
                                     @RequestParam("familly_address") String famillyAddress){
        studentInfoService.updateFamillyAddress(studentId,famillyAddress);
        Response response = new Response();
        System.out.println(response.toString());
        return response;
    }
}
`application.properties`配置
代码语言:javascript
复制
#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=tiger

#mybatis
#开启mybatis驼峰命名,这样可以将mysql中带有下划线的映射成驼峰命名的字段
mybatis.configuration.map-underscore-to-camel-case=true

#datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tiger?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&generateSimpleParameterMetadata=true
spring.datasource.username=tiger
spring.datasource.password=tiger
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.max-idle=10
spring.datasource.max-wait=60000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validationQuery=select 'x'

总结

  • controller层使用注解@RestController,这样返回结果就是json格式,而@Controller返回结果是字符串
  • throw 异常,如果exception类型是自定义的异常StudentException,直接抛出,如果是其它异常统一抛出网络错误
代码语言:javascript
复制
try{

}catch (Exception e){
            if(e instanceof StudentException){
                throw e;
            }else {
                log.error("findByStudentId error:",e);
                throw new StudentException(ErrorCodeAndMsg.Network_error);
       }
}
  • 在返回结果类添加了注解@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL) 是因为更新或者删除操作,一般没有返回值,我只需要知道是否更新成功或者删除成功就OK了,如果不加这个注解,我们返回的结果中data为null!!! { "data": null, "code": "0000", "msg": "请求成功" } 加上注解之后结果会变成: { "code": "0000", "msg": "请求成功" } 也就是说,这个注解的作用就是:返回结果中有null值,干掉它!

测试结果

mysql数据库中有一条学号为13240115的数据:

img

  • student_id = "13240115"时:
代码语言:javascript
复制
{
    "data": {
        "id": ,
        "studentId": "13240115",
        "name": "Tiger",
        "age": ,
        "famillyAddress": "北京",
        "createdDate": "2018-10-08T05:45:49.000+0000",
        "updatedDate": "2018-10-09T05:36:36.000+0000"
    },
    "code": "0000",
    "msg": "请求成功"
}
  • student_id = "13240114"时:
代码语言:javascript
复制
{
    "code": "0001",
    "msg": "学号不存在"
}
  • student_id = "1324011",不足8位时
代码语言:javascript
复制
{
    "code": "0002",
    "msg": "学号长度不足"
}
  • student_id = "13240115",然后在接口中加上一行代码,System.out.println(1/0);模拟一个异常发生

img

返回结果:

代码语言:javascript
复制
{
    "code": "9999",
    "msg": "网络错误,待会重试"
}

控制台日志:

代码语言:javascript
复制
java.lang.ArithmeticException: / by zero
    at com.dl.cn.message.controller.StudentInofController.findByStudentId(StudentInofController.java:) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:) ~[na:1.8.0_161]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:) ~[na:1.8.0_161]
    at java.lang.reflect.Method.invoke(Method.java:) ~[na:1.8.0_161]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]

综上所述:正好满足了文章开头提出的需求,如果你正好有需要,就动手撸一遍试试!

这个周末,正好赶上降雪,在家待了两天,寸步未出家门… …

银链如华,寒钩钓雪,清冷的冬夜里总是盈荡着一丝跳脱出凛冽的温暖。

雪花飘逸的舞姿在天地间成为冬的深沉,从那遥远的苍穹片片洒落人间,纯洁的晶莹,融化成瞬间的美丽,即便是粉身碎骨,也要投向大地的怀抱,即使已然消逝,留下呓语般的清冷气息,仍旧撒满天际。

在不南不北的上海,难得见雪!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员啊粥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • 案例
    • 建表
      • 引入包
        • 加入缓存(可以不需要)
          • 创建实体类
            • 创建Mapper
              • 创建service
                • 创建统一返回结果实体类
                  • 创建异常编码和描述类:`StudentException`
                    • 创建异常处理的全局配置类:`ExceptionHandler`
                      • 创建controler:`StudentInofController`
                        • `application.properties`配置
                        • 总结
                        • 测试结果
                        相关产品与服务
                        云数据库 Redis
                        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档