失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Java项目构建基础:统一结果 统一异常 统一日志(好文推荐)

Java项目构建基础:统一结果 统一异常 统一日志(好文推荐)

时间:2020-09-23 23:37:43

相关推荐

Java项目构建基础:统一结果 统一异常 统一日志(好文推荐)

点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

作者:永动的图灵机

juejin.im/post/5e073980f265da33f8653f2e

统一结果返回

目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式有利于前后端的交互与UI的展示。

统一结果的一般形式

是否响应成功;

响应状态码;

状态码描述;

响应数据

其他标识符

结果类枚举

前三者可定义结果枚举,如:success,code,message

@GetterpublicenumResultCodeEnum{SUCCESS(true,20000,"成功"),UNKNOWN_ERROR(false,20001,"未知错误"),,PARAM_ERROR(false,20002,"参数错误"),;//响应是否成功privateBooleansuccess;//响应状态码privateIntegercode;//响应信息privateStringmessage;ResultCodeEnum(booleansuccess,Integercode,Stringmessage){this.success=success;this.code=code;this.message=message;}}

统一结果类

第5个属于自定义返回,利用前4者可定义统一返回对象

注意:

外界只可以调用统一返回类的方法,不可以直接创建,因此构造器私有;

内置静态方法,返回对象;

为便于自定义统一结果的信息,建议使用链式编程,将返回对象设类本身,即return this;

响应数据由于为json格式,可定义为JsonObject或Map形式;

@DatapublicclassR{privateBooleansuccess;privateIntegercode;privateStringmessage;privateMap<String,Object>data=newHashMap<>();//构造器私有privateR(){}//通用返回成功publicstaticRok(){Rr=newR();r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());r.setCode(ResultCodeEnum.SUCCESS.getCode());r.setMessage(ResultCodeEnum.SUCCESS.getMessage());returnr;}//通用返回失败,未知错误publicstaticRerror(){Rr=newR();r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());returnr;}//设置结果,形参为结果枚举publicstaticRsetResult(ResultCodeEnumresult){Rr=newR();r.setSuccess(result.getSuccess());r.setCode(result.getCode());r.setMessage(result.getMessage());returnr;}/**------------使用链式编程,返回类本身-----------**///自定义返回数据publicRdata(Map<String,Object>map){this.setData(map);returnthis;}//通用设置datapublicRdata(Stringkey,Objectvalue){this.data.put(key,value);returnthis;}//自定义状态信息publicRmessage(Stringmessage){this.setMessage(message);returnthis;}//自定义状态码publicRcode(Integercode){this.setCode(code);returnthis;}//自定义返回结果publicRsuccess(Booleansuccess){this.setSuccess(success);returnthis;}}

控制层返回

视图层使用统一结果

@RestController@RequestMapping("/api/v1/users")publicclassTeacherAdminController{@AutowiredprivateUserServiceuserService;@GetMappingpublicRlist(){List<Teacher>list=teacherService.list(null);returnR.ok().data("itms",list).message("用户列表");}}

json结果

{"success":true,"code":20000,"message":"查询用户列表","data":{"itms":[{"id":"1","username":"admin","role":"ADMIN","deleted":false,"gmtCreate":"-12-26T15:32:29","gmtModified":"-12-26T15:41:40"},{"id":"2","username":"zhangsan","role":"USER","deleted":false,"gmtCreate":"-12-26T15:32:29","gmtModified":"-12-26T15:41:40"}]}}

统一结果类的使用参考了mybatis-plus中R对象的设计

统一异常处理

使用统一返回结果时,还有一种情况,就是程序的保存是由于运行时异常导致的结果,有些异常我们可以无法提前预知,不能正常走到我们return的R对象返回。

因此,我们需要定义一个统一的全局异常来捕获这些信息,并作为一种结果返回控制层

@ControllerAdvice

该注解为统一异常处理的核心

是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上

该类中的设计思路:

使用@ExceptionHandler注解捕获指定或自定义的异常;

使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;

必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;

自定一个异常类,捕获针对项目或业务的异常;

异常的对象信息补充到统一结果枚举中;

自定义全局异常类

@DatapublicclassCMSExceptionextendsRuntimeException{privateIntegercode;publicCMSException(Integercode,Stringmessage){super(message);this.code=code;}publicCMSException(ResultCodeEnumresultCodeEnum){super(resultCodeEnum.getMessage());this.code=resultCodeEnum.getCode();}@OverridepublicStringtoString(){return"CMSException{"+"code="+code+",message="+this.getMessage()+'}';}}

统一异常处理器

//...importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseBody;@ControllerAdvicepublicclassGlobalExceptionHandler{/**--------通用异常处理方法--------**/@ExceptionHandler(Exception.class)@ResponseBodypublicRerror(Exceptione){e.printStackTrace();returnR.error();//通用异常结果}/**--------指定异常处理方法--------**/@ExceptionHandler(NullPointerException.class)@ResponseBodypublicRerror(NullPointerExceptione){e.printStackTrace();returnR.setResult(ResultCodeEnum.NULL_POINT);}@ExceptionHandler(HttpClientErrorException.class)@ResponseBodypublicRerror(IndexOutOfBoundsExceptione){e.printStackTrace();returnR.setResult(ResultCodeEnum.HTTP_CLIENT_ERROR);}/**--------自定义定异常处理方法--------**/@ExceptionHandler(CMSException.class)@ResponseBodypublicRerror(CMSExceptione){e.printStackTrace();returnR.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配置文件

/xu_san_duo/article/details/80364600

配置

以下直接贴出配置信息,介绍信息可以直接参考备注

<?xmlversion="1.0"encoding="UTF-8"?><!--日志级别从低到高分为TRACE<DEBUG<INFO<WARN<ERROR<FATAL,如果设置为WARN,则低于WARN的信息都不会输出--><!--scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true--><!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。--><!--debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。--><configurationscan="true"scanPeriod="10seconds"><contextName>logback</contextName><!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。--><propertyname="log.path"value="D:/Documents/logs/edu"/><!--0.日志格式和颜色渲染--><!--彩色日志依赖的渲染类--><conversionRuleconversionWord="clr"converterClass="org.springframework.boot.logging.logback.ColorConverter"/><conversionRuleconversionWord="wex"converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/><conversionRuleconversionWord="wEx"converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/><!--彩色日志格式--><propertyname="CONSOLE_LOG_PATTERN"value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-ddHH: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}}"/><!--1.输出到控制台--><appendername="CONSOLE"class="ch.qos.logback.core.ConsoleAppender"><!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--><filterclass="ch.qos.logback.classic.filter.ThresholdFilter"><level>debug</level></filter><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!--设置字符集--><charset>UTF-8</charset></encoder></appender><!--2.输出到文档--><!--2.1level为DEBUG日志,时间滚动输出--><appendername="DEBUG_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><!--正在记录的日志文档的路径及文档名--><file>${log.path}/edu_debug.log</file><!--日志文档输出格式--><encoder><pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern><charset>UTF-8</charset><!--设置字符集--></encoder><!--日志记录器的滚动策略,按日期,按大小记录--><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志归档--><fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文档保留天数--><maxHistory>15</maxHistory></rollingPolicy><!--此日志文档只记录debug级别的--><filterclass="ch.qos.logback.classic.filter.LevelFilter"><level>debug</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--2.2level为INFO日志,时间滚动输出--><appendername="INFO_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><!--正在记录的日志文档的路径及文档名--><file>${log.path}/edu_info.log</file><!--日志文档输出格式--><encoder><pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern><charset>UTF-8</charset></encoder><!--日志记录器的滚动策略,按日期,按大小记录--><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--每天日志归档路径以及格式--><fileNamePattern>${log.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文档保留天数--><maxHistory>15</maxHistory></rollingPolicy><!--此日志文档只记录info级别的--><filterclass="ch.qos.logback.classic.filter.LevelFilter"><level>info</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--2.3level为WARN日志,时间滚动输出--><appendername="WARN_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><!--正在记录的日志文档的路径及文档名--><file>${log.path}/edu_warn.log</file><!--日志文档输出格式--><encoder><pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern><charset>UTF-8</charset><!--此处设置字符集--></encoder><!--日志记录器的滚动策略,按日期,按大小记录--><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文档保留天数--><maxHistory>15</maxHistory></rollingPolicy><!--此日志文档只记录warn级别的--><filterclass="ch.qos.logback.classic.filter.LevelFilter"><level>warn</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--2.4level为ERROR日志,时间滚动输出--><appendername="ERROR_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><!--正在记录的日志文档的路径及文档名--><file>${log.path}/edu_error.log</file><!--日志文档输出格式--><encoder><pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern><charset>UTF-8</charset><!--此处设置字符集--></encoder><!--日志记录器的滚动策略,按日期,按大小记录--><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文档保留天数--><maxHistory>15</maxHistory></rollingPolicy><!--此日志文档只记录ERROR级别的--><filterclass="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。addtivity:是否向上级logger传递打印信息。默认是true。<loggername="org.springframework.web"level="info"/><loggername="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor"level="INFO"/>--><!--使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:第一种把<rootlevel="info">改成<rootlevel="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:【.mybatis=debuglogging.level.dao=debug】--><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG可以包含零个或多个元素,标识这个appender将会添加到这个logger。--><!--4.最终的策略--><!--4.1开发环境:打印控制台--><springProfilename="dev"><loggername="com.cms"level="info"/><rootlevel="info"><appender-refref="CONSOLE"/><appender-refref="DEBUG_FILE"/><appender-refref="INFO_FILE"/><appender-refref="WARN_FILE"/><appender-refref="ERROR_FILE"/></root></springProfile><!--4.2生产环境:输出到文档--><springProfilename="pro"><loggername="com.cms"level="warn"/><rootlevel="info"><appender-refref="ERROR_FILE"/><appender-refref="WARN_FILE"/></root></springProfile></configuration>

