spring的aop(Aspect Oriented Programming:面向切面编程)机制是基于动态代理实现的,那么动态代理是怎样的呢?为什么动态代理可以实现切面的效果呢?
其实在理解动态代理的时候,可以先去学习静态代理,也可以先去了解代理模式。静态代理理解了,那么动态代理就会比较好理解。这里先介绍一下静态代理。
在《大话设计模式》中有一章节来讲解代理模式,其中有一个例子是这样的:说小明喜欢上了班里的一位女同学,但性格腼腆不敢直接追求,于是就委托女同学的闺蜜,每天买了花呀,布偶呀就通过闺蜜送给那位女同学。这样一来,其实花和布偶都是 小明同学送的,只不过在送给这位女同学的时候是经过闺蜜的手送的,也就是说,和女同学交互的人是闺蜜,真正送花的主人是小明。这里呢,就可以把闺蜜当做是小明的代理。代码实现如下:
声明一个送礼物接口:
1 public interface GiveGift { 2 void sendFlower();// 送花 3 void giveDolls();// 送布偶 4 }
小明暗恋的女同学类:
1 public class SchoolGirl { 2 private String name; 3 public String getName() { 4 return name; 5 } 6 public SchoolGirl(String name) { 7 this.name = name; 8 } 9 public void setName(String name) { 10 this.name = name; 11 } 12 }
追求者(小明)要送礼物,继承上面的接口:
1 public class Pursuit implements GiveGift { 2 SchoolGirl girl; 3 public Pursuit(SchoolGirl girl) { 4 this.girl = girl; 5 } 6 @Override 7 public void sendFlower() { 8 Logger.getLogger(Pursuit.class).info("Pursuit送给" + girl.getName() + "的花"); 9 //System.out.println("Pursuit送给" + girl.getName() + "的花"); 10 } 11 @Override 12 public void giveDolls() { 13 Logger.getLogger(Pursuit.class).info("Pursuit送给" + girl.getName() + "的布偶"); 14 // System.out.println("Pursuit送给" + girl.getName() + "的布偶"); 15 } 16 }
小明要经过女同学闺蜜的手去送礼物,这里声明代理类(也就是闺蜜):
1 public class Proxy implements GiveGift { 2 private GiveGift p;//这里使用接口 3 public Proxy(SchoolGirl girl) { 4 p = new Pursuit(girl); 5 } 6 @Override 7 public void sendFlower() { 8 //这里可以做一些处理 before 9 p.sendFlower(); 10 //这里可以做一些处理 after 11 } 12 @Override 13 public void giveDolls() { 14 //这里可以做一些处理 before 15 p.giveDolls(); 16 //这里可以做一些处理 after 17 } 18 }
这里可以看到,在送礼物这些方法里面其实都是调用的追求者的方法,代理同样继承了送礼物的接口,这样的话在追求者调用方法的前面和后面 代理类就可以做一些处理了。这个就可以看做是简单的切面了。
最后看一下客户端如何去调用:
我们先来看一下InvocationHandler这个接口的唯一一个方法 invoke 方法
1 public class ProxyTest { 2 public static void main(String[] args) { 3 SchoolGirl girl = new SchoolGirl("tingting"); 4 GiveGift p = new Proxy(girl);//代理类,也就是闺蜜 5 p.giveDolls();//闺蜜送布偶 6 p.sendFlower();//闺蜜送花 7 } 8 }
执行结果如下:
[INFO ] 2017-04-23 11:26:04,126 method:com.designmode.staticproxy.Pursuit.giveDolls(Pursuit.java:27) Pursuit送给tingting的布偶 com.designmode.staticproxy.Pursuit@9807454 [INFO ] 2017-04-23 11:26:04,129 method:com.designmode.staticproxy.Pursuit.sendFlower(Pursuit.java:20) Pursuit送给tingting的花 com.designmode.staticproxy.Pursuit@9807454
从标红的地方可以看到:在Proxy类调用送布偶和花时,实际上使用的是追求者Persuit(小明)对象。
这里先介绍静态代理的目的有两个:1、代理类调用接口方法时实际上是使用被代理类对象来调用相应的方法 2、可以通过代理类的方法看到一些切面的雏形。
接下来介绍动态代理。
动态代理
从上面可以看到,其实静态代理的实现是有一些规律的,(代理类继承被代理类接口,在实现接口方法时调用被代理类对象调用自己的方法来实现。)这些可以通过反射机制动态获取到,封装成类提供相应方法。开发者只需要调用开发的方法,然后传递参数即可。这也就是动态代理类。
实现动态代理类涉及的类或接口有两个:InvocationHandler(接口)和Proxy(类)。这也就是上面说的封装的类。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:
InvocationHandler:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
上面这段话比较重要的几点:
- 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到一个handler。(就是必须要实现InvocationHandler接口,代理类实例如何关联到handler?后面讲)
- 通过代理对象调用一个方法的时候,方法调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用。(代理对象如何产生的?invoke()方法是怎样的?)
我们先来看一下InvocationHandler这个接口的唯一一个方法 invoke 方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
这个方法有三个参数:三个参数的具体含义如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy: 指代我们所代理的那个真实对象 method: 指代的是我们所要调用真实对象的某个方法的Method对象 args: 指代的是调用真实对象某个方法时接受的参数
OK,现在我们知道了invoke()方法是怎样的,那么其他两个疑问点呢?1、代理对象是如何产生的?2、代理类如何关联到handler?上面还提到了一个类,Proxy类呀,再来看这个类,估计可以解开上面两个疑问点。
Proxy:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
果然,Proxy类用来动态创建代理对象。再来看一下newProxyInstance()这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
创建一个动态代理对象需要三个参数,第一个参数是说谁来加载这个生成的对象,第二个参数是说这个动态代理对象要实现哪些参数(这个可以和静态代理做类比),第三个参数关联到哪个InvocationHandler上面,这不就是上面的第二个疑问点吗?如何关联呢?就是要传递一个InvocationHandler类型的参数呀。
到这里动态代理涉及到的类和接口就差不多了解了,下面来一个具体的例子来看一下到底是怎么使用的。
还是举上面那个例子,礼物接口和追求者类不变,变的是代理类Proxy,为了防止混淆,这里名字叫做DynamicProxy。怎么做呢?
1 public class DynamicProxy implements InvocationHandler{ 2 private Object pursuit;//这个是要被代理的类,也就是追求者类 3 public Object newProxyInstance(Object obj){ 4 this.pursuit = obj; 5 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this); 6 } 7 @Override 8 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 9 method.invoke(pursuit,args); 10 return null; 11 } 12 }
分析一下上面的代码:
- 继承InvocationHandler,因为文档已经说了必须要实现这个接口
- 实现invoke()方法,上面有提到:当代理类调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。调用啥呢?就是被代理类调用自己的方法。也就是:method.invoke(pursuit,args);
- 生成代理类实例:也就是Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this):使用被代理类的类加载器,使用被代理类的全部接口,绑定的handler就是DynamicProxy对象本身。
1 public class Main { 2 public static void main(String[] args) { 3 SchoolGirl girl = new SchoolGirl("tingting"); 4 Pursuit pursuit = new Pursuit(girl); 5 DynamicProxy proxy = new DynamicProxy(); 6 GiveGift p = (GiveGift) proxy.newProxyInstance(pursuit); 7 p.giveDolls(); 8 p.sendFlower(); 9 } 10 }
最后执行以下,输出结果和静态代理相同。
参考文章:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html