代理模式
1.生活中: 代理就是一个人或者一个组织代表其他人去做一件事的现实生活中的。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
2.官方: 代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用
一、静态代理
类图结构如下
在代理模式中的角色:
●抽象主题角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
●真实主题角色:定义了代理对象所代表的目标对象。
●代理主题角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代 目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。它可以增加一些真实主题里面没有的功 能。
生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费。但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的。这点很重要! 上面这个例子,你就是“客户”,票务中心就是“代理角色”,火车站是“真实角色”,卖票称为“抽象角色”!
静态代理类:在程序运行前,代理类的.class文件就已经存在了,已确定被代理的对象 静态代理: 优点:对真实对象进行封装,不会修改目标类的代码。 缺点: 1.多个不同类型目标对象需要代理时,我就需要建立多个代理类,造成类的膨胀 2.代码的冗余 3.编译期加入,不够灵活
二、动态代理
描述(这个描述从网上看到的,相对比较容易理解) 动态代理(Dynamic Proxy):相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。 所谓代理,就是需要代理类和被代理类有相同的对外接口或者说成服务,所以代理类一般都必须实现了所有被代理类已实现的接口,因为接口就是制定了一系列对外服务的标准。 1.JDK实现动态代理
正因为动态代理有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)相同的接口(interface),而是把这种实现推迟到运行时。 为了能让DynamicProxy类能够在运行时才去实现RealSubject类已实现的一系列接口并执行接口中相关的方法操作,需要让 DynamicProxy类实现JDK自带的java.lang.reflect.InvocationHandler接口,该接口中的invoke() 方法能够让DynamicProxy实例在运行时调用被代理类的“对外服务”,即调用被代理类需要对外实现的所有接口中的方法,也就是完成对真实方法的调 用,Java帮助文档中称这些真实方法为处理程序。 按照上面所述,我们肯定必须先把被代理类RealSubject已实现的所有interface都加载到JVM中,不然JVM怎么能够找到这些方法呢?明白了这个道理,那么我们就可以创建一个被代理类的实例,获得该实例的类加载器ClassLoader。 所谓的类加载器ClassLoader,就是具有某个类的类定义,即类的内部相关结构(包括继承树、方法区等等)。 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到控制被代理对 象的行为的目的。请详看下面代码中的DynamicProxy类,其中必须实现的invoke()方法在调用被代理类的真实方法的前后都可进行一定的特殊 操作。这是动态代理最明显的优点
类图
package com.ztesoft.delegate.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author elephant-huang-zhao
* @date 2017/12/5
*/
public class DynamicProxyTicketManager implements InvocationHandler {
private Object targetObject;
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
// 3个参数:
// 一:目标对象的装载器
// 二:目标对象已实现的所有接口
// 三:用实现了InvocationHandler的对象生成动态代理实例
// 当你一调用代理,代理就会调用InvocationHandler的invoke方法
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkIdentity();
Object ret = null;
try {
ret = method.invoke(targetObject, args);
log();
} catch (Exception e) {
e.printStackTrace();
System.out.println("erro->: " + method.getName());
throw e;
}
return ret;
}
/**
* 身份验证
*/
public void checkIdentity() {
System.out.println("身份验证--------------");
}
public void log() {
System.out.println("日志...");
}
}
public class Test {
public static void main(String[] args) {
DynamicProxyTicketManager dynamicProxyTicketManager=new DynamicProxyTicketManager();
TicketManager tm=(TicketManager) dynamicProxyTicketManager.newProxyInstance(new TicketManagerImpl());
tm.soldTicket();
tm.changeTicket();
tm.returnTicket();
}
}
结果同上 优缺点 优点: 1、一个动态代理类更加简单了,可以解决创建多个静态代理的麻烦,避免不断的重复多余的代码 2、调用目标代码时,会在方法“运行时”动态的加入,决定你是什么类型,才调谁,灵活 缺点: 1、系统灵活了,但是相比而言,效率降低了,比静态代理慢一点 2、动态代理比静态代理在代码的可读性上差了一点,不太容易理解 3、JDK动态代理只能对实现了接口的类进行代理 总结 各有各的好,具体情况具体讨论
2.Cglib实现动态代理
描述(网上整理) AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。 两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的 ,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通 过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有 上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛, 且在效率上更有优势。 JDK的动态代理机制只能代理实现了接口的类,否则不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
介绍: CGLIB的核心类: net.sf.cglib.proxy.Enhancer – 主要的增强类 net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现 net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用: Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。 net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法 public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; 第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method 对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使 用,因为它更快
代码
package com.ztesoft.delegate.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* @author elephant-huang-zhao
* @date 2017/12/5
*/
public class CglibDynamicProxyTicketManager implements MethodInterceptor {
private Object targetObject;
public Object getInstance(Object targetObject) {
this.targetObject = targetObject;
// 用这个类来创建代理对象(被代理类的子类): 并设置父类;设置回调;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObject.getClass());
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
/**
* methodProxy 会调用父类(目标对象)的被代理的方法,比如soldTicket方法等
*
* @param o
* @param method
* @param objects
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object ref = null;
checkIdentity();
//ref = method.invoke(targetObject,objects);
//调用新生成的cglib的代理对象 所属的父类的被代理的方法
ref = methodProxy.invokeSuper(o, objects);
log();
return ref;
}
/**
* 身份验证
*/
public void checkIdentity() {
System.out.println("cglib身份验证--------------");
}
public void log() {
System.out.println("cglib日志...");
}
}
package com.ztesoft.delegate.cglib;
import com.ztesoft.delegate.statics.TicketManager;
import com.ztesoft.delegate.statics.TicketManagerImpl;
/**
* @author elephant-huang-zhao
* @date 2017/12/5
*/
public class Test {
public static void main(String[] args) {
CglibDynamicProxyTicketManager cglibDynamicProxyTicketManager = new CglibDynamicProxyTicketManager();
TicketManager ticketManager = (TicketManager)cglibDynamicProxyTicketManager.getInstance(
new TicketManagerImpl());
ticketManager.soldTicket();
ticketManager.changeTicket();
ticketManager.returnTicket();
}
}