java exception 本地化_Java中常见异常打印输出方式

Java中异常打印输出的常见方法总结

前言

Java异常是在Java应用中的警报器,在出现异常的情况下,可以帮助我们程序猿们快速定位问题的类型以及位置。但是一般在我们的项目中,由于经验阅历等多方面的原因,依然有若干的童鞋在代码中没有正确的使用异常打印方法,导致在项目的后台日志中,没有收到日志或者日志信息不完整等情况的发生,这些都给项目埋下了若干隐患。本文将深入分析在异常日志打印过程中的若干情况,并给出若干的使用建议。

1. Java异常Exception的结构分析

我们通常所说的Exception主要是继承于Throwable而来,可以参见如下的结构图示:

e26bb7ec0aa13fb52e50fad8972761ca.png

主要的Throwable分为异常和错误两种,然后异常Exception和错误Error做为基类,分别被具体个性化以及衍生出NullPointerException、EOFException等等异常信息类。

基于Java中的源代码来分析,Error和Exception仅仅是继承了Throwable,做了构造函数的拓展,没有进行额外方法的延展;Exception输出的主要核心方法都是定义在Throwable中的,感兴趣的童鞋可以尝试阅读JDK的源代码。

c68e69bf657a22a80023cde286db7d4b.png

下面将介绍一下关键的几个异常类方法:

1、getMessage(): String

输出异常的描述信息

2、getLocalizedMessage()

输出本地化的描述信息,一般此方法可被子类所覆盖,缺省实现与getMessage()输出信息一致

3、printStackTrace()

将异常栈打印到输出流中,此为一类方法,默认打印到console控制台,也可以显式指定输出流。

4、fillInStackTrace()

将当前的异常栈保存到一个Throwable中,返回这个Throwable。大部分情况下,用在保留异常栈嵌套调用的情况,尝试保留完整的异常栈,无需使用该方法。

2. Error vs Exception

Error在Java体系中定义为不可控制的问题,往往用来描述系统错误或者底层的问题,比如虚拟机错误,例如内存空间不足,方法调用栈溢等。我们以上图中列举出的内存溢出错误(StackOverflowError)为例,它是在JVM层面发生的错误,已经游离于java应用层之外;在应用程序层面是无法进行捕获,且无法从错误中恢复的。一般一旦发了类似问题,一般都是直接宕机,应用停止正常的工作,需要重新启动或者修复问题之后,方可重新正常工作。

Exception一般发生在应用层,即在由项目中的Java代码层面引发的问题,且可以尝试进行捕获,此类问题不会影响到应用程序的正常工作的,即不会导致宕机现象的发生。我们在工作或者代码中常见的都是Exception衍生出来的各类异常。

这里需要强调说明一下,JVM是Java语言的运行环境和平台,但是并不是Java语言体系的一个部分;在JVM平台上,还可以运行Groovy, JPython, JRuby, Closure,Scala等等遵守Java语言规范(JavaLanguage Specification)的编程语言,故我们可以将Error理解为脱离Java应用之外的问题。

3. Exception中的运行时异常(RuntimeException)和受控异常(checked exception)

运行时异常(RuntimeException)是指在运行之时发生的异常,无需显式地进行捕获;如果程序中发生类似的异常,JVM会直接抛出此类异常,并打出响应的异常栈信息。此类异常也通常被称为unchecked exception, 未受控异常。

受控异常(checked Exception)是我们最常见的异常种类,在代码中使用的异常基本上都是此类异常,此类异常会在代码编译阶段由Java编译器进行语法检查,如果未显式进行异常捕获,则会报出相应的编译异常信息。

4. 如何在代码中正确打印异常信息

下面我们将通过一系列的例子来说明上述几个Exception中方法的使用技巧。

Case 1: getMessage()/getLocalizedMessage()

public void testCase1() {

System.out.println("We are going to do something interesting....");

try {

throw new NullPointerException("I am an exception in the code.");

} catch (Exception e) {

System.out.println("We got unexpected:" + e.getMessage());

System.out.println("We got unexpected:" + e.getLocalizedMessage());

}

}

输出结果:

We are going to do testing interesting....

We got unexpected in getMessage==> I am an exception in the code.

We got unexpected in getLocalizedMessage==> I am an exception in the code.

基于结果来分析, 上述两个方法只是将异常对象中的Message打印出来,这些信息对于我们追踪问题和调试帮助有限。

Case 2:e.printStackTrace()

public void testCase2() {

System.out.println("We are going to do something interesting....");

try {

throw new Exception("I am an exception in the code.");

} catch (Exception e) {

e.printStackTrace();

}

}

运行结果:

865db8fdc3863594c392444e93a9d457.png

运行结果图