日志收集异常信息

日志信息往往伴随着异常信息的输出,因此,我们需要修改统一异常的处理器,将异常信息以流的方式写到日志文件中

异常信息文件工具类

@Slf4jpublicclassExceptionUtil{/***打印异常信息*/publicstaticStringgetMessage(Exceptione){StringswStr=null;try(StringWritersw=newStringWriter();PrintWriterpw=newPrintWriter(sw)){e.printStackTrace(pw);pw.flush();sw.flush();swStr=sw.toString();}catch(IOExceptionex){ex.printStackTrace();log.error(ex.getMessage());}returnswStr;}}

修改统一异常处理器,将异常方法中的直接打印改为日志输入并打印

//...importlombok.extern.slf4j.Slf4j;@ControllerAdvice@Slf4jpublicclassGlobalExceptionHandler{/**--------通用异常处理方法--------**/@ExceptionHandler(Exception.class)@ResponseBodypublicRerror(Exceptione){//e.printStackTrace();log.error(ExceptionUtil.getMessage(e));returnR.error();}//...}

注意

日志的环境即spring.profiles.acticve,跟随项目启动;

启动后,即可到自定目录查找到生成的日志文件;

本地idea调试时,推荐Grep Console插件可实现控制台的自定义颜色输出

详细过程,可参考源代码:

/chetwhy/cloud-flow

热门内容:

Kafka为什么这么快?

一线大厂的分布式唯一ID生成方案是什么样的?

JVM 史上最最最完整深入解析(12000 字噢)

硬货 | Redis 性能问题分析

Spring Boot 服务监控,健康检查,线程信息,JVM堆信息,指标收集,运行情况监控等!

一开工,就遇到上亿(MySQL)大表的优化,我的天...

瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了

分享 14 个 Spring MVC 顶级技巧!

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复666领取,更多内容陆续奉上。

明天见(。・ω・。

如果觉得《Java项目构建基础:统一结果 统一异常 统一日志(好文推荐)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。