暂且先将AOP这个英文缩写名词放到一边,先从一个简单常见的例子来看一个议题,这个例子当中含有日志(Logging)动作,程序中很常需要为某些动作或事件作下记录,以便在事后检视程序运作过程或是作为除错时的信息。
来看一个最简单的例子,当您需要在执行某些方法时留下日志讯息,直觉的,您可能会如下撰写:
package onlyfun.caterpillar;
import java.util.logging.*;
public class HelloSpeaker {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public void hello(String name) {
// 方法执行开始时留下日志
logger.log(Level.INFO, "hello method starts....");
// 程序主要功能
System.out.println("Hello, " + name);
// 方法执行完毕前留下日志
logger.log(Level.INFO, "hello method ends....");
}
}
在HelloSpeaker类别中,当执行hello()方法时,您希望该方法执行开始与执行完毕时都能留下日志,最简单的作法就是如以上的程序设计,在 方法执行的前后加上日志动作,然而记录的这几行程序代码横切入(Cross-cutting)HelloSpeaker类别中,对于 HelloSpeaker来说,日志的这几个动作并不属于HelloSpeaker商务逻辑(显示"Hello"等文字),这使得 HelloSpeaker增加了额外的职责。
想想如果程序中这种日志的动作到处都有需求,以上的写法势必造成您必须到处撰写这些日志动作的程序代码,这将使得维护日志程序代码的困难度加大。如果需要的服 务(Service)不只有日志动作,有一些非对象本身职责的相关动作也混入了对象之中(例如权限检查、交易管理等等),会使得对象的负担更形加重,甚至 混淆了对象本身该负有的职责,对象本身的职责所占的程序代码,或许还小于这些与对象职责不相关的动作或服务的程序代码。
另一方面,使用以上的写法,若您有一日不再需要日志(或权限检查、交易管理等)的服务,那么您将需要修改所有留下日志动作的程序代码,您无法简单的就将这些相关服务从即有的程序中移去。
可以使用代理(Proxy)机制来解决这个问题,在这边讨论两种代理方式:静态代理(Static proxy)与动态代理(Dynamic proxy)。
在静态代理的实现中,代理对象与被代理的对象都必须实现同一个接口,在代理对象中可以实现记录等相关服务,并在需要的时候再呼叫被代理的对象,如此被代理对象当中就可以仅保留业务相关职责。
举个实际的例子来说,首先定义一个IHello接口:
package onlyfun.caterpillar;
public interface IHello {
public void hello(String name);
}
然后让实现商务逻辑的HelloSpeaker类别要实现IHello接口,例如:
package onlyfun.caterpillar;
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
可以看到,在HelloSpeaker类别中现在没有任何日志的程序代码插入其中,日志服务的实现将被放至代理对象之中,代理对象同样也要实现IHello接口,例如:
package onlyfun.caterpillar;
import java.util.logging.*;
public class HelloProxy implements IHello {
private Logger logger =
Logger.getLogger(this.getClass().getName());
private IHello helloObject;
public HelloProxy(IHello helloObject) {
this.helloObject = helloObject;
}
public void hello(String name) {
// 日志服务
log("hello method starts....");
// 执行商务逻辑
helloObject.hello(name);
// 日志服务
log("hello method ends....");
}
private void log(String msg) {
logger.log(Level.INFO, msg);
}
}
在HelloProxy类别的hello()方法中,真正实现商务逻辑前后可以安排记录服务,可以实际撰写一个测试程序来看看如何使用代理对象。
package onlyfun.caterpillar;
public class ProxyDemo {
public static void main(String[] args) {
IHello proxy =
new HelloProxy(new HelloSpeaker());
proxy.hello("Justin");
}
}
程序中呼叫执行的是代理对象,建构代理对象时必须给它一个被代理对象,记得在操作取回的代理对象时,必须转换操作接口为IHello接口。
代理对象HelloProxy将代理真正的HelloSpeaker来执行hello(),并在其前后加上日志的动作,这使得我们的 HelloSpeaker在撰写时不必介入日志动作,HelloSpeaker可以专心于它的职责,可以从图解的方式来更进一步看出代理机制的运作流程。
这是静态代理的基本范例,然而如您所看到的,代理对象的一个接口只服务于一种类型的对象,而且如果要代理的方法很多,您势必要为每个方法进行代理,静态代理在程序规模稍大时就必定无法胜任,在这边介绍静态代理的目的,是在让您了解代理的基本原理。