代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到三个角色:
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
代理,可以通过下面代码实现
需要一个接口、实现类、以及一个代理类
抽象角色
IHello.java
public interface IHello {
/**
* 假设这是一个业务方法
* @param name
*/
public void sayHello(String name) ;
}
真实角色
Hello.java
public class Hello implements IHello {
public void sayHello(String name) {
System.out.println("hello," + name + "!!!");
}
}
代理角色:
现在我们要为这个业务方法加上日志记录的业务,我们在不改变原代码的情况下,我们会去怎么做呢?也许,你会去写一个类去实现IHello接口,并依赖Hello这个类.代码如下:'
HelloProxy.java
public class HelloProxy implements IHello {
private IHello hello;
public HelloProxy(IHello hello) {
this.hello = hello;
}
public void sayHello(String name) {
Logger.logging(Level.INFO,"方法开始");
hello.sayHello(name);
Logger.logging(Level.INFO,"方法结束");
}
}
其中.Logger类和Level枚举代码如下:
Logger.java
import java.text.SimpleDateFormat;
import java.util.Date;
public class Logger {
/**
* 根据等级记录信息
* @param level
* @param context
*/
public static void logging(Level level, String context) {
if (level.equals(Level.INFO)) {
System.out.println("start: "
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()) + " " + context);
}
if (level.equals(Level.DEBUGE)) {
System.err.println("end: "
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()) + " " + context);
}
}
}
Level.java
public enum Level {
INFO,DEBUGE;
}
下面我们写一个测试类Base.java
public class Base {
public static void main(String[] args) {
IHello hello = new HelloProxy(new Hello());//生成代理类
//sayHello方法已经由代理类代理
hello.sayHello("java");
}
}
结果输出为:
start: 2009-03-08 14:39:48 方法开始
hello,java!!!
start: 2009-03-08 14:39:48 方法结束
从上面的代码我们可以看出,hello对象是被HelloProxy这个所谓的代理态所创建的,但是存在这样一个问题,如果方法一多的话,我们必须对每个方法都这样重写一次,使得程序不易维护
JDK为我们提供了一个API java.lang.reflect.InvocationHandler的类. 这个类可以让我们在JVM调用某个类的方法时动态的为些方法做些什么事.
Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
(1). Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。
(2).Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
在使用动态代理类时,我们必须实现InvocationHandler接口
下面例子中仍然使用上面代码中的抽象角色和真实角色
代理角色修改为一个实现IncocationHandler接口的类DynaProxyHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynaProxyHandler implements InvocationHandler {
/**
* 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
*/
private Object obj;
/**
* 动态生成方法被处理过后的对象 (写法固定)
* @param obj
* @return
*/
public Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
/**
* 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
* 此方法是动态的,不是手动调用的
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
Logger.logging(Level.INFO,"动态代理类方法" + method.getName() + "开始");
//JVM通过这条语句执行原来的方法(反射机制)
result = method.invoke(this.obj,args);
Logger.logging(Level.INFO,"动态代理类方法" + method.getName() + "结束");
return result;
}
}
测试类修改为
public class Base {
public static void main(String[] args) {
IHello h = (IHello)new DynaProxyHandler().bind(new Hello());
h.sayHello("java");
}
}
结果输出为:
start: 2009-03-08 14:43:42 动态代理类方法sayHello开始
hello,java!!!
start: 2009-03-08 14:43:42 动态代理类方法sayHello结束
从上面的例子我们看出.只要你是采用面向接口编程,那么,你的任何对象的方法执行之前要加上记录日志的操作都是可以的.他(DynaPoxyHandler)自动去代理执行被代理对象(Hello)中的每一个方法,一个java.lang.reflect.InvocationHandler接口就把我们的代理对象和被代理对象解藕了.但是,我们又发现还有一个问题,这个DynaPoxyHandler对象只能跟我们去在方法前后加上日志记录的操作.我们能不能把DynaPoxyHandler对象和日志操作对象(Logger)解藕呢?
结果是肯定的.让我们来分析一下我们的需求.
我们要在被代理对象的方法前面或者后面去加上日志操作代码(或者是其它操作的代码),
那么,我们可以抽象出一个接口,这个接口里就只有两个方法,一个是在被代理对象要执行方法之前执行的方法,我们取名为start,第二个方法就是在被代理对象执行方法之后执行的方法,我们取名为end .接口定义如下 :
IOperation.java
package com.royzhou;
import java.lang.reflect.Method;
public interface IOperation {
/**
* 方法开始前操作
* @param method
*/
public void start(Method method);
/**
* 方法结束后操作
* @param method
*/
public void end(Method method);
}
我们去写一个实现上面接口的类.我们把作他真正的操作者,如下面是日志操作者的一个类:
LoggerOperation.java
package com.royzhou;
import java.lang.reflect.Method;
public class LoggerOperation implements IOperation {
public void end(Method method) {
Logger.logging(Level.INFO, method.getName() + " Method end ...");
}
public void start(Method method) {
Logger.logging(Level.INFO, method.getName() + " Method start ...");
}
}
然后我们要改一下代理对象DynaProxyHello中的代码.如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynaProxyHandler implements InvocationHandler {
/**
* 操作对象
*/
private Object proxy;
/**
* 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
*/
private Object obj;
/**
* 动态生成方法被处理过后的对象 (写法固定)
* @param obj
* @return
*/
public Object bind(Object obj, Object proxy) {
this.obj = obj;
this.proxy = proxy;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
/**
* 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
* 此方法是动态的,不是手动调用的
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
//反射得到操作者的实例
Class clazz = this.proxy.getClass();
//反射得到操作者的Start方法
Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class});
//反射执行start方法
start.invoke(this.proxy, new Object[]{method});
//Logger.logging(Level.INFO,"动态代理类方法" + method.getName() + "开始");
//JVM通过这条语句执行原来的方法(反射机制)
result = method.invoke(this.obj,args);
//反射得到操作者的end方法
Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class});
//反射执行end方法
end.invoke(this.proxy, new Object[]{method});
//Logger.logging(Level.INFO,"动态代理类方法" + method.getName() + "结束");
return result;
}
}
修改一下测试类
public class Base {
public static void main(String[] args) {
IHello h = (IHello)new DynaProxyHandler().bind(new Hello(),new LoggerOperation());
h.sayHello("java");
}
}
结果还是一样的吧.
通过这样的修改,如果你想在每个方法之前加上日志记录,而不在方法后加上日志记录.你就把LoggerOperation类中start的实现注释掉,很轻松就解决了这个问题。
运行一下.你就会发现,每个方法之后没有记录日志了. 这样,我们就把代理者和操作者解藕了!
还有这样一个问题,如果只有部分方法需要记录日志,可以这样实现
在代理对象的public Object invoke(Object proxy, Method method, Object[] args)方法里面加上个if(),对传进来的method的名字进行判断,判断的条件存在XML里面.这样我们就可以配置文件时行解藕了.可以把操作者,被代理者,都通过配置文件进行配置 ,那么就可以写一个简单的SpringAOP框架了.