14.0_[Java 异常]-异常以及异常处理机制

##################################################

目录

异常

为什么需要异常

效率低下的 if-else 异常处理

Java 的异常处理

try-catch 块

try-catch-finally 块

多重 catch 块

声明异常 throws

抛出异常

throw 抛出异常

throw 和 throws 的区别

异常的分类

如何处理 Checked 非运行时异常

在 MyEclipse 中使用开源日志记录工具 log4j

为什么需要 log4j 日志记录工具

日志是什么以及日志的分类

获取 log4j 开源项目

如何在 MyEclipse 中配置使用 log4j 记录日志

导入 log4j 的 jar 包

创建 log4j.properties 文件

编写 log4j.properties 文件以配置日志信息

在程序中使用 log4j 记录日志信息

Logger 对象

详解 log4j 配置文件

输出级别

日志输出目的地 Appender

日志布局类型 Layout

转换模式 ConversionPattern

示例写一个 log4j.properties 配置文件


##################################################

异常

——————————

为什么需要异常

        Java 通过异常机制使得程序中的业务代码与异常处理代码分离

        从而使得代码更加优雅 使程序员更专心于业务代码的编写

        我们先使用 try-catch-finlly 捕获异常

        使用 throw、throuws 抛出和生命异常

        以及异常的分类 最后介绍用于记录日志的开源框架 log4j 记录异常信息

——————————

效率低下的 if-else 异常处理

        例如 本该输入数字 输入字母的代码:

import java.util.Scanner;

public class Test {

