Java开发异常及日志

来源:互联网 发布:2016最新淘宝网店书籍 编辑:程序博客网 时间:2024/05/24 07:45

异常日志 

异常:

      三类:

1系统无法支撑导致异常(错误);

2代码逻辑异常,无法继续运行下去导致异常;

3不符合开发者业务要求导致异常。

系统错误:error(系统支持不下去) 。OutOfMemoryErrorStackOverflowError、IllegalAccessError。jvm运行错误、内存不足等等。

代码逻辑异常:代码执行过程中,因为用户代码问题,导致代码执行不下去。

两类:

1 RuntimeException

NullPointException等,代码逻辑中比较容易避免而没有避免掉的一些异常。用户代码逻辑可以很容易直接校验处理的异常。

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>



原创粉丝点击