概要
在执行一个功能函数时,经常需要在其中写入与功能不是直接相关但很有必要的代 码,如日志记录,信息发送,安全和事务支持等,这些枝节性代码虽然是必要的,但只有将枝节性代码和功能性代码分开来才能降低耦合程度,去符合现代OO系统的要求。
代理模式
代理模式,目的是为某对象(委托类的对象)提供一个代理(对象),以控制外界对这个真实对象的访问。代理类负责为委托类实现消息的预处理,消息的过滤,消息的转发,消息被委托类执行后的后续处理。
UML:
代理模式一般涉及到三个角色:
- 抽象角色:是真实对象和代理对象的共同接口
- 代理角色:代理对象内部包含着真实角色的引用,从而可以操作真实角色;代理对象与真实对象有相同的接口,所以代理对象能在任何时候代替真实对象;代理对象可以在执行真实对象的方法前后加入特定的逻辑以实现功能的扩展。
- 真实角色:代理角色所代表的真实对象,是最终要引用的对象。
静态代理
public class ProxyDemo {
public static void consumer(Subject s) {
s.request();
}
public static void main(String[] args) {
ProxyDemo.consumer(new ProxySubject(new RealSubject()));
}
}
//抽象角色:
interface Subject {
void request();
}
//真实角色:实现了Subject的request()方法
class RealSubject implements Subject {
public RealSubject() { }
public void request() {
System.out.println( "From RealSubject.Request()" );
}
}
//代理角色:
class ProxySubject implements Subject {
private Subject realSubject; //以真实角色作为代理角色的属性
public ProxySubject(Subject realSubject) { //真实角色作为创建代理角色时的参数
this.realSubject = realSubject ;
}
//该方法封装了真实对象的request方法,并添加一些扩展功能
public void request() {
preRequest();
realSubject.request(); //此处执行真实对象的request方法
postRequest();
}
private void preRequest() {
System.out.println( "Request开始");
}
private void postRequest() {
System.out.println( "Request结束");
}
}
Output:
Request开始
From RealSubject.Request()
Request结束
缺点:
- 上述方法中,真实角色必须事先已经存在,才能将其作为代理对象的内部属性(不然编译时找不到类)
- 若有多个不同的真实角色,同时每个真实角色被代理的方法都不同,则 1)可以为这些方法创建不同的代理类(重复创建多个逻辑相似,仅仅RealObject引用不同的Proxy)或者 2)创建一个代理角色,实现Action1、Action2、Action3接口,同时让它持有不同的真实角色。(导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑)
- 此外,若事先并不知道真实角色,该如何编写和使用代理类呢?
动态代理
静态代理,需要提前把代理类创建好,以便客户端可以使用某真实角色的代理类;那能不能事先不进行代理类的创建,而在用户真正需要的时候动态创建并使用。
- 静态:在程序运行前代理类的.class文件就已经存在了。
- 动态:代理类在程序运行时运用反射机制动态创建而成。
- 动态代理类只能代理接口(不支持抽象类),而静态代理两者都支持。
- 静态代理类只能为特定的接口服务(缺点2.2:难以判断代理类构造器中作参数的真实对象实现的是哪个接口)。如想要为多个接口服务则需要建立很多个代理类。而动态代理却可以指定接口数组创建proxy,然后转型为数组中的某接口。
通过使用动态代理,我们可以通过在运行时,动态生成一个持有真实角色、并实现某些接口的Proxy(动态代理类),同时注入我们相应的扩展逻辑。哪怕你要代理的真实角色是不同的对象,甚至代理不同的方法,都可以通过动态代理,来扩展功能。
相关类与方法介绍:
//调用处理器接口
java.lang.reflect.InvocationHandler:
/**
该方法负责集中处理动态代理类上的所有方法调用。根据参数明确要调用的真实对象的方法。
第一个参数既是代理类实例,
第二个参数是被调用的方法对象
第三个方法是调用参数。
调用处理器根据这三个参数进行预处理或确定分派到委托类实例上的方法并执行
*/
//proxy对象代理的真实对象的所有方法,都会在这里进行分辨,把任务分派给真实对象的对应方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
//动态代理机制的主类,提供一组静态方法为一组接口,动态的生成代理类对象
java.lang.reflect.Proxy;
//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
//动态生成一个包含扩展功能、持有RealObject引用(h),实现指定接口(interfaces)的代理实例
//代理三要素:真实对象:RealObject,代理接口:interfaces,代理实例:Proxy
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h)
//获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy)
//判断指定Class对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl)
//获取关联于指定类装载器和一组接口的动态代理类的Class对象
@revised 9
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//类装载器类,将类的字节码装载到 Java 虚拟机中并为其定义Class对象,然后该类才能被使用。
//Proxy类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
//每次生成动态代理类对象时都需要指定一个类装载器对象
java.lang.ClassLoader
示例1:(仔细注意)
package test;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
public class ProxyDemo {
public static void main(String[] args) {
RealSubject rs = new RealSubject(); //指定委托类
InvocationHandler ds = new DynamicSubject(rs); //创建调用处理器
Class cls = rs.getClass();
//生成代理类,类型转换
Subject subject =
(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),ds);
subject.request();//invoke参数明确了要调用request方法
subject.print();//invoke参数明确了要调用print方法
//
System.out.println(Proxy.isProxyClass(subject.getClass())); //true
System.out.println(ds==Proxy.getInvocationHandler(subject)); //true
}
}
interface Subject {
void request();
void print();
}
//具体角色:
class RealSubject implements Subject {
public RealSubject() {}
public void request() {
System.out.println( "From RealSubject request()" );
}
public void print() {
System.out.println( "From RealSubject print()" );
}
}
//代理处理器:为某真实对象的某方法提供扩展功能
class DynamicSubject implements InvocationHandler {
private Object sub; //引用真实对象
public DynamicSubject() { }
public DynamicSubject(Object obj) {
sub = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println( "before calling: " + method);
method.invoke(sub,args);
System.out.println( "after calling: " + method);
return null ;
}
}
Output:
before calling: public abstract void test.Subject.request()
From RealSubject request()
after calling: public abstract void test.Subject.request()
before calling: public abstract void test.Subject.print()
From RealSubject print()
after calling: public abstract void test.Subject.print()
动态代理的注意事项
- 代理接口(抽象角色)是public,则代理类被定义在顶层包(package为空);否则代理类默认被定义在该接口所在包。
- 生成的代理类为 public final,不能被继承。
- 类名:格式是“$ProxyN”,N是逐一递增的数字,代表Proxy是第N次动态生成的代理类。对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。
- Proxy类是 所有 由Proxy创建的动态代理类的父类,而且该Proxy类还实现了其所代理的一组接口,这就是为什么它能够被安全地转型为其所代理的某接口的根本原因。
- 代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString。
常见的代理
远程代理:对一个位于其他地址空间的对象提供一个局域代表对象,如RMI中的stub。
虚拟代理:根据需要将一个资源消耗很大或者比较复杂的对象,延迟加载,在真正需要的时候才创建。
保护代理:控制对一个对象的访问权限。
智能引用:为目标对象提供额外的服务和功能。
cglib动态代理实现
JDK中提供的生成动态代理类的机制有个鲜明的特点是:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。若委托类除实现了继承自接口的方法外,另外还实现了自己的方法 ,则在产生的动态代理类中是不会有这个方法的;更极端的情况是:如果某个类没有实现接口,那么这个类就不能用JDK产生动态代理了。
cglib(Code Generation Library代码生成库)是一个优秀的动态代理框架,它的底层使用ASM(字节码操控框架)在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。cglib的运行速度要远远快于JDK的Proxy动态代理。
cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target的方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是才用类似jdk的方式,通过持有target对象来达到拦截方法的效果。