    public static void main (String[] args) {

        Scanner in = new Scanner (System.in);

        System.out.print ( "请输入第一个数字 <<< " );
        int num_1 = in.nextInt ();
        System.out.print ( "请输入第二个数字 <<< " );
        int num_2 = in.nextInt ();

        System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
    }
}

        测试的时候我们输入数字:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< A
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Unknown Source)
        at java.util.Scanner.next(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at Test.main(Test.java:10)

C:\Users\byme\javaTest>

        我们可以看出 一旦程序出现异常就会立刻结束 我们可以通过 if-else 语句来对各种异常情况进行判断处理;

import java.util.Scanner;

public class Test {

    public static void main (String[] args) {

        Scanner in = new Scanner (System.in);

        System.out.print ( "请输入第一个数字 <<< " );
        int num_1 = 0;

        if ( in.hasNextInt() )
        /* 如果输入的是整数 */

            num_1 = in.nextInt();
        else {
        /* 如果输入的不是整数 */

            System.out.println ( " 输入的数字不为整数 程序退出.. " );
            System.exit (1);    // 程序结束执行
        }

        System.out.print ( "请输入第二个数字 <<< " );
        int num_2 = 0;

        if ( in.hasNextInt() )
        /* 如果输入的是整数 */

            num_2 = in.nextInt();
        else {
        /* 如果输入的不是整数 */

            System.out.println ( " 输入的数字不为整数 程序退出.. " );
            System.exit (1);    // 程序结束执行
        }

        System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
    }
}

        这次有了提示信息 不再中断程序:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< A
 输入的数字不为整数 程序退出..

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
C:\Users\byme\javaTest>

        但是使用 if-else 语句进行异常处理的缺点也很明显:

        代码臃肿 加入了大量的异常情况判断和处理代码

        程序员将大量精力放在了异常处理代码上 放在了堵漏洞上 减少了编写主要代码的时间 必然影响开发效率

        很难穷举所有的异常情况 程序仍旧不健壮

        异常处理代码和业务代码交织在一起 映像代码的可读性 加大日后程序的维护难度

        如果堵住漏洞的工作能右系统来处理 用户只需要关注业务代码的编写 对于异常只需要调用相关的异常处理程序即可!

——————————

Java 的异常处理

        异常处理机制就像我们对平时可能遇到的意外情况 预先想好了一些处理方法

        也就是说 在程序执行代码的时候万一发生了一场 程序会按照预定的方法处理异常

        异常处理完毕之后程序继续运行

        Java 的异常处理是通过 5 个关键字来实现的:

try

catch

finally

throw

throws

——————————

try-catch 块

        我们可以把可能出现的异常放入 try 块

        利用 catch 块捕获异常

        如下:

import java.util.Scanner;

public class Test {

    public static void main (String[] args) {

        Scanner in = new Scanner (System.in);

        try {

            System.out.print ( "请输入第一个数字 <<< " );
            int num_1 = in.nextInt();

            System.out.print ( "请输入第二个数字 <<< " );
            int num_2 = in.nextInt();

            System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
        } catch (Exception e) {

            System.out.println ( "发生异常:输入的数字不为整数!" );
            e.printStackTrace ();    // 输出异常详细信息
        }
    }
}

        运行效果如下:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< C
发生异常:输入的数字不为整数!
java.util.InputMismatchException
        at java.util.Scanner.throwFor(Unknown Source)
        at java.util.Scanner.next(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at Test.main(Test.java:15)

C:\Users\byme\javaTest>

        如果 try 块中的所有语句正常执行完毕 不会发生异常

        如果 catch 块中的所有语句都将被忽略不会被执行

        但是 try 块执行时遇到异常 并且这个异常与 catch 中声明的异常类型相匹配

        那么在 try 块中其余剩下的代码都将被忽略 而相应的 catch 块将会被执行

        匹配是指 catch 所处理的异常类型与所生成的异常类型完全一致或者是她的父类

        当在控制台提示异常信息时

        例如我们输入 C 上面的测试中代码

int num_1 = in.nextInt();

        处抛出 InputMismatchException 异常

        由于 InputMismatchException 是 Exception 的子类

        程序将忽略 try 块中的其余剩下的代码而去执行 catch 语句块

        如果我们去掉 printStackTrace 方法就不会输出详细的异常信息:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< A
发生异常:输入的数字不为整数!

C:\Users\byme\javaTest>

        如果 try 语句块在执行过程中遇到异常 而抛出的异常在 catch 块中没有被声明 那么程序就会立刻退出

        在 catch 块中可以加入用户自定义处理信息 也可以调用异常对象的方法输出异常信息

        常用的方法主要有如下两种:

    void printStackTrace()
输出异常的堆栈信息
堆栈信息包括程序运行到当前类的执行流程
她将输出从方法调用处到异常抛出处的方法调用序列
该列中的 java.util.Scanner 类中的 throwFor() 这个方法是异常抛出处
而 Test 类中的 main() 在最外层的方法调用处

    String getMessage()
返回异常信息描述字符串
该字符串描述异常产生的原因
是 printStackTrace() 输出信息的一部分

        如果 try 块在执行过程中遇到异常 那么在 try 块中其余剩下的代码都将被忽略

        系统会自动生成或相应的异常对象 包括异常的类型、异常出现时程序的运行状态以及对该异常的详细描述

        如果这个异常对象与 catch 中声明的异常类型相匹配 会把该异常对象赋給 catch 后面的异常参数

        相应的 catch 块将会被执行

    常见的异常类型:
Exception    异常层次结构的根类
ArithmeticException    算术错误情形 例如以零作为除数
AreeayIndexOutOfBoundsException    数组下标越界
NullPointerException    尝试访问 null 对象成员
ClassNotFoundException    不能加载所需的类
InputMismatchException    欲得到的数据类型与实际输入的类型不匹配
IllegalArgumentException    方法接收到非法参数
ClassCastException    对象强制类型转换出错
NumberFormatException    数字格式转换异常 例如把字符串 "abc"转换为数字!

——————————

try-catch-finally 块

        如果希望无论是否发生异常 都执行某些代码语句 该如何实现?

        此时可以在 try-catch 语句块后加入 finally 块

        把该语句放入 finally 块 无论是否发生异常 finally 块中的代码总能够被执行

        例如:

import java.util.Scanner;

public class Test {

    public static void main (String [] args) {

        Scanner in = new Scanner ( System.in );

        System.out.print ( "请输入第一个数字 <<< " );
        try {
        /* 可能出现异常的代码块 */

            int num_1 = in.nextInt();
            System.out.print ( "请输入第二个数字 <<< " );
            int num_2 = in.nextInt ();
            System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );
        } catch (Exception e) {
        /* 异常处理块 */

            System.out.println ( "发生异常:输入的数字不为整数!" );
            System.out.println ( e.getMessage () );    // 输出异常描述
        } finally {
        /* 无论是否正常结束都将执行该代码块 */

            System.out.println ( "程序结束.." );
        }
    }
}

        如果 try 块所有的语句正常执行完毕 那么 finally 块就会被执行 不会执行 catch 语句块中的代码

        如果 try 语句块在执行过程中碰到异常 无论这种异常能否被 catch 块捕获到 都将执行 finally 块中的代码

        例如我们在输入字母时 try 块会抛出异常 进入 catch 语句块 最后 finally 块中的代码也将被执行:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
程序结束..

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< B
发生异常:输入的数字不为整数!
null
程序结束..

C:\Users\byme\javaTest>

        try-catch-finally 结构中 try 块是必需的

        catch 和 finally 为可选 但两者至少需要出现一个

        特别需要注意的是 即使在 try 块和 catch 块中存在 return 语句

        finally 块中语句也会被执行

        发生异常时的执行顺序:

执行 try 块或 catch 中 return 之前的语句

执行 finally 块中的语句

执行 try 块或 catch 中的 return 语句退出

        示例代码:

import java.util.Scanner;

public class Test {

    public static void main (String [] args) {

        Scanner in = new Scanner ( System.in );

        System.out.print ( "请输入第一个数字 <<< " );
        try {

            int num_1 = in.nextInt();
            System.out.print ( "请输入第二个数字 <<< " );
            int num_2 = in.nextInt ();
            System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );

            return;    // finally 块仍然会被执行
        } catch (Exception e) {

            System.out.println ( "发生异常:输入的数字不为整数!" );

            return;    // finally 块仍然会被执行
        } finally {

            System.out.println ( "程序结束.." );
        }
    }
}

        测试如下:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< A
