Skip to content

代理(Proxy)

作用:运行时创建一个实现了一个给定接口的新类

1、何时使用代理

编译时无法确定需要实现哪个接口时

代理类可以在运行时创建全新的类,代理类能够实现指定的接口,并且具有下列方法:

  • 指定接口所需要的全部方法。
  • Object 类中的全部方法,例如,toString、equals等。

然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:

public Object invoke(Object proxy, 
                        Method method, 
                        Object[] args) 
    throws Throwable;

无论何时调用代理对象的方法,调用处理器的 invoke 方法都会被调用,并向其传递 Method 对象和原始的调用参数。调用处理器必须给出处理调用的方式。

2、如何创建代理对象

要想创建一个代理对象,需要使用 Proxy 类的 newProxyInstance 方法。

public static Object newProxyInstance(ClassLoader loader,//类加载器,用null表示使用默认的类加载器。
                                          Class<?>[] interfaces,//Class对象数组,每个元素都是需要实现的接口
                                          InvocationHandler h)//调用处理器
        throws IllegalArgumentException

3、使用代理的原因:

  1. 路由对远程服务器的方法调用。
  2. 在程序运行期间,将用户接口事件与动作关联起来。
  3. 为调试,跟踪方法调用。

4、代理类的特性

代理类是在程序运行过程中创建出来的,一旦被创建,就变成了常规类

  • 所有的代理类都扩展于 Proxy 类

  • 个代理类只有一个实例域——调用处理器,它定义在 Proxy 类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。

  • 对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类
  • 代理类一定是 public 和 final。如果代理类实现的所有接口都是 public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。 可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。
  • 所有的代理类都覆盖了Object类中的方法toStringequalshashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clonegetClass)没有被重新定义。
  • 没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。

断言(assert)

1、什么是断言?

在测试期间向代码中插入一些检查语句。当代码发布时,这些插入的检测语句将会被自动移除

2、如何使用

1)assert 条件;

2)assert 条件 : 表达式;//表达式将被传入AssertionError的构造器,并转换成一个消息字符串

当条件不对时,抛出一个 AssertionError 异常

在默认情况下,断言被禁用。

  • 可以在运行程序时用-enableassertions-ea选项启用,可以在某个类或整个包中使用断言
  • 可以用选项-disableassertions-da禁用某个特定类和包的断言

在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器(class loader)的功能。当断言被禁用时,类加载器将跳过断言代码,因此,不会降低程序运行的速度。

有些类不是由类加载器加载,而是直接由虚拟机加载。可以使用这些开关有选择地启用或禁用那些类中的断言。

启用和禁用所有断言的-ea-da开关不能应用到那些没有类加载器的“系统类”上。对于这些系统类来说,需要使用-enablesystemassertions / -esa开关启用断言。

3种处理系统错误的机制:

  • 抛异常
  • 日志
  • 使用断言

断言是一种测试和调试阶段所使用的战术性工具;而日志记录是一种在程序的整个生命周期都可以使用的策略性工具

3、什么时候应该选择使用断言呢?

  • 断言失败是致命的、不可恢复的错误,不应该使用断言向程序的其他部分通告发生了可恢复性的错误
  • 断言检查只用于开发和测阶段,应该用于在测试阶段确定程序内部的错误位置

由于可以使用断言,当方法被非法调用时,将会出现难以预料的结果

日志

1、场景再现:

每个Java程序员都很熟悉在有问题的代码中插入一些System.out.println方法调用来帮助观察程序运行的操作过程。当然,一旦发现问题的根源,就要将这些语句从代码中删去。如果接下来又出现了问题,就需要再插入几个调用System.out.println方法的语句。

记录日志API就是为了解决这个问题而设计的。

记录日志的常见用途是记录那些不可预料的异常

日志记录并不将消息发送到控制台上,如果想在控制台显示,则需要设置配置文件(位于 jre/lib 目录下)

所有级别为INFO、WARNING和SEVERE的消息都将显示到控制台上。因此,最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为FINE是一个很好的选择。

2、记录日志 API 的优点

  • 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
  • 可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
  • 日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
  • 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项
  • 日志记录可以采用不同的方式格式化,例如,纯文本或XML。
  • 应用程序可以使用多个日志记录器,它们使用类似包名的这种具有层次结构的名字,例如,com.mycompany.myapp。
  • 在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。

3、简单使用

要生成简单的日志记录,可以使用全局日志记录器(global logger)并调用其info方法

不要将所有的日志都记录到一个全局日志记录器中,而是自定义日志记录器。可以调用getLogger方法创建或获取记录器,未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,一个静态变量存储日志记录器的一个引用

4、过滤器

在默认情况下,过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。另外,可以通过实现Filter接口并定义isLoggable方法来自定义过滤器。

public boolean isLoggable(LogRecord record);

在这个方法中,可以利用自己喜欢的标准,对日志记录进行分析,返回true表示这些记录应该包含在日志中。

要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用setFilter方法就可以了。注意,同一时刻最多只能有一个过滤器。

5、格式化器

ConsoleHandler类和FileHandler类可以生成文本和XML格式的日志记录。也可以自定义格式,只需要扩展Formatter类并覆盖下面这个方法:

public abstract String format(LogRecord record);

调用setFormatter方法将格式化器安装到处理器中

调试技巧

1、打印或记录任意变量的值

print一下;
或者
Logger.getGlobai().info("x=" + x);
Logger.getGlobai().info("this=" + this);//查看隐式参数对象的状态

2、在每一个类中放置一个单独的main方法,这样可以对每一个类进行单元测试。

3、使用JUnit

4、日志代理(logging proxy),截获方法调用,并进行日志记录,然后调用超类中的方法

5、利用Throwable类提供的printStackTrace方法

6、将一个程序中的错误信息保存在一个文件中

7、要想观察类的加载过程,可以用-verbose标志启动Java虚拟机

8、-Xlint选项告诉编译器对一些普遍容易出现的代码问题进行检查

9、jmap实用工具

参考文献:

《Java核心技术》