回想一下咱使用mybatis的时候,只定义了操作表的接口以及方法,并没有定义接口的实现类,一行jdbc代码都没写,就可以对数据库增删查改。哪有什么岁月静好,不过是有人替你负重前行。是勤恳的mybatis帮我们做了实现类,写了jdbc代码,并把mapper里的sql注入进去了。
梳理一下,框架的使用者需要做的事情包括:
- 定义表的接口(如ActorPublicNoticeDAO)。
- 定义表接口对应的XML,XML中包含查询SQL所需的参数,如表名、字段名等。
- 调用表的接口方法,无需实现接口,直接调用即可。
框架的做的事包括:
- 解析使用者定义的接口和XML配置。
- 实现接口方法,在方法内部加入具体的JDBC执行代码,根据XML配置获取参数设置到JDBC执行代码中。
现在有一个很明显的问题,先现有框架,后有使用者,写框架的时候还不知道有什么接口,怎么写接口的实现类?
答:其实很简单,不知道有什么接口就约定一个指定路径,路径下的接口都扫描出来。
那怎么写接口的实现类呢?虽然写框架的时候不知道有什么接口,但是调用的时候就知道了呀。这不由让人想起来一句典型的定义:一种在运行时创建代理对象的机制,而不是在编译时手动编写代理类,这就是动态代理技术。
要使用Java动态代理,需要做以下几个步骤:
一、创建接口和方法。在这个项目里就是ActorPublicNoticeMapper#getInfoById。
1 |
|
二、使用Proxy.newProxyInstance()方法创建接口的实例(即代理对象)。咱定义一个SqlSession类专门做这件事,你给它一个接口mapper,它给你返回一个mapper实例。
1 | public class SqlSession { |
三、创建一个实现InvocationHandler接口的类,调用接口时,实际上是调用InvocationHandler中的invoke()方法。在这个项目里就是MyInvocationHandler。
1 |
|
Proxy.newProxyInstance()底层到底做了什么事呢,点进去看看这个方法吧。
1 | public static Object newProxyInstance(ClassLoader loader, |
在编译时不知道类还需要构造一个类的对象好说,用反射生成。那么是怎么生成类的呢。一般的类都是我们编译前就写好的.java文件,jvm把.java文件编译成字节码,并以.class文件存储。而动态代理是在运行时直接生成了.class文件。让我们点进代码一探究竟。
分别跳入如下方法:
java.lang.reflect.Proxy#newProxyInstance
java.lang.reflect.Proxy#getProxyClass0
java.lang.reflect.WeakCache#get
这里的代码比较绕,加入了一些缓存相关的处理,我们只需要抓住核心流程。
java.lang.reflect.Proxy.ProxyClassFactory#apply
sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
sun.misc.ProxyGenerator#generateClassFile 这个方法就是动态代理生成字节码的核心代码,主要干的事有:
- 把接口所有的方法都生成了代理方法,还默认带了3个方法hashCodeMethod、equalsMethod、toStringMethod。
- 还生成了魔术头、小版本号、主版本号、类方法标志、当前类、超类、接口个数、每个实现接口、属性个数、每个属性、方法个数、每个方法等信息。
1 | private byte[] generateClassFile() { |
1 |
|
1 | ``` |
`