发生异常:输入的数字不为整数!
程序结束..

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
程序结束..

C:\Users\byme\javaTest>

        finally 块唯一不被执行的情况在异常处理代码中执行

System.exit (1);

        将退出 Java 虚拟机

        如下:

import java.util.Scanner;

public class Test {

    public static void main (String [] args) {

        Scanner in = new Scanner ( System.in );

        System.out.print ( "请输入第一个数字 <<< " );
        try {

            int num_1 = in.nextInt();
            System.out.print ( "请输入第二个数字 <<< " );
            int num_2 = in.nextInt ();
            System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );
        } catch (Exception e) {

            System.out.println ( "发生异常:输入的数字不为整数!" );

            System.exit (1);;    // finally 块中语句唯一不会被执行的情况
        } finally {

            System.out.println ( "程序结束.." );
        }
    }
}

        测试如下:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< 2
1 + 2 = 3
程序结束..

C:\Users\byme\javaTest>java Test
请输入第一个数字 <<< 1
请输入第二个数字 <<< B
发生异常:输入的数字不为整数!

C:\Users\byme\javaTest>

——————————

多重 catch 块

        如果遇到多种异常情况 我们还可以使用多重 catch 块分别捕获

        一段代码可能会引起多种类型的异常 这时可以在一个 try 语句块后跟多个 catch 语句块 分别处理不同的异常

        但排列顺序必须是从子类到父类 最后一个一般都是 Exception 类

        因为所有的异常子类都是继承自 Exception 类

所以如果将父类放到前面 那么所有的异常都将被捕获!后面的 catch 块中的子类异常将得不到被执行的机会

        运行时 系统从上到下分别对每个 catch 语句块处理的异常类型进行检测 并执行第一个与异常类型匹配的 catch 语句

        执行其中的一条 catch 语句之后 其后的 catch 语句都将被忽略

        Test.java code:

import java.util.Scanner;
import java.util.InputMismatchException;    /* 需要导包 */

public class Test {

    public static void main (String [] args) {

        Scanner in = new Scanner ( System.in );

        try {

            System.out.print ( "请输入被除数 <<< " );
            int num_1 = in.nextInt();
            System.out.print ( "请输入除数 <<< " );
            int num_2 = in.nextInt ();
            System.out.printf ( "%d / %d = %d\n", num_1, num_2, num_1 / num_2 );
        } catch (InputMismatchException e) {

            System.out.println ( "被除数和除数必须是整数!" );
        } catch (ArithmeticException e) {

            System.out.println ( "除数不能为零!" );
        } catch (Exception e) {

            System.out.println ( "发生未知异常!" );
        } finally {

            System.out.println ( "程序结束.." );
        }
    }
}

        测试结果:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 9
请输入除数 <<< 3
9 / 3 = 3
程序结束..

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 9
请输入除数 <<< 0
除数不能为零!
程序结束..

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 9
请输入除数 <<< Q
被除数和除数必须是整数!
程序结束..

C:\Users\byme\javaTest>

        如果一切正常 最后也会执行 finally 块

        如果输入的不是整数 系统会抛出 InputMismatchException 异常对象 因此进入第一个 catch 语句块执行其中的代码 其她的 catch 将被忽略

        如果被除数为 0 系统会抛出 ArithmeticException 异常对象 进入第二个 catch 块执行代码 其她的 catch 块将被忽略

一定要记住 多重 catch 块的排列顺序必须是从子类到父类 最后一个一般都是 Exception 类

——————————

声明异常 throws

        如果在一个方法体中抛出了异常 我们就希望调用者能够及时地捕获异常

        那么如何通知调用者呢?

        Java 语言中通过关键字 throws 声明某个方法可能抛出的各种异常

        throws 可以同时声明多个异常 之间用逗号隔开

        我们可以在计算输出商的过程封装在 divide 方法中

        并在方法的参数列表后通过 throws 声明了异常 然后再 main() 方法中调用该方法

        此时 main() 就知道 divide() 方法中抛出了异常

        可以采用如下两种方式处理:

