Java项目构建基础:统一结果,统一异常,统一日志
统一结果返回
目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式有利于前后端的交互与UI的展示。
统一结果的一般形式
是否响应成功;
响应状态码;
状态码描述;
响应数据
其他标识符
结果类枚举
前三者可定义结果枚举,如:success,code,message
@Getter
public enum ResultCodeEnum {SUCCESS(true,20000,"成功"),UNKNOWN_ERROR(false,20001,"未知错误"),,PARAM_ERROR(false,20002,"参数错误"),;// 响应是否成功private Boolean success;// 响应状态码private Integer code;// 响应信息private String message;ResultCodeEnum(boolean success, Integer code, String message) {this.success = success;this.code = code;this.message = message;}
}
统一结果类
第5个属于自定义返回,利用前4者可定义统一返回对象
注意:
外界只可以调用统一返回类的方法,不可以直接创建,因此构造器私有;
内置静态方法,返回对象;
为便于自定义统一结果的信息,建议使用链式编程,将返回对象设类本身,即return this;
响应数据由于为json格式,可定义为JsonObject或Map形式;
@Data
public class R {private Boolean success;private Integer code;private String message;private Map<String, Object> data = new HashMap<>();// 构造器私有private R(){}// 通用返回成功public static R ok() {R r = new R();r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());r.setCode(ResultCodeEnum.SUCCESS.getCode());r.setMessage(ResultCodeEnum.SUCCESS.getMessage());return r;}// 通用返回失败,未知错误public static R error() {R r = new R();r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());return r;}// 设置结果,形参为结果枚举public static R setResult(ResultCodeEnum result) {R r = new R();r.setSuccess(result.getSuccess());r.setCode(result.getCode());r.setMessage(result.getMessage());return r;}/**------------使用链式编程,返回类本身-----------**/// 自定义返回数据public R data(Map<String,Object> map) {this.setData(map);return this;}// 通用设置datapublic R data(String key,Object value) {this.data.put(key, value);return this;}// 自定义状态信息public R message(String message) {this.setMessage(message);return this;}// 自定义状态码public R code(Integer code) {this.setCode(code);return this;}// 自定义返回结果public R success(Boolean success) {this.setSuccess(success);return this;}
}
控制层返回
视图层使用统一结果
@RestController
@RequestMapping("/api/v1/users")
public class TeacherAdminController {@Autowiredprivate UserService userService;@GetMappingpublic R list() {List<Teacher> list = teacherService.list(null);return R.ok().data("itms", list).message("用户列表");}
}
json结果
{"success": true,"code": 20000,"message": "查询用户列表","data": {"itms": [{"id": "1","username": "admin","role": "ADMIN","deleted": false,"gmtCreate": "2019-12-26T15:32:29","gmtModified": "2019-12-26T15:41:40"},{"id": "2","username": "zhangsan","role": "USER","deleted": false,"gmtCreate": "2019-12-26T15:32:29","gmtModified": "2019-12-26T15:41:40"}]}
}
统一结果类的使用参考了mybatis-plus中R对象的设计
统一异常处理
使用统一返回结果时,还有一种情况,就是程序的保存是由于运行时异常导致的结果,有些异常我们可以无法提前预知,不能正常走到我们return的R对象返回。
因此,我们需要定义一个统一的全局异常来捕获这些信息,并作为一种结果返回控制层
@ControllerAdvice
该注解为统一异常处理的核心
是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上
该类中的设计思路:
使用@ExceptionHandler注解捕获指定或自定义的异常;
使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
自定一个异常类,捕获针对项目或业务的异常;
异常的对象信息补充到统一结果枚举中;
自定义全局异常类
@Data
public class CMSException extends RuntimeException {private Integer code;public CMSException(Integer code, String message) {super(message);this.code = code;}public CMSException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();}@Overridepublic String toString() {return "CMSException{" + "code=" + code + ", message=" + this.getMessage() + \'}\';}
}
统一异常处理器
// ...
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class GlobalExceptionHandler {/**-------- 通用异常处理方法 --------**/@ExceptionHandler(Exception.class)@ResponseBodypublic R error(Exception e) {e.printStackTrace();return R.error(); // 通用异常结果}/**-------- 指定异常处理方法 --------**/@ExceptionHandler(NullPointerException.class)@ResponseBodypublic R error(NullPointerException e) {e.printStackTrace();return R.setResult(ResultCodeEnum.NULL_POINT);}@ExceptionHandler(HttpClientErrorException.class)@ResponseBodypublic R error(IndexOutOfBoundsException e) {e.printStackTrace();return R.setResult(ResultCodeEnum.HTTP_CLIENT_ERROR);}/**-------- 自定义定异常处理方法 --------**/@ExceptionHandler(CMSException.class)@ResponseBodypublic R error(CMSException e) {e.printStackTrace();return R.error().message(e.getMessage()).code(e.getCode());}
}
控制层展示
以下为展示当遇到null指定异常时,返回的结果信息
{"success": false,"code": 20007,"message": "空指针异常","data": {}
}
本节介绍统一异常较为简略,详细参考:
https://juejin.im/post/5cbc744a6fb9a0685a3f01a7
统一日志收集
日志是追踪错误定位问题的关键,尤其在生产环境中,需要及时修复热部署,不会提供开发者debug的环境,此时日志将会是最快解决问题的关键
日志的框架比较丰富,由于spring boot对logback的集成,因此推荐使用logback在项目中使用。
Logback
关于logback的配置和介绍,可以参考官网或推荐博客glmapper的logback博客,logback-spring.xml配置文件
https://blog.csdn.net/xu_san_duo/article/details/80364600
配置
以下直接贴出配置信息,介绍信息可以直接参考备注
<?xml version="1.0" encoding="UTF-8"?><configuration scan="true" scanPeriod="10 seconds"><contextName>logback</contextName><property name="log.path" value="D:/Documents/logs/edu" /><conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /><conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /><conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /><property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>debug</level></filter><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><charset>UTF-8</charset></encoder></appender><appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/edu_debug.log</file><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> </encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>debug</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/edu_info.log</file><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>info</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/edu_warn.log</file><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> </encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>warn</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/edu_error.log</file><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> </encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><maxHistory>15</maxHistory></rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender> <springProfile name="dev"><logger name="com.cms" level="info"/><root level="info"><appender-ref ref="CONSOLE" /><appender-ref ref="DEBUG_FILE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="WARN_FILE" /><appender-ref ref="ERROR_FILE" /></root></springProfile><springProfile name="pro"><logger name="com.cms" level="warn"/><root level="info"><appender-ref ref="ERROR_FILE" /><appender-ref ref="WARN_FILE" /></root></springProfile></configuration>
日志收集异常信息
日志信息往往伴随着异常信息的输出,因此,我们需要修改统一异常的处理器,将异常信息以流的方式写到日志文件中
异常信息文件工具类
@Slf4j
public class ExceptionUtil {/*** 打印异常信息*/public static String getMessage(Exception e) {String swStr = null;try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {e.printStackTrace(pw);pw.flush();sw.flush();swStr = sw.toString();} catch (IOException ex) {ex.printStackTrace();log.error(ex.getMessage());}return swStr;}
}
修改统一异常处理器,将异常方法中的直接打印改为日志输入并打印
// ...
import lombok.extern.slf4j.Slf4j;@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/**-------- 通用异常处理方法 --------**/@ExceptionHandler(Exception.class)@ResponseBodypublic R error(Exception e) {// e.printStackTrace();log.error(ExceptionUtil.getMessage(e));return R.error();}// ...
}
注意
日志的环境即spring.profiles.acticve,跟随项目启动;
启动后,即可到自定目录查找到生成的日志文件;
本地idea调试时,推荐Grep Console插件可实现控制台的自定义颜色输出
详细过程,可参考源代码:
https://github.com/chetwhy/cloud-flow
作者:永动的图灵机
juejin.im/post/5e073980f265da33f8653f2e
文章推荐程序员效率:画流程图常用的工具程序员效率:整理常用的在线笔记软件远程办公:常用的远程协助软件,你都知道吗?51单片机程序下载、ISP及串口基础知识硬件:断路器、接触器、继电器基础知识
Java项目构建基础:统一结果,统一异常,统一日志相关推荐
- Java项目构建基础:统一结果,统一异常,统一日志(好文推荐)
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:永动的图灵机 juejin.im/post/5e073980 ...
- Java项目构建基础的三个统一,太厉害了!
作者:永动的图灵机 来源:juejin.im/post/5e073980f265da33f8653f2e 统一结果返回 目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式 ...
- delphi自定义统一基础设置_Java项目构建基础:统一结果,统一异常,统一日志...
作者:永动的图灵机 juejin.im/post/5e073980f265da33f8653f2e 统一结果返回 目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式有利于 ...
- Java项目构建工具Gradle是否可以完全替代Maven?
前言 在Java项目的开发中,需要引入自动化构建工具来帮助我们管理项目的外部依赖包.项目编译.打包等工作.Gradle和Maven是Java世界中两个重要的自动化构建工具,在项目中我们在两者之间如何选 ...
- java项目构建部署包
博客分类: JAVA Java 工程在生产环境运行时,一般需要构建成一个jar,同时在运行时需要把依赖的jar添加到classpath中去,如果直接运行添加classpath很不方便,比较方便的是创建 ...
- Java项目构建管理Maven的安装与配置
1.下载安装 首先安装配置好jdk[指路:jdk的安装和配置],然后下载安装maven(现在多数使用maven3,版本号3.x.x,可以去官网下载https://maven.apache.org/do ...
- java pipeline 实现_Docker+Jenkins+Pipeline实现持续集成(二)java项目构建
插件推荐 插件名 作用 Blue Ocean Jenkins2.7以后可安装,是Jenkins的一种新视图,能够通过图形化的界面创建和编辑Jenkinsfile,实现pipeline as code ...
- java项目构建ant_Ant - 项目构建 - 开发工具 - JAVA开源项目 - 开源吧
相关开源项目 Gradle: Gradle是一个基于Groovy的构建工具,它使用Groovy来编写构建脚本,支持依赖管理和多项目创建,类似Maven,但比之简单轻便.为Ivy提供了一个layer,提 ...
- Java项目构建打包规范,jenkins2.121.1构建java项目环境,一键打包发布
该版本为2.121.1,其他版本会稍有不同,仅做参考 1.登录jenkins后,点击左上角"新建任务". 2.填写任务名称,然后选择构建类型(一般是选择构建一个maven项目) 3 ...
最新文章
- 将request中的所有参数存放到自定义的map中
- djano 模型查询
- 配置opencv cmake
- 大数据项目开发案例_大数据开发相关术语解析
- python刷leetcode_零基础python刷leetcode -- 1. Two Sum
- zw版【转发·台湾nvp系列Delphi例程】.NET调用HALCON COM控件内存释放模式
- c# 开发项目的过程
- php webp格式转换,webp的格式的转换
- c4d软件安装上打开不了_Mac安装软件问题,“xxx”已损坏,无法打开
- 多双系统下蓝牙键盘鼠标的共享配对问题解决办法:win + debian + arch~IRK、LTK、ERand、EDIV、CSRK
- 使用华为光猫ONT使能工具后,本地主机如何通过Telnet登录光猫进行配置
- 中国在线直播行业用户规模达6.35亿人,约97.3%的主播年收入在3万元以下 | 美通社头条...
- github python100天_GitHub - 1814931012/Python-100-Days: Python - 100天从新手到大师
- 华为云宝塔linux,华为云上安装宝塔面板
- DataFrame的元素选择与赋值
- 自媒体人:如何在竞争激烈的环境下生存?
- 一个手机壳把华为4G机型升级到5G,首先适配P50Pro,售价799元计划下月开卖
- linux下latex中文字体,Ubuntu下LaTex中文环境配置
- 服务降级是什么?Spring Cloud如何实现?
- 转贴:网友叶落扬天利用火鸟字幕合并器来学习美剧中的英语的心得
热门文章
- [新手及懒人适用]轻松恢复误Ghost的硬盘
- 金山吹响讨伐灰鸽子的号角
- gpio_request 原形代码
- ABB 机器人 IsStopMoveAct 指令 - Is stop move flags active
- lua 从一串数字中取出偶数位的数字_为什么JavaScript中 0.1 0.2 不等于0.3?
- 第四章:条件语句(if)和循环结构(while)
- NGINX生命周期-转
- linux杂七杂八整理
- jQuery 学习笔记(jQuery: The Return Flight)
- jquery插件之无缝循环新闻列表