基于接口的動態代理是如何實現的?
什么叫動態代理
可以在運行期動態創建某個接口的實例;創建的這個代理實例可以幫我們完成很多事情,常見的比如:RPC框架中通過代理類幫助我們發送請求到遠程服務器端,Mybatis中通過代理類幫助我們映射到xml中的statement,Spring中通過代理類實現事務的控制,日志的打印等等;
如何實現
1.基于 JDK 實現動態代理,通過jdk提供的工具方法
Proxy.newProxyInstance
動態構建全新的代理類;2.基于CGlib 動態代理模式基于繼承被代理類生成代理子類,不用實現接口;
3.基于 Aspectj 實現動態代理,在程序編譯的時候 插入動態代理的字節碼,不會生成全新的Class;
源碼分析
下面以Mybatis為例子,分析一下XXXMapper是如何通過接口調用xml中的statement的,下面看一個常見的實例:
public class BlogMain {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config-sourceCode.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 常規方法
System.out.println(mapper.selectBlog(101));
} finally {
session.close();
}
}
}
BlogMapper是一個接口類,我們通過
session.getMapper
的時候獲取的就是一個動態代理類,由此代理類去映射到xml中的statement;如上代碼中使用openSession創建的一個DefaultSqlSession類,此類中包含了執行了sql的增刪改查等操作,另外還包含了getMapper方法:
此處的Configuration是關鍵,也是Mybatis的一個核心類,可以先簡單理解為就是我們的配置文件mybatis-config.xml的一個映射類;繼續往下走:
這里引出了MapperRegistry,所有的Mapper都在此類中注冊,通過key-value的形式存放,key對應xx.xx.xxMapper,而value存放的是Mapper的代理類,具體如類MapperRegistry代碼所示:
可以看到每次getMapper的時候其實都是去knownMappers獲取一個MapperProxyFactory類,至于是何時往knownMappers中添加數據的,是在解析mybatis-config.xml配置文件的時候,解析到mappers標簽的時候,如下所示:
繼續解析里面的BlogMapper.xml,會把BlogMapper.xml中的namespace作為key,如下所示:
namespace是必填的,此值作為MapperRegistry中的knownMappers的key,而value就是此Mapper類的一個代理工廠類MapperProxyFactory,每次調用getMapper的時候都會newInstance一個實例,代碼如下:
可以發現通過jdk自帶的代理類Proxy.newProxyInstance(...)創建了一個代理類,設置MapperProxy作為InvocationHandler,在實例化MapperProxy時同時傳入了一個methodCache對象,此對象是一個Map,存放的就是每個Mapper里面的方法,這里定義為MapperMethod;至此我們了解了getMapper的大致流程,下面繼續看執行方法;
當我們執行上面的mapper.selectBlog(101)時會自動觸發MapperProxy的invoke方法,此方法會幫我們映射到xml中的statement,然后執行里面配置的sql。