printStackTrace()可以打印出整个异常栈,但是异常栈信息将输出到默认的输出流中,绝大多数情况下是系统的控制台,而在实际项目中,都是需要将异常栈输出到日志文件的,如果不显式指定,则会丢失异常信息,在日志文件中无从追查。

Case 3: 基于log4j/slf4j等输出到日志文件

在实际项目中,一般会使用log4j/log4j2/JDK logging/slf4j/logback等日志系统来存放日志,那如何来讲日志的异常栈存入日志文件呢,我们来看示例。

public void testCase3() {

System.out.println("We are going to do something interesting....");

try {

throw new NullPointerException("abcedfeg");

} catch (Exception e) {

logger.info("Unexpected error in ", e);

}

}

我们需要到日志文件中,找到相应的异常信息,异常信息如下:

12:24:45.387 [main] INFO org.demo.TestException - Unexpected error in

java.lang.NullPointerException: abcedfeg

at org.demo.TestException.testCase3(TestException.java:39)

at org.demo.TestException.main(TestException.java:12)

我们可以发现,整个异常栈信息由两个部分组成:

>> 异常中的message, 类似getMessage()输出的信息,

>> 使用logger.info之类的方法,将异常信息写入到日志流中.

以下为log4j的声明,这里以slf4j为例来示例:

private static final Logger logger = LoggerFactory.getLogger(TestException.class);

Case 4: fillInStackTrace()

public class FillInExceptionTest {

public static void main(String[] args) {

FillInExceptionTest fit = new FillInExceptionTest();

try {

fit.outerMehtod();

} catch (Exception e) {

System.out.println("\n==========I am the one evil separation line==============");

e.printStackTrace();

}

}

public void innerMethod() throws Exception {

throw new Exception("I got exception in an inner method.");

}

public void outerMehtod() throws Exception {

try {

innerMethod(); //invoke inner method.

} catch (Exception e) {

e.printStackTrace();

throw (Exception)e.fillInStackTrace();

}

}

}

运行结果:

9c3cc4e1a9014a126205f3b91a17faa0.png

基于上述的运行结果可知,fillInStackTrace()只提取了当下的异常栈信息,而非完整的异常栈信息,这个就是此方法带给我们的特殊之处。

如果我们需要在最外层将完整的异常栈打印出来,该如何做呢? 将下述的语句:

throw (Exception)e.fillInStackTrace();

替换为:

throw e;

重新运行程序,我们就可以在最外层得到完整的异常栈信息了。

5. 总结

在本文中,我们介绍了异常类的继承体系,不同类型的异常区别与使用场景;并将基于代码示例展示了如何使用Exception的若干方法,利用这些方法来保留尽可能多的日志信息,方便我们后续针对日志中的异常信息,追查和解决问题。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对码农之家的支持。

Java如何自定义异常打印非堆栈信息详解

前言

在学习Java的过程中,想必大家都一定学习过异常这个篇章,异常的基本特性和使用这里就不再多讲了。什么是异常?我不知道大家都是怎么去理解的,我的理解很简单,那就是不正常的情况,比如我现在是个男的,但是我却有着女人所独有的东西,在我看来这尼玛肯定是种异常,简直不能忍。想必大家都能够理解看懂,并正确使用。

但是,光学会基本异常处理和使用不够的,在工作中出现异常并不可怕,有时候是需要使用异常来驱动业务的处理,例如: 在使用唯一约束的数据库的时候,如果插入一条重复的数据,那么可以通过捕获唯一约束异常DuplicateKeyException来进行处理,这个时候,在server层中就可以向调用层抛出对应的状态,上层根据对应的状态再进行处理,所以有时候异常对业务来说,是一个驱动方式。

有的捕获异常之后会将异常进行输出,不知道细心的同学有没有注意到一点,输出的异常是什么东西呢?

下面来看一个常见的异常:

java.lang.ArithmeticException: / by zero

at greenhouse.ExceptionTest.testException(ExceptionTest.java:16)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

at java.lang.reflect.Method.invoke(Method.java:597)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)

at org.junit.runners.ParentRunner.run(ParentRunner.java:236)

at org.junit.runner.JUnitCore.run(JUnitCore.java:157)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)

at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)

at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)

at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

一个空指针异常:

java.lang.NullPointerException

at greenhouse.ExceptionTest.testException(ExceptionTest.java:16)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

at java.lang.reflect.Method.invoke(Method.java:597)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)

at org.junit.runners.ParentRunner.run(ParentRunner.java:236)

at org.junit.runner.JUnitCore.run(JUnitCore.java:157)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)

at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)

at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)

at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

