精华内容
下载资源
问答
  • 例外最佳实践(合集) 描述 这是一个提供完整框架的简单(原始)项目管理异常查看性能我们通常不会在异常管理中投入太多精力。 这个微小的框架提供了不同的方法来管理异常以更好的方式所以,我们有: Stackless ...
  • Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花...然而,有几种异常处理的最佳方法被大多数开发团队所使用。下面为常见的几种实用的异常处理方法! 1. 在Finally中清理资源或者使用T

    Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常。这就是为什么大多数开发团队都有自己的异常处理的规则和方法。如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同。常见的异常类型:

    NullPointerException -空指针引用异常
    ClassCastException-类型强制转换异常
    lllegalArgumentException-传递非法参数异常
    ArithmeticException-算术运算异常
    ArrayStoreException-向数组中存放与声明类型不兼容对象异常
    IndexOutOfBoundsException-下标越界异常
    NegativeArraySizeException-创建一个大小为负数的数组错误异常
    NumberFormatException-数字格式异常
    SecurityException-安全异常
    UnsupportedOperationException-不支持的操作异常
    EOFException:文件已结束异常
    FileNotFoundException:文件未找到异常
    SQLException:操作数据库异常
    IOException:输入输出异常
    NoSuchMethodException:方法未找到异常

    然而,有几种异常处理的最

    展开全文
  • 如何处理异常是项目中极为头痛的一件事,尤其是在前后端分离的项目中,Exception必须作为Restful来处理,这里包括如何避免处理Exception的代码分散在项目代码,这样对于异常处理的重构和多语言支持都会造成很大的...

    如何处理异常是项目中极为头痛的一件事,尤其是在前后端分离的项目中,Exception必须作为Restful来处理,这里包括如何避免处理Exception的代码分散在项目代码,这样对于异常处理的重构和多语言支持都会造成很大的麻烦;还包括如何正确定义异常信息,使得用户所看到的错误提示信息是有效的,而不是一些数据库的error-code, 或者是500的HTTP STATUS。

    当异常能够作为JSON成功传递到前端,这时又涉及到前端代码如何解析异常,将异常所包含的信息显示给用户,下面就给出后端Java的异常生成和传递,以及前端VUE接收到异常后处理方法。

    我们要实现的结果是前端接收到如下JSON消息,并把消息的内容显示给用户

    {"exception":"DuplicatedUserException","error":"Conflict","message":"admin 已经被占用. ","status":409,"errors":null}

    从消息内容可以看出这是一个提示用户将要注册的用户名已经被占用,HttpStatus_Conflict: 409, 异常是用户自定义的DuplicatedUserException.

    后端Java代码:

    定义一个抽象类包含ErrorResponse的生成,将来其他每一个自定义异常都对应的Handler都会继承这个抽象类, 

    import java.util.Collection;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.http.HttpStatus;
    
    import com.lims.api.exception.ErrorResponse;
    
    /**
     * Extend this to code an exception handler
     */
    public abstract class AbstractExceptionHandler<T extends Throwable> {
    	
    	protected final Log log = LogFactory.getLog(this.getClass());
    	
    	private String exceptionName;
    	
    	public AbstractExceptionHandler(String exceptionName) {
    		this.exceptionName = exceptionName;
    	}
    
    	public String getExceptionName() {
    		return exceptionName;
    	}
    	
    	protected String getMessage(T ex) {
    		return ex.getMessage();
    	}
    	
    	protected HttpStatus getStatus(T ex) {
    		return null;
    	}
    	
    	protected Collection<FieldError> getErrors(T ex) {
    		return null;
    	}
    
    	public ErrorResponse getErrorResponse(T ex) {
    		
    		ErrorResponse errorResponse = new ErrorResponse();
    		
    		String message = getMessage(ex);
    		if (message != null)
    			errorResponse.setMessage(message);
    		
    		HttpStatus status = getStatus(ex);
    		if (status != null) {
    			errorResponse.setStatus(status.value());
    			errorResponse.setError(status.getReasonPhrase());
    		}
    		
    		errorResponse.setErrors(getErrors(ex));
    		
    		return errorResponse;
    	}
    }
    

    自定义异常类:DuplicatedUserException, 异常的消息通过property文件给定,便于消息内容更改和多语言支持

    import org.springframework.http.HttpStatus;
    
    import com.lims.api.util.LimsUtils;
    
    public class DuplicatedUserException extends RuntimeException {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = -21168497361531088L;
    	// HTTP Status code to be returned
    	private HttpStatus status = HttpStatus.BAD_REQUEST;
    
    	public DuplicatedUserException(String userName) {
    		super(LimsUtils.getMessage("com.lims.api.exception.DuplicatedUserException", userName));
    	}
    
    	public HttpStatus getStatus() {
    		return status;
    	}
    
    	public void setStatus(HttpStatus status) {
    		this.status = status;
    	}
    
    	public DuplicatedUserException httpStatus(HttpStatus status) {
    		this.status = status;
    		return this;
    	}
    
    }

    自定义异常类的Handler

    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import com.lims.api.exception.AbstractExceptionHandler;
    import com.lims.api.security.web.exception.DuplicatedUserException;
    
    @Component
    @Order(Ordered.LOWEST_PRECEDENCE)
    public class DuplicatedUserExceptionHandler extends AbstractExceptionHandler<DuplicatedUserException> {
    
    	public DuplicatedUserExceptionHandler() {
    		
    		super(DuplicatedUserException.class.getSimpleName());
    		log.info("DuplicatedUserExceptionHandler Created");
    	}
    	
    
    	@Override
    	public HttpStatus getStatus(DuplicatedUserException ex) {
    		return ex.getStatus();
    	}
    
    }

    基于以上三个类,所有@Controller中所抛出的异常就可以被包含到Response中传递到前端。以下示例代码:

        @PostMapping("/sign-up")
        public void signUp(@RequestBody ApplicationUser user){
            if(!applicationUserService.isValid(user)) {
            	throw (new DuplicatedUserException(user.getUserName()).httpStatus(HttpStatus.CONFLICT));
            };
            user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
            applicationUserRepository.save(user);
        }

    前端VUE大多数会使用Axios,这里也用它来示例:

     vm.$ajax.post('/api/users/sign-up', register)
                .then(function (res) {
                  toastr.success(res.data.message)
                }).catch(function (error) {
                  toastr.error(error.response.data.message)
                })

    这样前后端的异常处理在结构上显得十分简洁,每一个自定义的异常都可以显示给用户更友好的信息,通过property文件对于信息的定义可以充分利用属性文件的多语言支持功能。

    前端处理异常的代码无需任何条件处理,每一个异常的消息显示都是统一的代码。对于前端代码模块重用有着重要的作用。

    Cheers!

    展开全文
  • Java异常处理最佳实践

    千次阅读 2019-06-30 18:38:55
    在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费...本文给出几个被很多团队使用的异常处理最佳实践。 在 finally 块中清理资源或者使用 try-with-resource 语句...

    在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。

    本文给出几个被很多团队使用的异常处理最佳实践。

    1. 在 finally 块中清理资源或者使用 try-with-resource 语句

    当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。

    public void doNotCloseResourceInTry() {
        FileInputStream inputStream = null;
        try {
            File file = new File("./tmp.txt");
            inputStream = new FileInputStream(file);
            // use the inputStream to read a file
            // do NOT do this
            inputStream.close();
        } catch (FileNotFoundException e) {
            log.error(e);
        } catch (IOException e) {
            log.error(e);
        }
    }
    
    问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。
    
    所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。
    
    

    1.1 使用 finally 代码块

    与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。

    public void closeResourceInFinally() {
        FileInputStream inputStream = null;
        try {
            File file = new File("./tmp.txt");
            inputStream = new FileInputStream(file);
            // use the inputStream to read a file
        } catch (FileNotFoundException e) {
            log.error(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e);
                }
            }
        }
    }
    

    1.2 Java 7 的 try-with-resource 语法

    如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。

    public void automaticallyCloseResource() {
        File file = new File("./tmp.txt");
        try (FileInputStream inputStream = new FileInputStream(file);) {
            // use the inputStream to read a file
        } catch (FileNotFoundException e) {
            log.error(e);
        } catch (IOException e) {
            log.error(e);
        }
    }
    
    

    2. 优先明确的异常

    你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。

    因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。

    因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。

    public void doNotDoThis() throws Exception {
        ...
    }
    public void doThis() throws NumberFormatException {
        ...
    }
    
    

    3. 对异常进行文档说明

    当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
    在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。

    /**
    
     * This method does something extremely useful ...
    
     *
    
     * @param input
    
     * @throws MyBusinessException if ... happens
    
     */
    public void doSomething(String input) throws MyBusinessException {
        ...
    }
    
    

    4. 使用描述性消息抛出异常

    在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。

    但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。

    如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被 java.lang.Long 类的构造函数抛出。

    try {
        new Long("xyz");
    } catch (NumberFormatException e) {
        log.error(e);
    }
    
    

    5. 优先捕获最具体的异常

    大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。

    但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。

    总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。

    你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有 NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。

    public void catchMostSpecificExceptionFirst() {
        try {
            doSomething("A message");
        } catch (NumberFormatException e) {
            log.error(e);
        } catch (IllegalArgumentException e) {
            log.error(e)
        }
    }
    
    

    6. 不要捕获 Throwable 类

    Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!

    如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。

    所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。

    public void doNotCatchThrowable() {
        try {
            // do something
        } catch (Throwable t) {
            // don't do this!
        }
    }
    
    

    7. 不要忽略异常

    很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。

    public void doNotIgnoreExceptions() {
        try {
            // do something
        } catch (NumberFormatException e) {
            // this will never happen
        }
    }
    

    但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。

    合理的做法是至少要记录异常的信息。

    public void logAnException() {
        try {
            // do something
        } catch (NumberFormatException e) {
            log.error("This should never happen: " + e);
        }
    }
    
    

    8. 不要记录并抛出异常

    这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:

    try {
        new Long("xyz");
    } catch (NumberFormatException e) {
        log.error(e);
        throw e;
    }
    

    这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:

    17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
    Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:589)
    at java.lang.Long.(Long.java:965)
    at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
    at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
    

    如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。

    public void wrapException(String input) throws MyBusinessException {
        try {
            // do something
        } catch (NumberFormatException e) {
            throw new MyBusinessException("A message that describes the error.", e);
        }
    }
    

    因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。

    9. 包装异常时不要抛弃原始的异常

    捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
    在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。

    public void wrapException(String input) throws MyBusinessException {
        try {
            // do something
        } catch (NumberFormatException e) {
            throw new MyBusinessException("A message that describes the error.", e);
        }
    }
    
    

    10. 不要使用异常控制程序的流程

    不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。

    11. 使用标准异常

    如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。

    12. 异常会影响性能

    异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。

    • 仅在异常情况下使用异常;
    • 在可恢复的异常情况下使用异常;

    尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。

    13. 总结

    综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。

    异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。

    异常处理-阿里巴巴Java开发手册

    1. 【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…}

    2. 【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

    3. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。

    4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

    5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。

    6. 【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式。

    7. 【强制】不要在finally块中使用return。 说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。 反例:

    private int x = 0;
    public int checkReturn() {
        try {
            // x等于1,此处不返回
            return ++x;
        } finally {
            // 返回的结果是2
            return ++x;
        }
    }
    
    1. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

    2. 【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。 说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。

    3. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。 说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。

    4. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景: 1) 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。 反例:public int f() { return Integer对象}, 如果为null,自动解箱抛NPE。 2) 数据库的查询结果可能为null。 3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 4) 远程调用返回对象时,一律要求进行空指针判断,防止NPE。 5) 对于Session中获取的数据,建议进行NPE检查,避免空指针。 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
      正例:使用JDK8的Optional类来防止NPE问题。

    5. 【推荐】定义时区分unchecked / checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。

    6. 【参考】对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。 说明:关于RPC方法返回方式使用Result方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

    7. 【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
      private boolean checkParam(DTO dto) {…}

    展开全文
  • 主要为大家详细介绍了Android异常处理最佳实践,介绍了一个优秀的app异常处理机制包括什么,感兴趣的小伙伴们可以参考一下
  • Java异常,大家都很熟悉。那么具体操作是怎么样的呢?下面从基础开始,带大家一块了解是怎么实践的。本文是关于Exception处理的一篇不错的文章,从Java Exception的概念介绍起,依次讲解了Exception的类型(Checked/...
  • Spring MVC 异常处理最佳实践

    千次阅读 2016-03-04 18:47:57
    关于Spring MVC 异常处理的最佳实践

    前言

    异常处理一直以来都是java中的一块重点难点内容,在项目中合理的处理异常一方面可以给用户带来良好的体验,另一方面也可以帮我们开发人员准确快速的定位问题,那么本篇blog就结合这两点总结一下我在Spring MVC框架的项目中的异常处理方式。

    Spring MVC 全局异常处理

    通常MVC框架都为我们提供了全局异常处理的相关API,Spring MVC自然也不例外,全局异常处理的好处是规范了异常处理的方式,在一个方法中统一的处理异常,避免了在每个方法中进行单独的异常捕获,省略了大量重复性代码,比如:try…catch、logger.error(ex)等这些代码(此处暂且不考虑用户体验)。Spring MVC提供了多种全局异常处理方式,我这里就不一一介绍了,具体可参考这篇博客:
    “使用Spring MVC统一异常处理实战” http://cgs1999.iteye.com/blog/1547197

    正如标题,我这里仅仅介绍一下我认为的best practice以及异常处理需要注意的细节,我选择的全局异常处理方式是通过自定义一个异常处理类并且继承HandlerExceptionResolver并声明为受spring管理的bean即可。回顾blog开头,一开始我提到了两点我认为的异常处理的根本目标:

    1. 当出现异常时,给用户友好的界面展示与提示。
    2. 帮我们开发人员准确快速的记录并定位问题。

    这里我们暂时先不关注第一点,首先结合第二点看看我们自定义的HandlerExceptionResolver的代码:

    package com.wl.exception;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang.StringUtils;
    import org.apache.log4j.Logger;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.wl.util.JsonUtils;
    
    /**
     * <strong>类概要: 全局异常处理类</strong> <br>
     * <strong>创建时间: 2016-3-3 下午5:50:09</strong> <br>
     * 
     * @Project raito-framework(com.wl.exception)
     * @author Wang Liang
     * @version 1.0.0
     */
    public class GlobalExceptionHandler implements HandlerExceptionResolver {
    
        private static final Logger logger = Logger.getLogger(GlobalExceptionHandler.class);
    
        @Override
        public ModelAndView resolveException(HttpServletRequest request,
                HttpServletResponse response, Object handler, Exception ex) {
            // 日志记录异常message和stacktrace
            logger.error(ex.getMessage(),ex);
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("exception", ex.getMessage());
            String header = request.getHeader("X-Requested-With");
            // 处理异步请求
            if (StringUtils.isNotBlank(header) && (header.equals("X-Requested-With") || header.equals("XMLHttpRequest"))) {
                response.setContentType("application/json;charset=UTF-8");
                PrintWriter pw = null;
                try {
                    pw = response.getWriter();
                    pw.write(JsonUtils.toJson(map));
                    pw.flush();
                    pw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (pw != null)
                        pw.close();
                }
                return null;
            }
            return new ModelAndView("error", map);
        }
    
    }
    

    通过Apache log4j记录stack trace

    首先注意34行,我们在这里通过log4j记录了exception的message和stacktrace,注意尤其是stacktrace很重要,因为只有通过它我们才能准确的定位问题,否则我们无法知道最底层是哪个类的哪一行报错了(由于log4j的logger绑定了GlobalExceptionHandler这个类,所以logger仅能记录当前类的异常位置),所以这里注意要使用logger.error(Object message,Throwable t)而不是logger.error(Object message),只有前者才能记录到stacktrace,这一点很重要:
    这里写图片描述

    同时注意一下36行的小细节,尽管我给log里面写入了stack trace,但是给ajax请求的响应中我们就没必要加入stack trace了,因为它是给用户看的,所以这里我们通过ex.getMesage() 得到异常的message即可,这样也方便自定义异常的显示(在后面的2.3中会见到)。

    下面在项目中加一行错误代码int a = 1/0; 我们来看一下log文件中记录的内容:

    2016-03-04 12:26:57 ERROR [com.wl.exception.GlobalExceptionHandler] - / by zero
    java.lang.ArithmeticException: / by zero
        at com.wl.controller.UserController.login(UserController.java:110)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1805)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:722)
    

    log的第三行已经一目了然的找到了问题根源,显然这对于一个正在生产环境运行的线上项目来说至关重要。关于上述的第二点的目标暂且就说这么多,下面继续看我们自定义的GlobalExceptionHandler这个类。

    单独处理异步请求的异常响应

    注意看一下第37行,我们这里单独判断了异步请求的异常情况,因为这是必然的,异步请求往往我们需要给客户端返回json格式的数据,异常数据也是如此。首先看一下我们的判断方式是通过request.getHeader("X-Requested-With") 来进行判断的,之所以这样做,是因为异步请求的请求报文(request message)会比一般的同步请求多一项 X-Requested-With,通过firebug可以看到:
    这里写图片描述

    它的值是XMLHttpRequest,所以我们根据这一项即可判断是否为一个ajax请求了。40行~53行记录了ajax请求的响应方式,即以json格式给客户端写回数据即可。最后别忘记将这个类声明为受Spring管理的bean,我们在Spring MVC的配置文件中声明即可:

    <bean id="exceptionHandler" class="com.wl.exception.GlobalExceptionHandler"/>

    到这里关于全局异常处理的使用方法和注意点差不多都已介绍完毕,那么接下来讨论一下上述的第一个目标,即:当出现异常时,给用户友好的界面展示与提示?

    合理设计自定义异常以及异常结构

    上面的问题正如这个小标题,但怎么样设计异常结构(何时捕获?何时抛出?)以及如何设计自定义异常才是合理的呢?其实有很多方式,关于这个话题的讨论网上也有许多,感兴趣的朋友可以继续去了解学习,这里就不一一赘述,结合主题,这里仅仅记录一下我在项目中使用的处理方式。

    首先,在Spring MVC的三层架构中,Dao层和Service层始终向上层抛出异常,由Controller层统一处理。首先我们定义一个简单的自定义异常类,用于存放异常代码和异常信息:

    package com.wl.exception;
    
    /**
     * <strong>类概要: 自定义异常类</strong> <br>
     * <strong>创建时间: 2016-3-4 下午5:44:43</strong> <br>
     * 
     * @Project raito-framework(com.wl.exception)
     * @author Wang Liang
     * @version 1.0.0
     */
    public class CustomException extends RuntimeException {
    
        private static final long serialVersionUID = 1L;
    
        public CustomException(String message, Throwable cause,
                boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
            // TODO Auto-generated constructor stub
        }
    
        public CustomException(String message, Throwable cause) {
            super(message, cause);
            // TODO Auto-generated constructor stub
        }
    
        public CustomException(String message) {
            super(message);
            // TODO Auto-generated constructor stub
        }
    
        public CustomException(Throwable cause) {
            super(cause);
            // TODO Auto-generated constructor stub
        }
    
    }
    

    比较传统的写法,我们的自定义异常仅仅重写了父类的4个构造方法,如上所述,在Controller层我们捕获异常,并通过这个自定义异常包装后再次抛出,最终交给Spring MVC的全局异常处理机制来处理即可,这样做的目的就是一开始提到的第一点:提示友好。下面贴出Controller中的一个测试方法的代码片段:

    @RequestMapping(value = "/{username}/delete", method = RequestMethod.POST)
    @ResponseBody
    public Map<String,Object> delete(@PathVariable String username) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            int a = 1/0;
            users.remove(username);
        } catch (Exception e) {
            throw new CustomException("删除失败,服务器内部错误!");
        }
        return map;
    }

    很简单,在第6行制造了一个异常(java.lang.ArithmeticException),然后我们再次捕获包装成了自定义异常,并给出了相应的提示信息,紧接着这个异常会被Spring MVC的全局异常处理机制捕获并处理,通过firebug可以看到给客户端返回的json数据:
    这里写图片描述

    同时,log4j也正确的记录了异常的stack trace:

    2016-03-04 18:13:21 ERROR [com.wl.exception.GlobalExceptionHandler] - 删除失败,服务器内部错误!
    com.wl.exception.CustomException: 删除失败,服务器内部错误!
        at com.wl.controller.UserController.delete(UserController.java:106)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1805)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:722)

    这样就完美的实现了我们最初的两个目的:

    1. 当出现异常时,给用户友好的界面展示与提示。
    2. 帮我们开发人员准确快速的记录并定位问题。

    当然,关于Spring MVC的异常处理还有许多值得研究的地方,我这里的使用方式也有许多不足,比如可以适当优化一下自定义异常类,通过定义一些枚举类型来保存异常的code和message而不是通过硬编码直接写入程序等等。

    总结

    本篇blog到此就记录完毕,关于异常处理一直都是一个非常值得探究的话题,毕竟一个项目中的异常结构设计可以直接影响到项目质量的好坏和后期的维护成本,关于blog中提到的点如果有更好的实现方式或者我写错的地方欢迎批评指正,谢谢~ The End。

    展开全文
  • Java自定义异常异常使用最佳实践

    千次阅读 2018-05-03 10:12:05
    转载自HOKING的专栏Java自定义异常异常使用最佳实践异常的分类1. 非运行时异常(Checked Exception) Java中凡是继承自Exception但不是继承自RuntimeException的类都是非运行时异常。2. 运行时异常(Runtime ...
  • .NET异常处理最佳实践

    2011-05-19 09:02:40
    .NET异常处理最佳实践为大家提供15中在.net开发时及早发现异常并进行处理的方法,能够帮助大家在进行.net开发时少走歪路。
  • Java异常处理最佳实践及陷阱防范

    万次阅读 2019-04-15 09:26:14
    所以我们要时刻注意这些陷阱以及需要一套“最佳实践”来建立起一个完善的异常处理机制。 正文 异常分类 首先,这里我画了一个异常分类的结构图。 在JDK中,Throwable是所有异常的父类,其下分为”Err...
  • 主要介绍了Java编程异常处理最佳实践【推荐】,具有一定参考价值,需要的朋友可以了解下。
  • 异常处理最佳实践

    千次阅读 2015-09-13 21:04:10
    一、异常的分类 常规分类:  1、运行时异常(RuntimeException);  2、编译时异常(CheckedException) 用途分类:  1、打断(终止)程序继续往下运行;  2、打断程序继续往下运行,并将异常原因和信息送...
  • Java 异常设计最佳实践

    千次阅读 2016-08-05 10:49:31
    关于异常在讲Java异常实践之前,先理解一下什么是异常。到底什么才算是异常呢?其实异常可以看做在我们编程过程中遇到的一些意外情况,当出现这些意外情况时我们无法继续进程正常的逻辑处理,此时我们就可以抛出一个...
  • 主要介绍了Java异常处理最佳实践及陷阱防范,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 在 Web 开发中, 我们经常会需要处理各种异常,这篇文章主要介绍了详解Spring MVC/Boot 统一异常处理最佳实践,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • SpringBoot Validation参数校验及统一异常处理最佳实践
  • Java 异常处理的 9 个最佳实践,在处理异常时可以参考。
  • 主要给大家介绍了关于Spring Boot统一异常处理最佳实践(拓展篇)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 66,651
精华内容 26,660
关键字:

异常最佳实践