通过 try-catch 捕获并处理异常

通过 throws 继续声明异常 如果调用者不打算处理该异常 可以继续通过 throws 声明异常让上一级调用者处理异常 main() 声明的异常将由 Java 虚拟机来处理

        code_1:

import java.util.Scanner;

public class Test {

    public static void main (String [] args) {
    /* 通过 try-catch 捕获并处理异常 */

        try {

            divide ();
        } catch (Exception e) {

            System.out.println ( "被除数和除数必须为整数且除数不能为零!" );
            e.printStackTrace ();
        }
    }

    public static void divide() throws Exception {
    /* 输入被除数和除数 计算商并输出 */

        Scanner in = new Scanner ( System.in );
        System.out.print ( "请输入被除数 <<< " );
        int num_1 = in.nextInt();
        System.out.print ( "请输入除数 <<< " );
        int num_2 = in.nextInt ();
        System.out.printf ( "%d / %d = %d\n", num_1, num_2, (num_1 / num_2) );   
    }
}

        demo_1:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 10
请输入除数 <<< 5
10 / 5 = 2

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 10
请输入除数 <<< 0
被除数和除数必须为整数且除数不能为零!
java.lang.ArithmeticException: / by zero
        at Test.divide(Test.java:26)
        at Test.main(Test.java:10)

C:\Users\byme\javaTest>

        code_2:

import java.util.Scanner;

public class Test {

    public static void main (String [] args) throws Exception {
    /* 通过 throws 继续声明异常 */

        divide ();
    }

    public static void divide() throws Exception {

        Scanner in = new Scanner ( System.in );
        System.out.print ( "请输入被除数 <<< " );
        int num_1 = in.nextInt();
        System.out.print ( "请输入除数 <<< " );
        int num_2 = in.nextInt ();
        System.out.printf ( "%d / %d = %d\n", num_1, num_2, (num_1 / num_2) );   
    }
}

        demo_2:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 8
请输入除数 <<< 2
8 / 2 = 4

C:\Users\byme\javaTest>java Test
请输入被除数 <<< 8
请输入除数 <<< 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.divide(Test.java:18)
        at Test.main(Test.java:8)

C:\Users\byme\javaTest>

##################################################

抛出异常

——————————

throw 抛出异常

        我们介绍了很多捕获异常的知识 既然可以捕获到各种类型的异常 那么这些异常是在什么地方抛出的呢?

        除了系统自动抛出异常外 在编程过程中我们往往会发现有些问题是系统无法自动发现并解决的

        此时需要程序员而不是系统来自行抛出异常 把问题提交給调用者去解决

        在 Java 中可以使用 throw 关键字来自行抛出异常

        例如 无法解决参数问题 因此在方法内通过 throw 抛出异常 把问题交給调用者去解决 在调用该方法者中捕获并处理异常:

class Person {
/* 人类 */

    private String name = "姓名";
    private int age = 0;
    private String sex = "男";

    public void setSex (String sex) throws Exception {
    /* 设置性别 声明异常 Exception */

        if ( "男".equals(sex) || sex.equals("女") )
        /* 条件只能为男或女 */
            this.sex = sex;
        else
        /* 将异常提交給调用者处理 */
            throw new Exception ( "性别必须为 男/女 !" );
    }

    public void print () {
    /* 输出基本信息 */

        System.out.printf ( "%s 的年龄为 %d 性别为 %s\n", this.name, this,age, this.sex );
    }
}

public class Test {
/* 测试类 */

    public static void main (String[] args) {

        try {

            Person person = new Person();
            person.setSex ( "不男不女" );    // 故意給错误参数
            person.print ();
        } catch (Exception e) {

            e.printStackTrace ();
        }
    }
}

        测试结果如下:

C:\Users\byme\javaTest>javac Test.java

C:\Users\byme\javaTest>java Test
java.lang.Exception: 性别必须为 男/女 !
        at Person.setSex(Test.java:15)
        at Test.main(Test.java:33)

C:\Users\byme\javaTest>

%%%%%

throw 和 throws 的区别

        作用不同:

throw 用于在程序中抛出异常

throws 用于声明在该方法内抛出了异常

        使用的位置不同:

throw 位于方法体内部 可以作为单独语句使用

throws 必须跟在方法参数列表后面 不能单独使用

        内容不同:

throw 抛出一个异常对象 而且只能是一个

throws 后面跟异常类 而且可以跟多个异常类

——————————

