代理模式
所谓代理,就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式类图如下:
在代理模式中的角色:
- 抽象对象角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
- 目标对象角色:定义了代理对象所代表的目标对象。
- 代理对象角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
源代码
//抽象对象角色 public abstract class AbstractObject { public abstract void operation(); } //目标对象角色 public class RealObject extends AbstractObject { @Override public void operation() { System.out.println("一些操作"); } } //代理对象角色 public class ProxyObject extends AbstractObject { RealObject realObject = new RealObject(); @Override public void operation() { // 调用目标对象之前可以做相关操作 System.out.println("before"); realObject.operation(); // 调用目标对象之后可以做相关操作 System.out.println("after"); } } //客户端 public class Client { public static void main(String[] args) { AbstractObject obj = new ProxyObject(); obj.operation(); } }
代理对象将客户端的调用委派给目标对象,在调用目标对象的方法之前跟之后都可以执行特定的操作。 采用Java代理模式,代理类通过调用委托类对象的方法,来提供特定的服务。委托类需要实现一个业务接口,代理类返回委托类的实例接口对象。按照代理类的创建时期,可以分为:静态代理和动态代理。所谓静态代理:指程序员创建好代理类,编译时直接生成代理类的字节码文件。所谓动态代理:在程序运行时,通过反射机制动态生成代理类。
静态代理类的特点: 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。而且代理类只能为特定的接口(Service)服务。
动态代理: 代理类需要实现InvocationHandler接口。
使用场合举例: 如果需要委托类处理某一业务,那么我们就可以先在代理类中,对客户的权限、各类信息先做判断,如果不满足某一特定条件,则将其拦截下来,不让其代理。
JAVA动态代理例子
//抽象对象角色 public interface Service { public String queryDate(); //查询日期 public int sub(int a, int b); // 计算两个整数之差 } //目标对象角色 public class ServiceImpl implements Service { @Override public String queryDate() { return new Date().toString(); } @Override public int sub(int a, int b) { return a - b; } public String ownMethod() { System.out.println("It's my own method"); return "admin"; } } //代理类 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ServiceProxy implements InvocationHandler { private Object target; public ServiceProxy(Object target) { this.target = target; } * @param proxy 目标对象的代理类实例 * @param method 对应于在代理实例上调用接口方法的Method实例 * @param args 传入到代理实例上方法参数值的对象数组 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; if (!(target instanceof ServiceImpl)) { System.out.println("不能代理该对象"); return result; } else if (!((ServiceImpl) target).ownMethod().equals("admin")) { System.out.println("权限不够"); return result; } result = method.invoke(target, args); return result; } /** * @param target * @return 返回委托类接口的实例对象 */ public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } } //客户 public class ServiceTest { public static void main(String[] args) { // 创建委托类实例,即被代理的类对象 ServiceImpl target = new ServiceImpl(); // 创建动态代理类 ServiceProxy proxy = new ServiceProxy(target); Service service = (Service) proxy.getProxyInstance(); String date = service.queryDate(); System.out.println(date); int result = service.sub(10, 20); System.out.println("10-20 = " + result); } }
CgLib动态代理
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类。使用CGLIB即使被代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理,使用CGLIB需要导入以下两个jar文件:
asm.jar – CGLIB的底层实现。
cglib.jar – CGLIB的核心jar包。
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);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
代码实现
public class UserManagerCglibProxy implements MethodInterceptor { private Object targerObject; //代理的目标对象 /** * 创建目标对象的代理对象 * @param targerObject 目标对象 * @return */ public Object createProxyInstance(Object targerObject){ this.targerObject=targerObject; Enhancer enhancer=new Enhancer(); //该类用于生成代理对象 enhancer.setSuperclass(this.targerObject.getClass()); //设置父类 enhancer.setCallback(this); //设置回调用对象为本身 return enhancer.create(); //创建代理对象 } /** * @param obj 目标对象代理类的实例 * @param method 代理实例上调用父类方法的Method实例 * @param args 传入到代理实例上方法参数值的对象数组 * @param methodProxy 使用它调用父类的方法 * @return * @throws Throwable */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{ System.out.println("代理类 "+obj.getClass()); System.out.println("方法名称 "+method.getName()); if(args!=null&&args.length>0){//方法的参数值 for(int i=0;i<args.length;i++){ System.out.println("方法参数 "+args[i]); }} Object returnvalue =null; //方法的返回值,无返回类型时,为null //调用目标对象的方法 returnvalue=methodProxy.invoke(this.targerObject, args); System.out.println("方法的返回值 "+returnvalue); return returnvalue; }
spring在运行期创建代理,不需要特殊的编译器.spring有两种代理方式:
- 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
- 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
使用该方式时需要注意:
- 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
- 标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。
JDK动态代理和CGLib的比较
CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不 少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理, 因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。除此之外,JDK动态代理技术,目标类必须实现接口。
同样的,Spring的AOP编程中相关的ProxyFactory代理工厂内部就是使用JDK动态代理或CGLib动态代理的,通过动态代理,将增强(advice)应用到目标类中。