大家有没有发现一个特点,就是异常的输出是中能够精确的输出异常出现的地点,还有后面一大堆的执行过程类调用,也都打印出来了,这些信息从哪儿来呢? 这些信息是从栈中获取的,在打印异常日志的时候,会从栈中去获取这些调用信息。能够精确的定位异常出现的异常当然是好,但是我们有时候考虑到程序的性能,以及一些需求时,我们有时候并不需要完全的打印这些信息,并且去方法调用栈中获取相应的信息,是有性能消耗的,对于一些性能要求高的程序,我们完全可以在这一个方面为程序性能做一个提升。

所以如何避免输出这些堆栈信息呢? 那么自定义异常就可以解决这个问题:

首先,自动异常需要继承RuntimeException, 然后,再通过是重写fillInStackTrace, toString 方法, 例如,下面我定义一个AppException异常:

package com.green.monitor.common.exception;

import java.text.MessageFormat;

/**

* 自定义异常类

*/

public class AppException extends RuntimeException {

private boolean isSuccess = false;

private String key;

private String info;

public AppException(String key) {

super(key);

this.key = key;

this.info = key;

}

public AppException(String key, String message) {

super(MessageFormat.format("{0}[{1}]", key, message));

this.key = key;

this.info = message;

}

public AppException(String message, String key, String info) {

super(message);

this.key = key;

this.info = info;

}

public boolean isSuccess() {

return isSuccess;

}

public String getKey() {

return key;

}

public void setKey(String key) {

this.key = key;

}

public String getInfo() {

return info;

}

public void setInfo(String info) {

this.info = info;

}

@Override

public Throwable fillInStackTrace() {

return this;

}

@Override

public String toString() {

return MessageFormat.format("{0}[{1}]",this.key,this.info);

}

}

那么为什么要重写fillInStackTrace, 和 toString 方法呢? 我们首先来看源码是怎么一回事.

public class RuntimeException extends Exception {

static final long serialVersionUID = -7034897190745766939L;

/** Constructs a new runtime exception with null as its

* detail message. The cause is not initialized, and may subsequently be

* initialized by a call to {@link #initCause}.

*/

public RuntimeException() {

super();

}

/** Constructs a new runtime exception with the specified detail message.

* The cause is not initialized, and may subsequently be initialized by a

* call to {@link #initCause}.

*

* @param message the detail message. The detail message is saved for

* later retrieval by the {@link #getMessage()} method.

*/

public RuntimeException(String message) {

super(message);

}

/**

* Constructs a new runtime exception with the specified detail message and

* cause.

Note that the detail message associated with

* cause is not automatically incorporated in

* this runtime exception's detail message.

*

* @param message the detail message (which is saved for later retrieval

* by the {@link #getMessage()} method).

* @param cause the cause (which is saved for later retrieval by the

* {@link #getCause()} method). (A null value is

* permitted, and indicates that the cause is nonexistent or

* unknown.)

* @since 1.4

*/

public RuntimeException(String message, Throwable cause) {

super(message, cause);

}

/** Constructs a new runtime exception with the specified cause and a

* detail message of (cause==null ? null : cause.toString())

* (which typically contains the class and detail message of

* cause). This constructor is useful for runtime exceptions

* that are little more than wrappers for other throwables.

*

* @param cause the cause (which is saved for later retrieval by the

* {@link #getCause()} method). (A null value is

* permitted, and indicates that the cause is nonexistent or

* unknown.)

* @since 1.4

*/

public RuntimeException(Throwable cause) {

super(cause);

}

}

RuntimeException是继承Exception,但是它里面去只是调用了父类的方法,本身是没有做什么其余的操作。那么继续看Exception里面是怎么回事呢?

public class Exception extends Throwable {

static final long serialVersionUID = -3387516993124229948L;

/**

* Constructs a new exception with null as its detail message.

* The cause is not initialized, and may subsequently be initialized by a

* call to {@link #initCause}.

*/

public Exception() {

super();

}

/**

* Constructs a new exception with the specified detail message. The

* cause is not initialized, and may subsequently be initialized by

* a call to {@link #initCause}.

*

* @param message the detail message. The detail message is saved for

* later retrieval by the {@link #getMessage()} method.

*/

public Exception(String message) {

super(message);

}

/**

* Constructs a new exception with the specified detail message and

* cause.

Note that the detail message associated with

* cause is not automatically incorporated in

* this exception's detail message.

*

* @param message the detail message (which is saved for later retrieval

* by the {@link #getMessage()} method).

* @param cause the cause (which is saved for later retrieval by the

* {@link #getCause()} method). (A null value is

* permitted, and indicates that the cause is nonexistent or

* unknown.)

* @since 1.4

*/

public Exception(String message, Throwable cause) {

super(message, cause);

}

/**

* Constructs a new exception with the specified cause and a detail

* message of (cause==null ? null : cause.toString()) (which

* typically contains the class and detail message of cause).

* This constructor is useful for exceptions that are little more than

* wrappers for other throwables (for example, {@link

* java.security.PrivilegedActionException}).

*

* @param cause the cause (which is saved for later retrieval by the

* {@link #getCause()} method). (A null value is

* permitted, and indicates that the cause is nonexistent or

* unknown.)

* @since 1.4

*/

public Exception(Throwable cause) {

super(cause);

}

}