异常的分类

        Java 的异常体系包括许多异常类 她们之间存在继承关系

        Java 的异常体系结构如下:

Object
    Throwable
        Error
            AWTError
            ThreadDeath
            ...
        Exception
            RuntimeException
                ArithmeticException
                NullPointerException
                NumberFormatException
                ...
            SQLException
            ClassNotFoundException

        Throwable 类:

        所有的异常类型都是 Throwable 类的子类

        她派生两个子类 即 Error 和 Exception 两个类

        Error 类:

        Error 类表示仅靠程序本身无法恢复的严重错误 例如内存溢出动态链接失败、虚拟机错误

        应用程序不应该抛出这种类型的对象 一般都是由虚拟机抛出

        假如出现这种错误 除了尽力使得程序安全退出以外在其她方面是无能为力的

        所以在进行程序设计时应该更关注 Exception 类

        Exception 类:

        此类由 Java 应用程序抛出和处理的非严重错误!

        例如

所需文件在找不到

网络连接不通或中断

算术运算出错例如被零除

数组下标越界

装载了一个存在的类

对 null 对象操作

类型转换异常

        等

        她的各种不同地方子类分别对应不同类型的异常

        运行时异常:

        包括 RuntimeException 及其所有子类 不要求程序必须对她们做出处理

        例如 ArithmeticException 异常和 InputMismatchException 异常

        在程序中并没有使用 try-catch 或 throws 进行处理 仍旧可以进行编译和运行

        如果运行时发生异常会输出异常的堆栈信息并中止程序运行

       非运行时异常:

        Checked 异常即非运行时异常

        除了运行时异常外的其她由 Exception 继承来的异常类

        程序必须捕获或者声明抛出这种异常 否则会出现编译错误 导致无法通过编译!

        处理方式包括两种:

通过 try-catch 在当前位置捕获并处理异常

通过 throws 声明抛出异常交給上一级调用方法处理

%%%%%

如何处理 Checked 非运行时异常

        示例不处理 Checked 异常 code:

import java.io.*;

public class Test {

    public static void main (String[] args) {

        /* 创建指定文件的流 */
        FileInputStream fis = null;
        fis = new FileInputStream ( new File ( "temp.txt" ) );

        fis.close ();    // 关闭指定文件的流
    }
}

        在该代码中由于没有对 Checked 异常进行处理 无法通过编译:

C:\Users\byme\javaTest>javac Test.java
Test.java:9: 错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明
以便抛出
        fis = new FileInputStream ( new File ( "temp.txt" ) );
              ^
Test.java:11: 错误: 未报告的异常错误IOException; 必须对其进行捕获或声明以便抛出
        fis.close ();    // 关闭指定文件的流
                  ^
2 个错误

C:\Users\byme\javaTest>

        这就是没有处理 Checked 异常

        下面就对 Checked 异常进行处理 使其可以正常通过编译 code:

import java.io.*;

public class Test {

    public static void main (String[] args) {

        FileInputStream fis = null;

        try {

            fis = new FileInputStream ( new File ( "temp.txt" ) );    // 创建指定文件的流
        } catch (FileNotFoundException e) {

            System.err.println ( "无法找到指定的文件!" );    // 异常中推荐使用 err 错误输出而不是 out 正常输出

            e.printStackTrace ();
        }

        try {

            fis.close ();    // 关闭指定文件的流
        } catch (IOException e) {

            System.err.println ( "关闭指定文件输入流时出现异常!" );

            e.printStackTrace ();
        }
    }
}

        测试过程:

C:\Users\byme\javaTest>javac Test.java    /* 编译通过 */

C:\Users\byme\javaTest>java Test    /* 运行成功 只是没有指定文件 */
无法找到指定的文件!
java.io.FileNotFoundException: temp.txt (系统找不到指定的文件。)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(Unknown Source)
        at Test.main(Test.java:11)
Exception in thread "main" java.lang.NullPointerException
        at Test.main(Test.java:21)

C:\Users\byme\javaTest>dir > temp.txt    /* 管道创建一个文件 */

C:\Users\byme\javaTest>java Test    /* 再次运行成功执行没有错误 */

C:\Users\byme\javaTest>

##################################################

在 MyEclipse 中使用开源日志记录工具 log4j

——————————

为什么需要 log4j 日志记录工具

        如果我们不希望在控制台上输出相应信息 而是以文件形式记录这些异常信息

        甚至记录程序正常运行的关键步骤信息 以便日后查看 这种情况该如何处理呢?

        很显然我们可以自行编程实现这一效果

        但是从更注重效率和性能方面考虑 还有一个更好的选择 那就是使用流行的开源项目

log4j

        在 MyEclipse 中使用 log4j 的步骤比较简单 主要分为 4 步:

