Java设计模式之代理模式(Proxy)
Posted 朝北教室的风筝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式之代理模式(Proxy)相关的知识,希望对你有一定的参考价值。
Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没有接口的话,通过方法级别的代理 CGLib代理实现。
2、静态代理
所谓静态就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
通过上面的代理模式描述我们可以知道,其目的就是为了控制对象引用,生活场景中我们以买车为例,如果我们要买一辆轿车必须通过汽车4S店,汽车4s店就是充当代理角色,其目的就是控制买车客户的买车行为,必须通过汽车4S店才能从汽车厂商买一辆车。
1.)首先新建一个买车的接口
public interface IBuyCar { //买车 void buyCar(); }
2.)声明一个要买车的客户,实现买车接口
public class Customer implements IBuyCar { private int cash;//购车款 public int getCash() { return cash; } public void setCash(int cash) { this.cash = cash; } @Override public void buyCar() { Log.e("buyCar", "买一辆车花费了-->" + cash + "元"); } }
3.)声明一个买车代理汽车4S店,同样也实现买车接口,必须接受客户下单
public class BuyCarProxy implements IBuyCar{ private Customer customer;//接收买车客户 public BuyCarProxy(Customer customer){ this.customer=customer;//接收买车客户 } @Override public void buyCar() {//实现为客户买车 customer.buyCar(); } }
4.) 创建一个客户端,模拟一次买车
Customer customer=new Customer(); customer.setCash(120000); BuyCarProxy buyCarProxy=new BuyCarProxy(customer); buyCarProxy.buyCar();
代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
5.)通过代理模式实现权限控制
通过上面的例子,我们可能有个疑问,难道就不能直接去厂家买车吗?当然可以,如果在使用场景中实现类能满足要求时,我们当然可以直接实现类,但当实现类不能满足要求,要扩展需求,根据开闭原则你又不能修改实现类代码,这时你就用代理类。比如购买一辆车我们要对客户进行一个购车款审核,如果符合条件就买车,不符合要求我们就告知客户购车款不足。
@Override public void buyCar() {//实现为客户买车 int cash=customer.getCash(); if(cash<100000){ Log.e("buyCar","你的钱不够买一辆车"); return; } customer.buyCar(); }
实现场景
Customer customer=new Customer(); customer.setCash(120000); BuyCarProxy buyCarProxy=new BuyCarProxy(customer); buyCarProxy.buyCar(); Customer customer1 =new Customer(); customer1.setCash(90000); BuyCarProxy buyCarProxy1 =new BuyCarProxy(customer1); buyCarProxy1.buyCar();
静态代理优点:
客户端不必知道实现类(委托类)如何如何,只需要调用代理类即可。
缺点:
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。但这样出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也要实现这个方法。这显然增加了代码的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象,那就要对每种对象都进行代理。静态代理子啊程序规模稍大是就无法胜任了。
3、动态代理机制:
以上讲的都是代理模式的静态实现,所谓静态代理就是自己要为要代理的类写一个代理类,或者用工具为其生成的代理类,总之,就是程序运行前就已经存在的编译好的代理类,这样有时候会觉得非常麻烦,也导致非常的不灵活,相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。
3.1 JDK代理——接口级别代理
package Proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //Created by zhengbinMac on 2017/2/19. public class DynamicProxy implements InvocationHandler{ private Object target; public DynamicProxy(Object target) { this.target = target; } @SuppressWarnings("unchecked") public <T> T getProxy() { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { sayBefore(); Object result = method.invoke(target, args); sayAfter(); return result; } private void sayBefore() { System.out.println("before..."); } private void sayAfter() { System.out.println("after..."); } }
JDK 提供的 Proxy 类的工厂方法 newProxyInstance 去动态地创建一个 Hello 接口的代理类。
Proxy.newProxyInstance:
参数:
- loader - 定义代理类的类加载器
- interfaces - 代理类要实现的接口列表
- h - 指派方法调用的调用处理程序(每个代理实例都具有一个关联的调用处理程序,调用代理实例的方法时,将对方法的调用指派到它的调用处理程序的 invoke 方法)
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定类加载器定义,并实现指定的接口。
package Proxy; //Created by zhengbinMac on 2017/2/19. public class Test { public static void main(String[] args) { DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl()); Hello hello = dynamicProxy.getProxy(); hello.sayHello("JDK"); } }
再举例:还是接着上面的例子
1.)首先我们要声明一个动态代理类,实现InvocationHandler接口
public class DynamicProxy implements InvocationHandler { // 被代理类的实例 Object obj; // 将被代理者的实例传进动态代理类的构造函数中 public DynamicProxy(Object obj) { this.obj = obj; } /** * 覆盖InvocationHandler接口中的invoke()方法 * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构 * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到 * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊 * 代码切入的扩展点了。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* * before :doSomething(); */ Object result = method.invoke(this.obj, args); /* * after : doSomething(); */ return result; } }
2.)具体实现
//我们要代理的真实对象 Customer customer = new Customer(); //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 InvocationHandler handler = new DynamicProxy(customer); /* * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象 * 第二个参数customer.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上 */ IBuyCar buyCar = (IBuyCar) Proxy.newProxyInstance(handler.getClass().getClassLoader(), customer.getClass().getInterfaces(), handler); buyCar.buyCar();
动态态代理相比静态代理,接口变了,动态代理类不需要改变,而静态代理类不仅需要改变实现类,代理类也需要修改。
但如果要代理一个没有接口的类,JDK 动态代理就用不上了,这就引出了 CGLib 代理。
CGLib代理——方法级别代理
Spring、Hibernate 框架都是用了它,当然,spring两个都用到了 (a) 用到了JDK的 接口动态代理 (b) 也用到了 CGLib代理,它是一个在运行期间动态生成字节码的工具,也就是动态生成代理类。
package Proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; //Created by zhengbinMac on 2017/2/19. public class CGLibProxy implements MethodInterceptor { // 单例模式 private static CGLibProxy instance = new CGLibProxy(); private CGLibProxy() {} public static CGLibProxy getInstance () { return instance; } public <T> T getProxy(Class<T> cls) { return (T) Enhancer.create(cls, this); } public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { sayBefore(); Object result = methodProxy.invokeSuper(obj, objects); sayAfter(); return result; } private void sayBefore() { System.out.println("before..."); } private void sayAfter() { System.out.println("after..."); } }
测试类
package Proxy; //Created by zhengbinMac on 2017/2/19. public class Test { public static void main(String[] args) { Hello helloCGLib = CGLibProxy.getInstance().getProxy(HelloImpl.class); helloCGLib.sayHello("CGLib"); } }
3.)动态代理好处
使用Java动态代理机制的好处:
1、减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。
2、系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。
代理模式的好处:
(1).职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
(3).高扩展性
总结:
Spring提供的实现动态代理有两种方式,一个是被代理对象需要实现JDK提供的动态代理接口。通过cglib的jar包实现动态代理,该方法只需要对目标对象继承即可
spring支持两种方法,那么我们在使用spring进行动态代理时究竟使用的哪一种方法呢?spring优先支持实现接口的方式,如果没有接口则使用cglib方式。
下面我们看一看这两种方法有什么区别。 下面我们通过JDK实现动态代理,需求是如下:有一个服务层对象ServiceImpl 如下
public class ServiceImpl implements Service { @Override public void add() { System.out.println("add"); } @Override public void delete() { System.out.println("delete"); } @Override public void update() { System.out.println("update"); } }
他们有三个方法,增删改。我们知道执行增删改操作时需要开启事务,提交事务。接下来我们通过动态代理的方式为这三个方法织如事务控制。以伪代码的形式。因为JDK动态代理需要使用接口,那我们为他创建一个接口。
public interface Service { void add(); void delete(); void update(); }
接下来我们使用ServiceImpl的代理工厂生成代理对象,创建代理工厂类ServiceProxyFactory如下
@Data
public class ServiceProxyFactory implements InvocationHandler { private ServiceImpl si; public Service getInstance(){ Service newProxyInstance =(Service) Proxy.newProxyInstance(ServiceProxyFactory.class.getClassLoader(), ServiceImpl.class.getInterfaces(),this); return newProxyInstance; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("打开事务"); Object invoke =method.invoke(si,args); System.out.println("提交事务"); return invoke; } }
第一眼看上去可能有点混乱,一步一步来,既然代理工厂生成代理对象,我们首先创建一个getInstance方法返回代理对象。JDK提供了一个Proxy下的一个静态方法newProxyInstance(),帮助我们生成代理对象。该方法需要传入三个参数,
第一个参数为的类加载器, Java有三种类加载器,这里不做重点描述,提供任意一个类的class对象有一个方法getClassLoader()返回一个classLoader对象,源码中该对象是被final修饰的,防止黑客修改类加载以达到不可告人的目的,sun公司在jvm中有着大量的代码维护反射和类加载器,防止被坏人利用。第二个参数为Class<?>[] 类型数组,什么意思呢,这里可以传入任意的Class类,那么如何获取Class类数组呢,通过任意的class对象getInterfaces()方法可以获得Class[],但是这里我们必须传入需要被代理对象的class对象。为什么呢,前面说过需要的参数为Class<?>[],由于Class类实现了Type接口,在生成代理对象的过程中Class数组的泛型类型?会被替换成传入的class类型的类型,就是说我传入一个A.class.getInterfaces(),进入到底层时会变成Class<A>[]。因为我们这里必须传入被代理对象的class的Class[]。第三个参数需要传入一个执行器对象,该执行器必须符合InvocationHandler接口规范,就是实现这个接口的invoke方法。该方法同样要求传入三个参数,
参数一:局部变量:被代理对象实例参数二:Method类型对象,该参数有JDK的Proxy负责传入,method为我们被代理对象的执行方法对象。参数三:Object[],它是被执行方法参数,在方法执行中参数由Proxy负责传入至Object[]中。
那么第3个参数我们实质只需要传入被代理对象实例即可。那么传入被代理方法对象有两种方法,一个是构造器传入,另一个是set方法传入,也可以使用spring注入。我们使用method对象调用invoke()方法,该方法需要两个参数,第一个参数:全局变量的被代理对象实例,这里与全局变量被代理对象实例做一个区分帮助大家更清晰的理解。看图中,被代理对象实例由set方法传入全局变量si,然后si作为InvocationHandler的实现类下的invoke方法的参数method对象下的invoke方法参数传入。 这个参数也就是invoke的调用对象。第二个参数就是被代理对象实例调用的方法参数,也就是上方的参数三Object[]。该invoke方法返回一个Object对象,因为我使用的自动生成变量所以这个Object对象名也是invoke,不好意思!最后我们将该对象invoke返回即可。这样就可以创建一个代理对象了,我们使用动态代理的目的是在原编码不变的基础上对方法进行增强,请看下图
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("打开事务"); Object invoke =method.invoke(si,args); System.out.println("提交事务"); return invoke; }
该方法相当于对图中三行代码进行一个封装,封装成了一个方法。执行顺序自上而下。下面看一下如果使用代理对象调用被代理对象的方法
public class mainTest { public static void main(String[] args) { ServiceImpl serviceImpl=new ServiceImpl(); ServiceProxyFactory spf=new ServiceProxyFactory(); spf.setSi(serviceImpl); Service instance =spf.getInstance(); instance.add(); } }
1先创建一个被代理对象实例,用于传入代理对象工厂。
2然后创建代理对象工厂实例,为内部的被代理对象赋值。
3代理对象工厂实例调用getInstance方法生成代理对象
4代理对象调用增强后的方法
下面请看执行结果
打开事务
add
提交事务
执行的是我们增强后的方法,这就说明代理对象的作用已经达到了。这就是JDK提供的动态代理。下面我们看看由第三方人员提供的拓展类库cglib是如何实现动态代理的。
这就是cglib创建动态代理的代码,具体就不详细介绍了,只需了解即可。下面看执行代码
结果还需要看吗?
可以看到cglib的代理方式不需要传入被代理对象实例即可完成动态代理。cglib和jdk代理有什么区别呢?jdk的代理对象与被代理对象实现了同一接口,底层通过clone()方法,将实现类的方法复制过来在按照invoke中的配置对方法进行增强。而cglib代理则是代理对象继承了被代理对象的方法,然后自身进行增强。这两种代理思想在外来的自动生成代码方向会经常被使用,实现完全体代码的复用性。以上就是springAOP的实现原理了。
以上是关于Java设计模式之代理模式(Proxy)的主要内容,如果未能解决你的问题,请参考以下文章