Java开发异常及日志

异常日志 

异常:

      三类:

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 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap();
    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 MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap();
    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 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 list = new ArrayList();
        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、
     * 避免打印重复日志
     * 尽量避免循环打印日志
     * 浪费磁盘空间
     * 避免重复打印的一个方面的正例子: 
     * 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



    
    
    
    
    
    
    ${APP_NAME}

    
    
        
            ${ENCODER_PATTERN}
        
    

    
    
        
            ${LOG_HOME}/output.%d{yyyy-MM-dd}.log
            7
        
        
            ${ENCODER_PATTERN}
        
    

    
    
        
            ${LOG_HOME}/error.%d{yyyy-MM-dd}.log
            15
        
        
            ${ENCODER_PATTERN}
        
        
            WARN
        
    

    
    
        
            ${LOG_HOME}/sync.%d{yyyy-MM-dd}.log
            7
        
        
            ${ENCODER_PATTERN}
        
    

    
        
    

    
        
        
        
        
    



你可能感兴趣的:(Java后台开发)