本文将通过模拟 Mybatis 动态代理生成 Mapper 代理类, 讲解 Mybatis 原理
1. 平常我们是如何使用 Mapper 的
先写一个简单的 UserMapper, 它包含一个全表查询的方法, 代码如下
- public interface UserMapper {
- @Select("select * from user")
- public List<User> queryAll();
- }
然后大家思考一个问题, 我们平时是怎么使用这个 UserMapper 的?
很多时候我们会把 Mybatis 和 Spring 整合起来一起使用, 于是会有类似下面的代码:
- @Service
- public class UserServiceImpl {
- @Autowired
- private UserMapper userMapper;
- public List<User> queryAll(){
- return this.userMapper.queryAll();
- }
- }
看到这段熟得不能再熟的代码不知道大家会不会有一丝疑惑: UserMapper 明明是一个接口, 为什么可以直接调用他的 queryAll 方法呢?
这个问题其实也不难解, 我们不能直接调用一个接口的方法, 这背后肯定是有一个对象的, 至于这个对象是怎么来的, 这里直接告诉大家是通过动态代理生成的. 只要弄懂了这个动态代理对象是怎么生成的, 整个 Mybatis 框架原理就就说清楚了. 在模拟之前我们先验证一下是不是使用 JDK 的动态代理.
2. 验证 Mybatis 是通过动态代理生成 Mapper 代理对象
由于上面一段代码整合了 spring,spring 又为我们封装了许多细节, 我们重新看一段代码, 看看没有 spring 的情况下我们怎么获得一个 UserMapper
- public static void main(String[] args){
- SqlSessionFactory sqlSessionFactory = .... // 这里省略, 官网给了很多配置 SqlSessionFactory 的方法 (不一定是这么获得)
- SqlSession session = sqlSessionFactory.openSession();
- UserMapper userMapper = session.getMapper(UserMapper.class);
- }
为了验证获得 UserMapper 采用的是动态代理, 我们可以在 IDE 中对着 session.getMapper(UserMapper.class) 一路按着 Ctrl 点进去, 我们会发现最终调用的代码是这样的:
- protected T newInstance(MapperProxy<T> mapperProxy) {
- return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
最后果然调用的是 JDK 的动态代理
3. 动手模拟一次 Mybatis 的动态代理
既然知道了原理, 下面我们就动手验证吧!
为了简单明了, 我们不写 SqlSessionFactory 类了, 直接自定义一个 MySession 类, 在里面给出模拟的 getMapper 方法:
- public class MySession {
- public static Object getMapper(Class clazz){
- // 调用 newProxyInstance 需要传入 class 数组
- Class[] clazzs = new Class[]{clazz};
- // 把动态代理过程中生成的代理类保留下来, 有助于新手理解动态代理 (此行可省略)
- System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
- // 通过动态代理生成 Mapper
- Object object = Proxy.newProxyInstance(MySession.class.getClassLoader(), clazzs, new MyInvocationHandler());
- return object;
- }
- }
如果不熟悉 JDK 的动态代理也没关系, 下面我会逐步分析.
大家可以看到调用动态代理生成动态代理对象需要三个参数:
类加载器
class 数组
InvocationHandler 实例
为什么需要类加载器?
动态代理生成类和其调用类必须通过同一个类加载器加载, 否则它们之间无法相互调用
为什么有个 class 数组?
JDK 的动态代理是基于接口的, class 数组中存放的是动态代理类需要实现的接口. 比如本文中的例子生成的动态代理类需要实现 UserMapper 接口, 所以你得把接口告诉它.
为什么会有 InvocationHandler 实例?
动态代理会在原有方法上实现增强, 而增强的逻辑就写在 InvocationHandler 类的 invoke 方法上, 所以要有这么个实例.
你想一想, 当初的这段代码
- public interface UserMapper {
- @Select("select * from user")
- public List<User> queryAll();
- }
, 我们想实现一个怎样的功能?
无非就是给它一条 sql 语句, 希望它能去数据库中执行这条 sql 语句并返回结果. 这个过程可以拆分成两个部分:
得到 sql 语句
通过 JDBC 操作数据库, 并执行 sql 返回结果
这里省略 JDBC 的过程, 给出一个简单的 invoke 方法示例 (Mybatis 为我们封装了一切 JDBC 的处理细节):
- public class MyInvocationHandler implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 解析得到 sql
- Select annotation = method.getAnnotation(Select.class);
- String sql = annotation.value()[0];
- // 执行 sql(模拟 JDBC)
- System.out.println(sql + "executing...");
- return null;
- }
- }
最终我们执行 UserMapper 的 queryAll() 方法时, 就会出现如下结果:
- public class Main {
- public static void main(String[] args) {
- UserMapper userMapper = (UserMapper) MySession.getMapper(UserMapper.class);
- userMapper.query();
- }
- }
- // 打印:
- //select * from user executing...
总结
最后我们总结一下 Mybatis 框架的核心原理:
用户只需要创建 Mapper 接口, 并使用 Mapper 接口即可.
Mybatis 会对 Mapper 接口产生动态代理对象, 这个动态代理对象实现了 Mapper 接口, 拥有 Mapper 中定义的所有方法, 并对这些方法进行了增强. 增强的逻辑是获得 sql 语句和执行 sql 语句.
通过个核心原理我们也就知道了 Mybatis 为我们做了什么:
让方法和 sql 语句对应起来, 操作数据库就如同调用方法一般简单
屏蔽掉 JDBC 的细节
来源: http://www.bubuko.com/infodetail-3383759.html