本篇能够理解为Spring AOP的铺垫,之前大概会用Spring框架提供的AOP,可是对AOP还不是很了解,因而打算系统的梳理一下代理模式和AOP。
软件世界是对现实世界抽象,因此软件世界的某些概念也不是凭空产生,也是从现实世界中脱胎而来,就像多态这个概念同样,Java官方出的指导书 《The Java™ Tutorials》在讲多态的时候,首先提到多态首先是一个生物学的概念。那么设计模式中的代理模式,咱们也能够认为是从现实世界中抽象而来,在现实世界代理这个词也是很常见的,好比房产中介代理房东的房子,经纪人代理明星谈商演。基本上代理人都在必定范围内代理被表明人的事务。html
那咱们为何须要代理模式呢? 咱们事实上也能够从现实世界去寻找答案,为何房东要找房产中介呢? 由于房东也是人,也有本身的生活,不可能将全身心都放置在将本身的房屋出租上,不肯意作改变。那么软件世界的代理模式,我认为也是有着一样的缘由,原来的某个对象须要再度承接一个功能,可是咱们并不肯意修改对象附属的类,缘由可能有不少,也许旧的对象已经对外使用,一旦修改也许会有其余意想不到的反应,也许咱们没办法修改等等。缘由有不少,同时对一个过去的类修改也不符合开闭原则,对扩展开放,对修改封闭。因而咱们就但愿在不修改目标对象的功能前提下,对目标的功能进行扩展。java
网上的大多数博客在讲代理模式的时候,大多都会从一个接口入手,而后需求是扩展实现对该接口实现类的加强,而后写代码演示。像是下面这样:程序员
public interface IRentHouse { /** * 实现该接口的类将可以对外提供房子 */ void rentHouse(); } public class Landlord implements IRentHouse { @Override public void rentHouse() { System.out.println(" 我向你提供房子..... "); } } public class HouseAgent implements IRentHouse { @Override public void rentHouse() { System.out.println("在该类的方法以前执行该方法"); IRentHouse landlord = new Landlord(); landlord.rentHouse(); System.out.println("在该类的方法以后执行该方法"); } } public class Test { public static void main(String[] args) { IRentHouse landlord = new HouseAgent(); landlord.rentHouse(); } }
测试结果:
许多博客在刚讲静态代理的时候一般会从这里入手,在不改变原来类同时,对原来的类进行增强。对,这算是一个至关现实的需求,尤为是在原来的类在系统中使用的比较多的状况下,且运行比较稳定,一旦改动就算是再当心翼翼,也没法保证对原来的系统一点影响都没有,最好的方法是不改,那如何加强原有的目标对象,你新加强的通常就是要知足新的调用者的需求,那我就新增一个类吧,供你调用。很完美,那问题又来了,为何各个博客都是从接口出发呢?面试
由于代理类和目标类实现相同接口,是为了尽量的保证代理对象的内部结构和目标对象保持一致,这样咱们对代理对象的操做均可以转移到目标对象身上,咱们只用着眼于加强代码的编写。编程
从面向对象的设计角度来看,若是我这个时候我不放置一个顶层的接口,像上面我将接口中的方法移动至类中,再也不有接口。那你这个时候加强又改该怎么作呢?这就要聊聊类、接口、抽象类的区别了,这也是常见的面试题,在学对象的时候,咱们经常和过去的面向过程进行比较,强调的一句话是类拥有属性和行为,但在面向过程系语言自己是不提供这种特性的。在学习Java的时候,在学完类,接着就是抽象类和接口,咱们能够说接口强调行为,是一种契约,咱们进行面向对象设计的时候,将多个类行为抽象出来,放到接口中,这样扩展性更强,该怎么理解这个扩展性更强呢?就像上面那样,面向接口编程。设计模式
假设咱们顽固的不进行抽象,许多类中都放置了相同的方法,那么在使用的时候就很难对旧有的类进行扩展,进行升级,咱们不得不改动旧有的类。那抽象类呢? 该怎么理解抽象类呢? 若是接口是对许多类行为的抽象,那么抽象类就是对这一类对象行为的抽象,抽象的层次是不同的。就像是乳制品企业你们都要实现一个标准,可是怎么实现的国家并无论。抽象类抽象的是鸟、蜂鸟、老鹰。这一个体系的类的共性,好比都会飞。oracle
其实到这里,静态代理基本上就讲完了,代理模式着眼于在不改变旧的类的基础上进行加强,那么加强一般说的就是方法,行为加强,属性是增长。那么为了扩展性强,咱们设计的时候能够将行为放置在接口中或者你放在抽象类里也行,这样咱们就能够无缝加强。关于设计模式,我以为咱们不要被拘束住,我以为设计模式是一种理念,践行的方式不同而已。框架
静态代理着眼于加强一个类的功能,那么当咱们的系统中有不少类都须要加强的时候,就有点不适合了,假设有三十个类都须要加强,且设计都比较好,都将行为抽象放置到了接口中,这种状况下,你总不能写三十个静态代理类吧。固然不能让咱们本身写,咱们让JVM帮咱们写。这也就是动态代理。ide
对于Java程序员来讲,一个对象的建立过程多是这样的:
咱们在思考下,将面向对象的这种思想贯彻到底,思考一下,做为Java程序员咱们使用类来对现实世界的事物进行建模,那么类的行为是否也应该建模呢?也就是描述类的类,也就是位于JDK中的类: java.lang.Class。每一个类仅会有一个Class对象,从这个角度来看,Java中类的关系结构以下图所示:
因此假如我想建立一个对象,JVM的确会将该类的字节码加载进入JVM中,那么在该对象建立以前,该对象的Class对象会先行建立,因此对象建立过程就变成了下面这样:
在建立任何类的对象以前,JVM会首先建立给类对应的Class对象,每一个类仅对应一个,若是已经建立则再也不建立。而后在建立该类的时候,用于获取该类的元信息。函数
咱们这里再度强调一下咱们的目标:
第一个目标咱们可让目标对象和代理对象实现共同的接口,这样咱们就能只着眼于编写目标对象代码的编写。
那第二个目标该如何实现呢? 咱们知道接口是没法实例化的,咱们上面讲了目标对象有一个Class类对象,拥有该类对象的构造方法,字段等信息。咱们经过Class类对象就能够代理目标类,那关于加强代码的编写,JDK提供了java.lang.reflect.InvocationHandler(接口)和 java.lang.reflect.Proxy类帮助咱们在运行时产生接口的实现类。
咱们再回想一下咱们的需求,不想代理类,让JVM写。那么怎么让JVM知道你要代理哪一个类呢?通常的设计思惟就是首先你要告知代理类和目标类须要共同实现的接口,你要告知要代理的目标类是哪个类由哪个类加载器加载。这也就是Proxy类的:
getProxyClass方法,咱们先大体看一下这个方法:
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)throws IllegalArgumentException
该方法就是按照上述的思想设计的,第一个参数为目标类的类加载器,第二个参数为代理类和目标类共同实现的接口。
那加强呢? 说好的加强呢? 这就跟上面的InvocationHandler接口有关系了,经过getProxyClass获取代理类,这是JDK为咱们建立的代理类,可是它没有本体(或者JDK在为咱们建立完本地就把这个类删除掉了),只能经过Class类对象,经过反射接口来间接的建立对象。
因此上面的静态代理若是改形成静态代理的话,就能够这么改造:
private static void dynamicProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { /** * 第一个参数为目标类的加载器 * 第二个参数为目标类和代理类须要实现的接口 */ Class<?> rentHouseProxy = Proxy.getProxyClass(IRentHouse.class.getClassLoader(), IRentHouse.class); //这种由JDK动态代理的类,会有一个参数类型为InvocationHandler的构造函数。咱们经过反射来获取 Constructor<?> constructor = rentHouseProxy.getConstructor(InvocationHandler.class); // 经过反射建立对象,向其传入InvocationHandler对象,目标类和代理类共同实现的接口中的方法被调用时,会先调用 // InvocationHandler的invoke方法有目标对象须要加强的方法。为目标对象须要加强的方法调用所须要的的参数 IRentHouse iRentHouseProxy = (IRentHouse) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { IRentHouse iRentHouse = new Landlord(); System.out.println("方法调用以前............."); Object result = method.invoke(iRentHouse, args); System.out.println("方法调用以后............."); return result; } }); iRentHouseProxy.rentHouse(); }
上面这种写法还要咱们在调用的时候显式的new一下咱们想要加强的类,属于硬编码,不具有通用性,假设我想动态代理另外一个类,那我还得再写一个吗? 事实上我还能够这么写:
private static Object getProxy(final Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces()); Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class); Object proxy = constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + "方法开始执行.........."); Object object = method.invoke(target, args); System.out.println(method.getName() + "方法执行结束.........."); return object; } }); return proxy; }
这样咱们就将目标对象,传递进来了,通用性更强。事实上还能够这么写:
private static Object getProxyPlus(final Object target) { Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法开始执行.........."); Object obj = method.invoke(target, args); System.out.println("方法执行结束.........."); return obj; } }); return proxy; }
而后我想加强一个类,这个类恰巧没有实现接口怎么办? 这就须要Cglib了,其实道理却是相似的,你有接口,我就给你建立实现类,你没接口还要加强,我就给你动态的建立子类。经过“继承”能够继承父类全部的公开方法,而后能够重写这些方法,在重写时对这些方法加强,这就是cglib的思想。
Cglib代理一个类的一般思路是这样的,首先实现MethodInterceptor接口,MethodInterceptor接口简介:
咱们能够这么实现:
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("方法执行前执行"); System.out.println(args); Object returnValue = methodProxy.invokeSuper(obj, args); System.out.println("方法执行后执行"); return returnValue; } }
注意调用在intercept方法中调用代理类的方法,会再度回到intercept方法中,形成死循环。intercept就能够认为是代理类的加强方法,本身调用本身会致使递归,因此搜门上面的调用是用methodProxy调用继承的父类的函数,这就属于代理类。
测试代码:
private static void cglibDemo() { // 通常都是从Enhancer入手 Enhancer enhancer = new Enhancer(); // 设置须要加强的类。 enhancer.setSuperclass(Car.class); // 设置加强类的实际加强者 enhancer.setCallback(new MyMethodInterceptor()); // 建立实际的代理类 Car car = (Car) enhancer.create(); System.out.println(car.getBrand()); }
这种加强是对类全部的公有方法进行加强。这里关于Cglib的介绍就到这里,在学习Cglib的动态代理的时候也查了网上的一些资料,怎么说呢? 老是不那么尽如人意,老是存在这样那样的缺憾。想一想仍是本身作翻译吧,可是Cglib的文档又稍微有些庞大,想一想仍是不放在这里吧,但愿各位同窗注意体会思想就好。
无论是动态代理仍是静态代理都是着眼于在不修改原来类的基础上进行加强,静态代理是咱们手动的编写目标类的加强类,这种代理在咱们的代理类有不少的时候就有些不适用了,咱们并不想为每个须要加强的累都加一个代理类。这也就是须要动态代理的时候,让JVM帮咱们建立代理类。建立的代理类也有两种形式,一种是基于接口的(JDK官方提供的),另外一种是基于类的(Cglib来完成)。基于接口的是在运行时建立接口的实现类,基于类是在运行时建立须要加强类的子类。