从源码中可以看到, Exception里面也是直接调用了父类的方法,和RuntimeException一样,自己其实并没有做什么。 那么直接来看Throwable里面是怎么一回事:

public class Throwable implements Serializable {

public Throwable(String message) {

fillInStackTrace();

detailMessage = message;

}

/**

* Fills in the execution stack trace. This method records within this

* Throwable object information about the current state of

* the stack frames for the current thread.

*

* @return a reference to this Throwable instance.

* @see java.lang.Throwable#printStackTrace()

*/

public synchronized native Throwable fillInStackTrace();

/**

* Provides programmatic access to the stack trace information printed by

* {@link #printStackTrace()}. Returns an array of stack trace elements,

* each representing one stack frame. The zeroth element of the array

* (assuming the array's length is non-zero) represents the top of the

* stack, which is the last method invocation in the sequence. Typically,

* this is the point at which this throwable was created and thrown.

* The last element of the array (assuming the array's length is non-zero)

* represents the bottom of the stack, which is the first method invocation

* in the sequence.

*

*

Some virtual machines may, under some circumstances, omit one

* or more stack frames from the stack trace. In the extreme case,

* a virtual machine that has no stack trace information concerning

* this throwable is permitted to return a zero-length array from this

* method. Generally speaking, the array returned by this method will

* contain one element for every frame that would be printed by

* printStackTrace.

*

* @return an array of stack trace elements representing the stack trace

* pertaining to this throwable.

* @since 1.4

*/

public StackTraceElement[] getStackTrace() {

return (StackTraceElement[]) getOurStackTrace().clone();

}

private synchronized StackTraceElement[] getOurStackTrace() {

// Initialize stack trace if this is the first call to this method

if (stackTrace == null) {

int depth = getStackTraceDepth();

stackTrace = new StackTraceElement[depth];

for (int i=0; i < depth; i++)

stackTrace[i] = getStackTraceElement(i);

}

return stackTrace;

}

/**

* Returns the number of elements in the stack trace (or 0 if the stack

* trace is unavailable).

*

* package-protection for use by SharedSecrets.

*/

native int getStackTraceDepth();

/**

* Returns the specified element of the stack trace.

*

* package-protection for use by SharedSecrets.

*

* @param index index of the element to return.

* @throws IndexOutOfBoundsException if index < 0 ||

* index >= getStackTraceDepth()

*/

native StackTraceElement getStackTraceElement(int index);

/**

* Returns a short description of this throwable.

* The result is the concatenation of:

*

*

the {@linkplain Class#getName() name} of the class of this object

*

": " (a colon and a space)

*

the result of invoking this object's {@link #getLocalizedMessage}

* method

*

* If getLocalizedMessage returns null, then just

* the class name is returned.

*

* @return a string representation of this throwable.

*/

public String toString() {

String s = getClass().getName();

String message = getLocalizedMessage();

return (message != null) ? (s + ": " + message) : s;

}

从源码中可以看到,到Throwable就几乎到头了, 在fillInStackTrace() 方法是一个native方法,这方法也就是会调用底层的C语言,返回一个Throwable对象, toString 方法,返回的是throwable的简短描述信息, 并且在getStackTrace 方法和 getOurStackTrace 中调用的都是native方法getStackTraceElement, 而这个方法是返回指定的栈元素信息,所以这个过程肯定是消耗性能的,那么我们自定义异常中的重写toString方法和fillInStackTrace方法就可以不从栈中去获取异常信息,直接输出,这样对系统和程序来说,相对就没有那么”重”, 是一个优化性能的非常好的办法。那么如果出现自定义异常那么是什么样的呢?请看下面吧:

@Test

public void testException(){

try {

String str =null;

System.out.println(str.charAt(0));

}catch (Exception e){

throw new AppException("000001","空指针异常");

}

}

那么在异常异常的时候,系统将会打印我们自定义的异常信息:

000001[空指针异常]

Process finished with exit code -1

所以特别简洁,优化了系统程序性能,让程序不这么“重”, 所以对于性能要求特别要求的系统。赶紧自己的自定义异常吧!

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对码农之家的支持。

以上就是本次给大家分享的关于java的全部知识点内容总结,大家还可以在下方相关文章里找到相关文章进一步学习,感谢大家的阅读和支持。

您可能感兴趣的文章:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值