代理、动态代理
一、概述
1、代理模式:即为其他对象提供一个代理以控制对某个对象的访问(即提供对另一个对象的访问,同时隐藏这个对象的实现细节。例如:读者通过书店买书,实际上,读者要获得一本书最终目标应该是出版社,因为书总是由出版社出版的。而实际上,我们买书总好像从书店就可以买到,这就是书店作为出版社的代理的好处)。
2、使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能。
3、为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
4、模式中包含的角色及其职责
(1)抽象主题角色(即抽象主题类,Subject):可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。
(2)具体主题角色(即被代理类,RealSubject):是业务逻辑的具体执行者。
(3)代理主题角色(即代理类,Proxy):把所有抽象主题类定义的方法给具体主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后工作。
5、代理模式的应用形式
(1)远程代理(Remote Proxy) -可以隐藏一个对象存在于不同地址空间的事实。也使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
(2)虚拟代理(Virtual Proxy) – 允许内存开销较大的对象在需要的时候创建。只有我们真正需要这个对象的时候才创建。
(3)写入时复制代理(Copy-On-Write Proxy) – 用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。是虚拟代理的一个变体。
(4)保护代理(Protection (Access)Proxy) – 为不同的客户提供不同级别的目标对象访问权限
(5)缓存代理(Cache Proxy) – 为开销大的运算结果提供暂时存储,它允许多个客户共享结果,以减少计算或网络延迟。
(6)防火墙代理(Firewall Proxy) – 控制网络资源的访问,保护主题免于恶意客户的侵害。
(7)同步代理(SynchronizationProxy) – 在多线程的情况下为主题提供安全的访问。
(8)智能引用代理(Smart ReferenceProxy) - 当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
(9)复杂隐藏代理(Complexity HidingProxy) – 用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Façade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。
6、实例
/*抽象主题角色*/
interface Interface{
public String book();
}
/*具体主题角色*/
class Press implements Interface{
@Override
public String book() {
System.out.println("I am a Press, Publication of books.You can get a book from here at last.");
return "book";
}
}
/*代理主题角色*/
class BookShop implements Interface{
private Interface in_aIterface;
public BookShop() { //这比带参的构造器,代理更加彻底
in_aIterface = new Press();
}
public BookShop(Interface intf) {
in_aIterface = intf;
}
@Override
public String book() {
System.out.println("I am a bookshop.You can buy book from me after the Press publishs it.");
return in_aIterface.book();
}
public String sellBook() {
return book();
}
}
/*客户*/
public class TestProxy {
public String books() {
//这就好像我们从书店获得书本,而实际出版社才是出版书本的地方
BookShop ob_aBookShop = new BookShop();
//Interface in_aInterface = new Press();
// BookShop ob_aBookShop = new BookShop(in_aInterface);
return ob_aBookShop.sellBook();
}
public static void main(String[] args) {
TestProxy ob_aTestProxy = new TestProxy();
System.out.println("I buy a book : " + ob_aTestProxy.books());
}
}
运行结果:
I am a bookshop.You can buy book from me after the Press publishs it.
I am a Press, Publication of books.You can get a book from here at last.
I buy a book : book
网络文章推荐:http://yangguangfu.iteye.com/blog/815787
http://blog.csdn.net/jackiehff/article/details/8621517
二、动态代理
1、动态代理:在程序运行时,使用反射机制动态创建代理对象。
2、为了支持动态代理,Java提供动态代理机制。程序员只要简单地指定一组接口及委托类对象,便能够动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行。在分派执行的过程中,程序员还可以按需调整委托类对象及其功能。
3、动态代理类:是一个实现在创建类时运行指定接口列表的类。
4、代理类Proxy
提供用于创建动态代理类和实例的静态方法,同时是由这些方法创建的所有动态代理类的超类。
Proxy是一个抽象类,提供了4个常用的静态方法。
方法 |
功能 |
getInvocationHandler() |
返回指定代理实例的调用处理程序。 |
getProxyClass() |
返回Class对象,并向其提供类加载器和接口数组。 |
isProxyClass() |
指定的类通过getProxyClass()或newProxyInstance()动态生成代理类时,返回true |
newProxyInstance() |
返回一个指定接口的代理类实例。 |
5、处理器接口InvocationHandler
自定义一个invoke()方法,用于集中处理在动态代理类对象上的方法调用,通常在该法中实现对委托类的代理访问。下面是invoke()方法的定义:
Object invoke(Object proxy, //代理实例
Method method, //在代理实例上调用的接口方法
Object[] args) //接口方法的参数数组
throws Throwable
6、类装载器ClassLoader
是一个抽象类。
负责将类的字节码装载到JVM中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
数组类的Class对象不是由类加载器创建的,而是由Java运行时根据所需要自动创建。数组类的加载器由Class.getClassLoader()返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。
ClassLoader提供了很多类方法,用于动态加载和管理。
7、动态代理的基本步骤
(1)通过实现InvocationHandler接口创建自己的调用处理器。
(2)通过Proxy类指定ClassLoader对象和一组interface来创建动态代理类。
(3)通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
(4)通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
8、实例
创建接口的代理:
interface Name{
void setName(String name);
String getName();
}
class PrintName implements Name{
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
//printName();
return name;
}
public void printName() {
System.out.println("The name is : " + name);
}
}
/*处理器*/
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(obj, args);
after();
return result;
}
public void before() {
System.out.println("ready go……");
}
public void after() {
System.out.println("finish……");
}
}
public class MyProxy {
public static void main(String[] args) {
PrintName obPName = new PrintName();
/*处理器实例*/
MyInvocationHandler obMIHandler = new MyInvocationHandler(obPName);
/*代理实例*/
Name obName = (Name) Proxy.newProxyInstance(
obPName.getClass().getClassLoader(),
obPName.getClass().getInterfaces(), obMIHandler);
/*代理实例应用*/
obName.setName("xuemaxiongfeng");
//obName.printName();
obPName.printName();
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
String name = obName.getName();
System.out.println(name);
}
}
运行结果:
ready go……
finish……
The name is : xuemaxiongfeng
aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
ready go……
finish……
xuemaxiongfeng
分析:1)由于代理的是接口,所以代理实例只能能调用接口所声明的函数。
2)调用接口中的所有函数,函数的入口都是invoke()
三、动态代理需要注意的事项
1、包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
2、类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
3、在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。
4、被代理的一组接口有哪些特点?首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。
网络文章推荐:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html