Java开发异常及日志
来源:互联网 发布:2016最新淘宝网店书籍 编辑:程序博客网 时间:2024/05/24 07:45
异常日志
异常:
三类:
1系统无法支撑导致异常(错误);
2代码逻辑异常,无法继续运行下去导致异常;
3不符合开发者业务要求导致异常。
系统错误:error(系统支持不下去) 。OutOfMemoryError、StackOverflowError、IllegalAccessError。jvm运行错误、内存不足等等。
代码逻辑异常:代码执行过程中,因为用户代码问题,导致代码执行不下去。
两类:
1 RuntimeException;
NullPointException等,代码逻辑中比较容易避免而没有避免掉的一些异常。用户代码逻辑可以很容易直接校验处理的异常。
2 CheckedException;
IOException等;
用户写代码时不容易检测因为代码问题导致运行时可以出现的异常。
需要有预先可能出现的异常的处理方式的一类异常。
Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。
业务异常:用户自定义异常,不符合用户需要的业务处理逻辑。就是程序执行到某个地方,如果继续往下,则不符合业务要求的逻辑,那么用户就自定义一个异常。
(一) 异常处理
1、RuntimeException不用try{}catch(){}
/** * GY * 2017年9月22日 * RuntimeException 可以通过预先检查进行规避 * 不用try-catch */ @Test public void runtimeExceptionNotUseTryCatch(){ String str = null; m1(str);//空指针了 System.out.println("over"); } @Test public void runtimeExceptionNotUseTryCatch2(){ String str = null; m2(str); System.out.println("over"); } //异常 public void m1(String str){ int i = str.indexOf("111"); } //建议 //两个原因:1、性能及开销;2、这类异常本就不应该在运行中出现(编程中就能规避),而不是等出现异常后再处理异常。 public void m2(String str){ if(str == null){ return;//不太业务场景不同处理方式 } int i = str.indexOf("111"); //略 }
2、不要用try-catch来控制程序的流程
/** * 追求卓越成功自然相随 * 2017年9月24日 * 功能:不要用是否异常做条件控制 */ //错误使用 @Test public void dontUseExceptionControlProcedure(){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); String strDate = "2017|09|16"; Date date = null; try { date = sdf.parse(strDate); System.out.println("sdf类型"); } catch (Exception e) { e.printStackTrace(); SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd"); try { date = sdf2.parse(strDate); System.out.println("sdf2类型"); } catch (Exception e2) { e2.printStackTrace(); SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy.MM.dd"); try { date = sdf3.parse(strDate); System.out.println("sdf3类型"); } catch (Exception e3) { e3.printStackTrace(); System.out.println("无法转化"); return; } } } //......后续使用date对象 System.out.println(date.getTime()); } /** * 使用代码条件控制流程 * 不用异常控制流程 * GY * 2017年9月25日 */ //推荐使用 定义正则表达式判断 因为异常的处理效率比条件分支低 //出现异常后再执行某段代码 跟 提前 判断进入某段代码的 区别。 @Test public void dontUseExceptionControlProcedure2(){ String strDate = "2017-02-18"; String regDate = "[0-9]{4}[\\-\\/\\s\\年\\.]?((0[1-9])|(1[0-2]))[\\-\\/\\s\\月\\.]?(([0-2][1-9])|(3[0|1]))[日\\s]?"; Pattern pattern = Pattern.compile(regDate); Matcher m = pattern.matcher(strDate); if(!m.matches()){ System.out.println("字符串无法转化日期");//抛出自定义异常 return; } SimpleDateFormat sdf = null; if((strDate.indexOf("年")) != -1){ sdf = new SimpleDateFormat("yyyy年MM月dd日"); System.out.println("年月日"); } if((strDate.indexOf("-")) != -1 && sdf == null){ sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println("yyyy-MM-dd"); } if((strDate.indexOf(".")) != -1 && sdf == null){ sdf = new SimpleDateFormat("yyyy.MM.dd"); System.out.println("yyyy.MM.dd"); } if((strDate.indexOf("/")) != -1 && sdf == null){ sdf = new SimpleDateFormat("yyyy/MM/dd"); System.out.println("yyyy/MM/dd"); } if(sdf == null){ sdf = new SimpleDateFormat("yyyyMMdd"); System.out.println("yyyyMMdd"); } Date date = null; try { date = sdf.parse(strDate); } catch (ParseException e) { e.printStackTrace(); System.out.println("太奇葩!"); return; } //......后续使用date对象 System.out.println(date.getTime()); }
3、不同异常不同catch处理
/** * try代码块能小则小 * 稳定代码不应包含在try中 * 不稳定的代码catch区分异常种类 * 因为不同的异常类型不同的处理方式 * GY * 2017年9月25日 */ //(...)代表瞎bb //原因: try执行代码的效率会有所下降,try得越多另外开辟的空间耗损越多(...) 见上例 // 不同的异常类型不同的处理方式,方便分析异常日志,针对不同的业务异常,解决方案更加具体(...) @Test public void minTryCatchBlock(){ //这里演示不同异常不同解决方案 //参数异常不计录,业务异常进行记录 try { handleService1("GY","1",new BigDecimal(500),123L); } catch (MyParameterException e) { e.printStackTrace(); return; } catch (MyBusinessException e) { e.printStackTrace(); //记录日志.........业务异常记录日志 Long userId = (Long)(e.getData()); System.out.println("异常用户id:"+userId); return; } } /** * @throws MyParameterException * @throws MyBusinessException * GY * 2017年9月25日 * 针对某个业务的处理类 */ public void handleService1(String name, String loanState, BigDecimal money, Long userId) throws MyParameterException, MyBusinessException{ met1(name); met2(loanState, money,userId); } /** * @param s * @throws MyParameterException * GY * 2017年9月25日 * 操作1 */ public void met1(String name) throws MyParameterException{ if(StringUtils.isBlank(name)){ throw new MyParameterException("0001"); } //..... } /** * @param loanState * @param money * @throws MyBusinessException * GY * 2017年9月25日 * 操作2 */ public void met2(String loanState, BigDecimal money, Long userId) throws MyBusinessException{ if("1".equals(loanState)){ MyBusinessException e = new MyBusinessException("9002"); e.setData(userId); throw e; } //..... }
/** * @author GY * 2017年9月25日 * 入参校验异常类 */@SuppressWarnings("serial")class MyParameterException extends Exception{ /** * 异常码 */ private String code; /** * 异常错误提示 */ private String msg; /** * 异常对象 */ private Object data;//比如异常json对象 后期捕获异常后处理的数据依据 /** * 可维护异常信息map */ private static final HashMap<String, String> MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap<String, String>(); static{ MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0001", "参数不能为空");//必要字段传空 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0002", "参数不符合业务逻辑");//比如不可能为负数的字段为负数 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0003", "参数内容不合法");//比如日期格式不合规 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0004", "不符合接口参数结构");//比如接口json报文解析失败 } /** * @param code异常码 */ public MyParameterException(String code){ this.code = code; this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code); } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } @Override public void printStackTrace() { System.out.println(this.code+":"+this.msg); } }/** * @author GY * 2017年9月25日 * 业务处理异常类 */@SuppressWarnings("serial")class MyBusinessException extends Exception{ /** * 异常码 */ private String code; /** * 异常错误提示 */ private String msg; /** * 异常对象 */ private Object data;//比如异常json对象 /** * 可维护异常信息map */ private static final HashMap<String, String> MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap<String, String>(); static{ MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9001", "借款信息不存在");//不存在该用户的欠款信息 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9002", "用户已经还款");//数据库已经还了又调了还款接口还钱 } /** * @param code异常码 */ public MyBusinessException(String code){ this.code = code; this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code); } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static HashMap<String, String> getMyParameterExceptionCodeMsgMap() { return MY_PARAMETER_EXCEPTION_CODE_MSG_MAP; } @Override public void printStackTrace() { System.out.println(this.code+":"+this.msg); } }
4、事物方法中异常被catch处理后,需要手动将事物回滚
if(userSave){ try { userDao.save(user); userCapabilityQuotaDao.save(capabilityQuota); } catch (Exception e) { logger.info("能力开通接口,开户异常,异常信息:"+e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。
spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过
配置来捕获特定的异常并回滚
换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚
解决方案:
方案1.例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理
方案2.在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)
在ServiceB的method2方法上注解,告诉spring当该方法抛出自定义异常CustomException时,不要回滚事务,这样当该方法抛出异常时,spring不会标记事务为回滚状态。
@Transactional(noRollbackFor=CustomException.class)
public void method2() throws CustomException{
}
@Transactional(rollbackFor = Throwable.class)//异常回滚绑定
这种设置是因为Spring的默认回滚RuntimeException,如果想要回滚Exception时,要设置@Transactional(rollbackFor = Exception.class),而且Exception还要抛出。
操作数据库异常:SQLException 属于RuntimeException
5、捕获了异常就得处理这个异常,不做处请不要捕获。最外层调用者必须捕获
最外层捕获异常后,将异常信息转换成用户可以理解的内容。
6、
/** * finally中需要释放打开的资源 */ @Test public void finallyNeedCloseResource(){ File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt"); if(file.exists()){ file.delete(); } FileOutputStream fos = null; PrintStream isr = null; PrintWriter pr = null; try {fos = new FileOutputStream(file);isr = new PrintStream (fos);pr = new PrintWriter(isr);pr.print("7777777777");} catch (FileNotFoundException e) {e.printStackTrace();} finally {if(pr != null){pr.close();}if(isr != null){isr.close();}if(fos != null){try {fos.close();} catch (IOException e) {e.printStackTrace();}}} } /** * jdk1.7及以上可以用try-with-resources * 出try体资源即被关闭 */ @Test public void finallyNeedCloseResource2(){ File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt"); if(file.exists()){ file.delete(); } try(FileOutputStream fos = new FileOutputStream(file); PrintWriter pr = new PrintWriter(fos)){ pr.print("7777777777"); System.out.println("000"); return; } catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {//return;System.out.println("try-catch块无论异常与否的终点");} System.out.println("出现异常,异常被处理后才会执行的代码"); } /* * 8、 * 对于异常处理机制一样的不通的异常可以catch这两种异常的父类异常 * 最好抛什么异常捕获(catch)什么异常,统一用Exception不方便区分,不便于区分处理方式 */ /** * 9、 * 可以为null * 必需说明白,方法什么情况会返回null */ @Test public void returnNull(){ //比如数据库删除一条记录,在进行删除完对象对应的数据库数据操作后,可以将对象置为null //return 这个引用(null) } /** * 10、 * 防止出现NPE */ @Test public void avoidNPE(){ method1(999L); } //反例 错误 解决:返回值类型跟方法返回类型保持一致 public long method1(Long taskId){ //Long userId = repayDao.findUserIdByTaskId(taskId); //如果没有这个字段或没有这条数据 数据库查询结果为null Long userId = null; return userId;//会有警告提示 } //JDK8 Optional obj.isPresent() 与 obj != null 无任何分别 /** * 10、 * 集合里的元素isNotEmpty 取出的这个元素仍然有可能为null * GY * 2017年9月26日 */ @Test public void avoidNPE2(){ List<String> list = new ArrayList<String>(); list.add(null); boolean isEmpty = (list.isEmpty()); System.out.println(isEmpty); //false 非空 System.out.println(list.get(0)); //null 集合中含有一个null元素,集合非空 } /* * 11、 * 避免直接抛出 new RuntimeException() * unchecked异常 运行时异常 控制组 数组越界 (....) 代码规避,不让出现 * checked 异常 Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。 需要具备处理这种异常的代码 * 更不允许抛出 Exception 或者 Throwable */ /* * 12、 * RPC调用:远程过程调用协议 * 一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 * */ /* * 13、 * DRY 原则 * 不要重复自己"。 * 强调的意思就是在进行编程时相同的代码不要重复写,最好只写一次,然后可以在其他地方直接引用。 * 一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候可以抽取成一个公共校验方法 * 必须在某项操作前校验(执行某段代码(即使代码为空)),也有必要加上。面向切面编程。(....) 资金类交易 */
(二) 日志规约
package exceptionlog;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class ExceptionLog { private static final Logger logger = LoggerFactory.getLogger(ExceptionLog.class); //log file sync等待时间发生在redo log从log buffer写入到log file期间。 /** * 不可以直接使用Log4j、Logback自带的api记录自己业务系统的日志 * 应该依赖使用日志框架 SLF4J 中的 API,比较统一日志格式,便于后续日志分析 * 有利于维护和各个类的日志处理方式统一 * 1、添加依赖包logback使用需要和slf4j一起使用,所以总共需要添加依赖的包有slf4j-api * logback使用需要和slf4j一起使用,所以总共需要添加依赖的包有slf4j-api.jar,logback-core.jar,logback-classic.jar。 * logback-access.jar这个暂时用不到所以不添加依赖了 * GY * 2017年9月26日 */ @Test public void runtimeExceptionNotUseTryCatch(){ //trace: 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出。一般没人用 logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); //fatal: 级别比较高了。重大错误,这种级别你可以直接停止程序了,是不应该出现的错误么!不用那么紧张,其实就是一个程度的问题。SLF4J 中的 API没有 } /* * 3、 * 日志命名规范化 * mppserver 应用中单独监控时区转换异常, * 如:mppserver_monitor_timeZoneConvert.log * 说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于 通过日志对系统进行及时监控。 */ /* * 4、 * 必须使用条件输出形式或者使用占位符的方式 1、拼接;2、{},传参数 * 如果代码中所打印的日志,没有达到日志级别的要求,应该要考虑 打印日志 所占用的系统资源, * 执行了上述操作(代码),最终日志却没有打印 * */ /* * 5、 * 避免打印重复日志 * 尽量避免循环打印日志 * 浪费磁盘空间 * 避免重复打印的一个方面的正例子:<logger name="com.taobao.dubbo.config" additivity="false"> * log4j.xml 中设置 additivity=false * * 它是 子Logger是否继承 父Logger 的 输出源(appender) 的标志位。 * 具体说,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。 * 若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。 */ /* * 6、 * 异常信息应该包括两类信息: * 案发现场信息和异常堆栈信息 * logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e); */ @Test public void exceptionInfoLog(){ File file = new File("I:\\javaeeWorkspace\\exceptionLog\\test.txt"); try(FileOutputStream fos = new FileOutputStream(file)){ } catch (FileNotFoundException e) { //e.printStackTrace(); logger.error("文件找不到:"+e.getMessage(), e); } catch (IOException e) { e.printStackTrace(); } //FileOutputStream fos = null; /*try { fos = new FileOutputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); logger.error("文件找不到"+e.getMessage(), e); } finally { if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } }*/ } /* * 7、 * 生产环境禁止输出 debug 日志;有选择地输出 info 日志;】 * 如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题, * 避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。 */ /* * 8、 * 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从 * error 级别只记录系统逻辑出错、异常等重要的错误信息 */}
logback.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug="true"> <!-- 应用名称 --> <property name="APP_NAME" value="ExceptionLog" /> <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为logs目录做日志存放的目录 --> <property name="LOG_HOME" value="${log.dir:-logs}/${APP_NAME}" /> <!-- 日志输出格式 --> <property name="ENCODER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" /> <contextName>${APP_NAME}</contextName> <!-- 控制台日志:输出全部日志到控制台 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>${ENCODER_PATTERN}</Pattern> </encoder> </appender> <!-- 文件日志:输出全部日志到文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder> </appender> <!-- 错误日志:用于将错误日志输出到独立文件 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>15</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <!-- 独立输出的同步日志 --> <appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder> </appender> <logger name="log.sync" level="DEBUG" addtivity="true"> <appender-ref ref="SYNC_FILE" /> </logger> <root> <level value="DEBUG" /> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> <appender-ref ref="ERROR_FILE" /> </root></configuration>
- Java开发异常及日志
- 阿里巴巴Java开发手册-异常日志-日志规约
- 【Java开发手册之异常日志(二)】日志规约
- 阿里巴巴Java开发手册-异常日志-异常处理
- 【Java开发手册之异常日志(一)】异常处理
- 网站开发进阶(八)tomcat异常日志分析及处理
- 《阿里巴巴Java开发手册(正式版)》--异常日志
- 阿里巴巴Java开发手册学习小结8-异常日志
- Java中的异常,日志
- Java开发代码规范之异常日志(二)——日志规约
- Java开发代码规范之异常日志(一)——异常处理
- java 自学日志【八】---异常
- JAVA 打印异常日志详细信息
- java异常信息日志输出
- java 日志异常格式化字符串
- Java编程学习日志 异常
- java-异常/断言/日志/调试
- java 异常、断言和日志
- HashMap之负载因子
- 快慢指针
- Dubbo快速启动
- 目标检测“Focal Loss for Dense Object Detection”
- docker常用命令
- Java开发异常及日志
- vijos最小总代价
- kernel driver probe sequence
- oracle中的左右外连接
- 20个高级Java面试题汇总
- 简易使用Xshell5查看生产日志
- linux led驱动例程(杂项设备)
- SelectionSort
- 二叉搜索树的后序遍历序列