在项目中加入 log4j 所使用的 JAR 文件

创建 log4j.properties 文件

编写 log4j.properties 文件

在程序中使用 log4j 记录日志信息

——————————

日志是什么以及日志的分类

        软件运行的过程离不开日志

        日志主要用来记录系统运行过程中的一些重要的操作信息

        便于监视系统运行情况 帮助用户提前发现和避开可能出现的问题

        或者出现问题后根据日志找到发生的原因

        日志根据记录的内容不同 主要分为如下三类:

    SQL 日志
机制系统执行的 SQL 语句

    异常日志
记录系统运行中发生的异常事件

    业务日志
记录系统运行过程例如用户登录、操作记录等

        log4j 是一个非常优秀的 日志/log 记录工具

        通过使用 log4j 我们可以控制日志的输出级别以及日志信息输送的目的地如控制台或文件中

        还可以控制每条日志的输出格式

——————————

获取 log4j 开源项目

        要使用 log4j 首先需要下载 log4j 的 JAR 文件

        log4j 是 Apache 的一个开源项目 官网网站是

https://logging.apache.org/log4jicon-default.png?t=N7T8https://logging.apache.org/log4j
        这里使用的为 log4j 1.2.17 版本 项目地址为:

Apache log4j 1.2 -icon-default.png?t=N7T8https://logging.apache.org/log4j/1.2/        下载地址为:

        注意不要使用浏览器下载 不然下载文件会被损坏无法打开不知道是为什么……

        但是用迅雷等工具下载就没有问题……

Apache log4j 1.2 - Download Apache log4j 1.2icon-default.png?t=N7T8https://logging.apache.org/log4j/1.2/download.html        网站截图:

        好人在这儿:

pan.baidu.com/s/1kW1Jx1aWP2kV9K4yDUBNTQ
1024

        这里因为 Java 的版本较低所以使用了 1.2.17 旧版本

        解压如下 这边我直接放在 C 盘根目录下了

        其中 log4j-1.2.17.jar 就是我们需要的 log4j 的 JAR 包:

C:\apache-log4j-1.2.17

         site 目录下的 manual.html 是使用手册的离线网页:

manual.html

        site 目录下的 apidocs 子目录中的 index.html 是 JavaDoc/APIDocs :

index.html

——————————

如何在 MyEclipse 中配置使用 log4j 记录日志

%%%%%

导入 log4j 的 jar 包

        在 MyEclipse 10.0 中新建了一个 Main 项目:

Main

        依次选择

Project/项目

properties/属性

Java Build Path/Java构建路径

Libraries

Add External JARs../添加外部 JAR 。。

        选中要加入 log4j 的项目 这里选中 Main 后选择菜单栏的 项目 中的 属性

选择属性

        或者右击项目选择属性:

选择属性

        选择构建路径:

构建路径

        选择 库 选项卡:

库

        选择添加外部 JAR 打开后选择 jar 包:

log4j-1.2.17.jar

        点击打开:

点击打开

        点击确定添加成功:

添加成功

%%%%%

创建 log4j.properties 文件

        使用 log4j 需要创建 log4j.properties 文件

        此文件专门用来配置日志信息

        例如输出级别、输出目的地、输出格式等

        选择要使用 log4j 的项目

        右击 src 依次选择

New/新建

File/文件

新建文件

        打开 New File 对话框:

New File

        输入文件名

log4j.properties

        后点击 Finish 完成:

完成

        创建成功:

创建成功

        将中间左下角的 Properties 模式改成 Source 模式 就可编写这个文件了!

%%%%%

编写 log4j.properties 文件以配置日志信息

        根据配置 将在控制台和文件中同时记录日志信息 这里的日志文件的名字是

JavaMainLog.log

        log4j.properties code:

### 设置 Logger 输出级别和输出目的地 ###
log4j.rootLogger = debug, stdout, logfile

### 把日志信息输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdount.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout

### 把日志信息输出到文件 ###
log4j.appender.logfile = org.apache.log4j.FileAppender
log4j.appender.logfile.File = JavaMainLog.log
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

%%%%%

在程序中使用 log4j 记录日志信息

        现在我们就可以在程序中使用 logj 了!

        测试代码如下:

package mmain;

import java.util.Scanner;
import org.apache.log4j.Logger;    /* 在此处导入 log4j */
import java.util.InputMismatchException;

public class Main {

    private static Logger logger = Logger.getLogger ( Main.class.getName() );    /* 首先创建一个私有静态的 Logger 对象以后就可以通过该对象的 debug() 或者 error() 等方法输出日志信息了!!! */

