代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操作。
定义:为其他对象提供一种代理以控制对这个对象的访问。
代理接口(Subject)
代理类(ProxySubject)
委托类(RealSubject)
代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口,对应代理接口(Subject)
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象,对应委托类(RealSubject)
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装,对应代理类(ProxySubject)
根据代理类的生成时间不同分为静态代理和动态代理两种。
静态代理:
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
(2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
另外,如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
动态代理:
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定,巧妙地实现了代理模式的设计理念。
1)java.lang.reflect.Proxy
这是 Java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
Proxy类的静态方法
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
InvocationHandler的核心方法
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是被调用的方法对象,第三个参数是调用参数
// 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
Object invoke(Object proxy, Method method, Object[] args)
3)java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到Java虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个 .class文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象
动态代理实现步骤
a. 实现InvocationHandler接口创建自己的调用处理器
IvocationHandlerhandler = newInvocationHandlerImpl(...);
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
Class clazz =Proxy.getProxyClass(classLoader,new Class[]{...});
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
Constructor constructor =clazz.getConstructor(new Class[]{InvocationHandler.class});
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
Interface Proxy= (Interface)constructor.newInstance(new Object[] (handler));
动态代理机制特点:
1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包,如果所代理的接口中有非public 的接口(因为接口不能被定义为protect 或 private,所以除 public之外就是默认的 package访问级别),那么它将被定义在该接口所在包,这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
2)类修饰符:该代理类具有 final 和 public修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
3)类名:格式是“$ProxyN”,其中N 是一个逐一递增的阿拉伯数字,代表Proxy 类第 N 次生成的动态代理类,不过并不是每次调用Proxy 的静态方法创建动态代理类都会使得N 值增加,原因是如果对同一组接口试图重复创建动态代理类,它会返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
4)类继承关系:该类的继承关系如图:
Proxy 类是它的父类,这个规则适用于所有由Proxy创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
被代理的接口特点:
首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。
其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。
再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。
最后,接口的数目不能超过 65535,这是 JVM设定的限制。
动态代理的优点和不足:
优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
不足:
始终无法摆脱仅支持 interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对 class的动态代理,原因是多继承在 Java中本质上就行不通。
第三方类库:cglib动态代理实现
jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm[ASM是一个 Java 字节码操控框架,可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为]来实现的。反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
spring两种代理方式:
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建 接口
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用 JDK 的动态代理好。
目标对象:进行AOP拦截的类
其他应用
mybatis:Mapper接口和xml,通过类名和方法名,就可以执行sql
struts2拦截器:拦截器机制就是基于AOP的思想
代码: http://download.csdn.net/download/shiyuezhong/9917409
动态代理机制特点
动态代理机制特点