打算写一个Java新手入门必须要会的系列,这个系列是入门之后,必须要学会理解的东西,对于以后的Java开发会特别有帮助,第一个选定的题材就是动态代理,理解了动态代理对于理解Spring AOP和mybatis,有特别大的帮助。
动态代理可以做什么?
可以对代码进行增强,例如给方法增加事务、日志、统计信息等(Spring AOP),mybatis框架也是用到了动态代理。
简单说下什么是动态代理
在编程中,类A有个方法buyTicket(),这个方法就是买票,我们要调用买票方法,常规是先new A(),然后就直接调用就行了,动态代理是通过某种方法,动态生成了一个类,这个类可以帮助我们调用买票的方法,当我们执行A.buyTicket()时候,实际是动态代理类调用买票的接口并返回结果。看起来就好像我们自己执行了buyTicket()一样。程序员是感觉不到代理类的存在的。
如果你懂代理服务器的概念,可能会更容易理解代理这个概念。
所以重点就是有2个:
1、某种方法,动态生成了一个类,是什么方法?
2、为什么我们感觉不到代理这个行为?
静态代理
先说个静态代理,静态代理的意思是我们写一个ProxyA类(动态代理是自动生成的代理类),代替我们执行buyTicket()方法:
1 | class ProxyA { |
这样做的坏处就是每次代理一个类,虽然没改A类,但是都需要手工增加1个类,开发人员很苦恼,增加了很多的重复工作量。每次需要买票的时候,都是明确的调用了ProxyA的方法,可以很明显感受到代理类的存在。
为什么感觉不到代理的行为?
代理类帮我们做了事情,但是为什么程序员没有感觉到这个行为呢?或者说是怎么做到没有侵入的呢?一般来说,要给方法A加日志,必须要修改方法A的源代码,为啥动态代理就可以做到不改A的源代码,还可以在调用A的时候加日志呢?
答案是在调用方法buyTicket的前后增加了日志或者事务代码。
但是这也解释不了,为什么我们感受不到代理行为呢?因为静态代理,用的是组合,动态代理,用的是接口和继承。动态代理类要知道目标类的方法,有2种方式,一种是继承A类,一种是A类是个接口,代理类实现这个接口,就可以知道接口有哪些方法了。
代理类继承或者实现了目标类
这样在框架里,例如Spring,在生成目标类的时候,实际上生成的是代理类,调用目标类的所有的方法,都是调用代理类来实现的,这个动作在框架里做好了,所以程序员感受不到被代理了。
1 | //源代码 |
某种方法是什么方法?
对于Spring来说:
如果被代理的类是一个接口,那么就会使用JDK的动态代理。
如果被代理的类不是接口,就只能使用cglib来生成代理类,cglib底层使用的是asm框架来动态
生成了字节码。
对于mybatis来说:
应该就是JDK的动态代理。
来一点代码?
往上关于代理的代码有很多,关键点是java.lang.reflect.InvocationHandler
和java.lang.reflect.Proxy
这2个类,具体的代码我就不贴了,可以自己去搜索Java动态代理,有很多的例子。
需要注意的一些知识
由于动态代理需要接口或者继承,所以如果你用spring框架,那么在使用AOP的时候,需要注意以下几点:
- 由于final类不能被继承,所以final类不能被代理。
- 如果方法是final的方法,代理失败。
- 如果方法是static的,代理失败。
- 私有的private方法也不能被代理,所以你要想spring的事务生效,必须是public方法;这里需要注意的是,同1个类里有A、B2个方法,B方法加了事务的注解,A方法调用B方法,事务不会生效,可以思考下为什么?
- cglib生成的代理类是目标类的子类,会重写目标类的所有方法。
对于第2条和第3条,分情况讨论,如果是接口方式,由于接口中的方法不能是final和static,所以不能被代理;
如果是普通的cglib,使用继承重写机制来实现代理,那么final和static方法,都不能被重写,自然也就不能被代理了。