    public static void main (String [] args) {

        try {

            Scanner in = new Scanner ( System.in );

            System.out.print ( "请输入被除数 <<< " );
            int num_1 = in.nextInt();

            logger.debug ( "输入被除数 >>> " + num_1 );

            System.out.print ( "请输入除数 <<< " );
            int num_2 = in.nextInt ();

            logger.debug ( "输入除数 >>> " + num_2 );

            System.out.printf ( "%d / %d = %d\n", num_1, num_2, num_1 / num_2 );

            logger.debug ( "输出运算结果 >>> " + String.format ( "%d / %d = %d", num_1, num_2, num_1 / num_2 ) );
        } catch (InputMismatchException e) {

            logger.error ( "被除数和除数必须是整数!", e );    // 又一种错误输出方
        } catch (ArithmeticException e) {

        	logger.error ( e.getMessage () );    // 输出异常方法返回值
        } catch (Exception e) {

        	logger.error ( e.getMessage () );
        } finally {

            System.out.println ( "程序结束.." );
        }
    }
}

        正常运行的时候发现 log4j 出现意外错误 但是不妨碍运行:

运行结果

log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
请输入被除数 <<< 9
DEBUG - 输入被除数 >>> 9
请输入除数 <<< 3
DEBUG - 输入除数 >>> 3
9 / 3 = 3
DEBUG - 输出运算结果 >>> 9 / 3 = 3
程序结束..

        日志文件我的默认路径为:

C:\Users\用户名\Workspaces\MyEclipse 10\项目名\日志文件

        反转都是在项目目录下 自己可以找一找 我的是

C:\Users\byme\Workspaces\MyEclipse 10\Main\JavaMainLog.log

C:\Users\byme\Workspaces\MyEclipse 10\Main\JavaMainLog.log

        查看日志文件 JavaMainLog.log 内容:

JavaMainLog.log
JavaMainLog.log

        试试零除报错:

零除报错

log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
请输入被除数 <<< 5211314
DEBUG - 输入被除数 >>> 5211314
请输入除数 <<< 0
DEBUG - 输入除数 >>> 0
ERROR - / by zero
程序结束..

        再试试其她错误:

其她错误

log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
请输入被除数 <<< 1
DEBUG - 输入被除数 >>> 1
请输入除数 <<< A
ERROR - 被除数和除数必须是整数!
java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:840)
	at java.util.Scanner.next(Scanner.java:1461)
	at java.util.Scanner.nextInt(Scanner.java:2091)
	at java.util.Scanner.nextInt(Scanner.java:2050)
	at mmain.Main.main(Main.java:23)
程序结束..

        打开日志文件查看:

日志文件

——————————

Logger 对象

        Logger 对象是用来替代 System.out 或 System.err 的日志记录器 供程序员输出日志信息

        她提供了一系列方法来输出不同级别的日志信息:

    调试级别的异常情况:
public void debug (Object msg)
public void debug (Object msg, Throwable t)
    例如:
用于记录程序变量
多次迭代中的变量
方法/运算 结果
替代代码中的注释

    运行过程中的强调异常:
public void info (Object msg)
public void info (Object msg, Throwable t)
    例如:
系统运行信息
serviceimpl 方法的出入口
主要逻辑分步骤
外部接口部分
客户端请求参数和返回给客户端的结果
调用第三方的调用参数和调用结果

    不应该出现但是不影响程序,当前请求正常运行的异常情况:
public void warn (Object msg)
public void warn (Object msg, Throwable t)
    例如
有容错机制的时候出现错误情况
找不到配置文件但是系统能自动创建配置文件
即将接近临界值的时候例如缓存池占用达到警告线

    影响程序运行,当前请求正常运行的异常情况:
public void error (Object msg)
public void error (Object msg, Throwable t)
    例如
打开配置文件失败
第三方网络连接异常
sqlException 等不应该出现的情况
某个 serviceimpl 的方法返回的 list 集合应该有元素却获取到一个空集合 list
字符转换的时候报错显示无 GBK 字符集

    主要用于系统运行中的完整信息:
public void fatal (Object msg)
public void fatal (Object msg, Throwable t)
    例如
完整的 http request 和 http response

——————————

详解 log4j 配置文件

        log4j.properties code:

### 设置 Logger 输出级别和输出目的地 ###
log4j.rootLogger = debug, stdout, logfile

### 把日志信息输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdount.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout

### 把日志信息输出到文件 ###
log4j.appender.logfile = org.apache.log4j.FileAppender
log4j.appender.logfile.File = JavaMainLog.log
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

%%%%%

输出级别

        这段代码

### 设置 Logger 输出级别和输出目的地 ###
log4j.rootLogger = debug, stdout, logfile

        语法如下:

log4j.rootLogger = 输出级别, 目的地

        其中 debug 指的是 日志记录器/Logger 的输出级别

        主要输出级别及含义如下:

fatal    指出严重的错误事件将会导致应用程序的退出
error    指出虽然发生错误事件但是仍然不影响系统的继续运行
warn    表明会出现潜在错误的情形
info    在粗粒度级别上指明消息 强调应用程序的运行过程
debug    指出细粒度信息事件 对调试应用程序是非常有帮助的

        各个输出级别优先级如下:

fatal > error ? warn > info > debug

        日志记录器 Logger 将只输出那些级别高于或等于她的信息

        例如级别为 debug 将输出 fatal、error、warn、info、debug 级别的日志信息

        而级别为 error 将只输出 fatal、error 级别的日志信息

%%%%%

日志输出目的地 Appender

        语法如下:

log4j.rootLogger = 输出级别, 目的地

        示例代码为:

### 设置 Logger 输出级别和输出目的地 ###
log4j.rootLogger = debug, stdout, logfile

        其中 stdout、logfile 指的是日志输出目的地的名字

        log4j 允许记录日志到多个输出目的地 一个输出目的地被称为一个 Appender

        log4j 中最常用的 Appender 有如下两种:

        ConsoleAppender 输出日志到控制台

通过 Target 属性配置输出到 System.out 或 System.err 默认的目标是 System.out

        FileAppender 输出日志到文件

输出日志事件到一个文件 通过 File 属性配置文件的路径以及名称

        示例代码中有两个 Appender

### 把日志信息输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdount.Target = System.err

### 把日志信息输出到文件 ###
log4j.appender.logfile = org.apache.log4j.FileAppender
log4j.appender.logfile.File = JavaMainLog.log

        第一个命名为 stdout 使用了 ConsoleAppender

        通过配置 Target 属性把日志信息写到控制台 System.err

        第二个 Appender 命名为 logfile 使用了 FileAppender

        通过配置 File 属性将日志信息写到指定的文件 JavaMainLog.log 中 这边没有指定路径 所以放在项目根目录下了

%%%%%

日志布局类型 Layout


        Appender 必须使用一个与之相关联的布局类型 Layout 用来指定输出样式

        log4j 中最常用的 Layout 有如下 3 种:

        HTMLLayout

格式化输出为 HTML 表格

        SimpleLayout

以一种非常简单的方式格式化日志输出

她的输出级别为 Level

然后跟着一个破折号 —— 最后是日志消息

        PatternLayout

根据指定的转换模式格式化日志输出

从而支持丰富多样的输出格式

需要配置 layout.ConversionPattern 属性

若没有配置该模式将使用默认的转换模式

        示例代码:

### 设置 Logger 输出级别和输出目的地 ###
log4j.rootLogger = debug, stdout, logfile

### 把日志信息输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdount.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout

### 把日志信息输出到文件 ###
log4j.appender.logfile = org.apache.log4j.FileAppender
log4j.appender.logfile.File = JavaMainLog.log
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

        第一个控制台 Appender 输出使用了 SimpleLayout

        第二个文件 Appender 输出使用了 PatternLayout 需要配置 layout.ConversionPattern 属性来自定义输出格式

%%%%%

转换模式 ConversionPattern

        对于 PatternLayout 来说需要配置 ConversionPattern 属性

        常用的配置参数及其含义如下:

%d    用来设置输出日志的日期和时间 默认格式为 ISO8601 也可以在其后指定格式 比如 %d { yyyy-MM-dd HH:mm:ss } 输出格式类似于 2022-06-28 15:43:30
%m    用来输出代码中指定的消息
%n    用来输出一个回车换行符
%l    用来输出日志事件的发生位置 包括类名、发生的线程以及在代码中的行数 例如如果输出为 mmain.Main.main(Main.java:23) 说明日志事件发生在 mmain 包下的 Main 类里的 maini 方法中 在代码中的行数为第 23 行
%P    用来输出优先级 即 debug、info、warn、error、fatal 等
%F    用来输出文件名
%M    用来输出方法名

%%%%%

示例写一个 log4j.properties 配置文件

        log4j.properties 文件要求如下:

日志级别为 debug

输出目的地名字为 stdout

布局类型为 PatternLayout

使用 ConversionPattern 配置输出格式 至少输出异常日期、完整的异常堆栈信息

        示例如下:

log4j.properties

log4j.rootLogger = debug, stdout

log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdount.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d %l %m %n

        我终于知道错在哪里了 是 l/L 不是 1/数字 !!!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燃吹

呜呜呜没钱钱吃饭了……

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值