《手写Mybatis渐进式源码实践》实践笔记 (第二章 创建简单的映射器代理工厂)


第2章 创建简单的映射器代理工厂

mybatis

背景

代理模式(Proxy Pattern)是一种设计模式,它为其他对象提供一个代理或占位符以控制对这个对象的访问。在Java中,代理模式可以通过静态代理和动态代理两种方式实现。

静态代理

静态代理在编译时就已经确定代理类和目标类的关系。代理类和目标类通常实现相同的接口。

步骤

  1. 定义主题接口:代理类和目标类都实现这个接口。
  2. 创建目标类:实现主题接口的具体业务逻辑。
  3. 创建代理类:实现主题接口,并在内部持有目标类的引用,增加额外的处理。
  4. 客户端代码:使用代理类来访问目标对象。

示例代码

// 主题接口
public interface Subject {
    void request();
}

// 真实主题
public class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 代理类
public class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy() {
        this.realSubject = new RealSubject();
    }

    public void request() {
        preRequest();
        realSubject.request();
        postRequest();
    }

    private void preRequest() {
        System.out.println("Proxy: Logging the time of request.");
    }

    private void postRequest() {
        System.out.println("Proxy: Logging the time of request completion.");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.request();
    }
}

动态代理(JDK Proxy)

动态代理在运行时动态创建代理类,代理类不需要与目标类实现相同的接口,而是通过反射机制来调用目标对象的方法。

步骤

  1. 创建目标接口:代理类和目标类都实现这个接口。
  2. 创建目标类:实现目标接口的具体业务逻辑。
  3. 创建代理工厂:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理对象。
  4. 客户端代码:使用代理对象来访问目标对象。

示例代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 目标接口
public interface Subject {
    void request();
}

// 真实主题
public class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 代理工厂
public class ProxyFactory implements InvocationHandler {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before the request.");
        Object result = method.invoke(target, args);
        System.out.println("After the request.");
        return result;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxyFactory factory = new ProxyFactory(realSubject);
        Subject proxyInstance = (Subject) factory.getProxyInstance();
        proxyInstance.request();
    }
}

在动态代理中,InvocationHandlerinvoke方法会在目标对象的方法被调用之前和之后执行,允许在目标方法执行前后添加额外的处理逻辑。这种方式使得代理模式的应用更加灵活,不需要在编译时就确定代理类。

目标

Mybatis ORM框架的实现,首先解决第一个问题:通过代理方式实现数据库操作接口IXxxDao和映射器mapper的关联。

设计

设计一个 ORM 框架的过程,需要要考虑怎么把用户定义的数据库操作接口xml配置的SQL语句数据库三者联系起来。这里使用代理的方式进行处理,实现数据库操作接口和映射器的关联。因为代理可以封装一个复杂的流程为接口对象的实现类,设计如图 :

image-20241206174119649

  • 首先提供一个映射器的代理实现类 MapperProxy,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。
  • 之后对 MapperProxy 代理类,提供工厂实例化操作 MapperProxyFactory#newInstance,为每个 IDAO 接口生成代理类。这块其实用到的就是一个简单工厂模式

实现

工程代码

image-20241206182731278

类图

image-20241206183441882
  • MapperProxy 负责实现 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法包装的逻辑。
  • MapperProxyFactory 是对 MapperProxy 的包装,对外提供实例化对象的操作。当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。

实现步骤

1.映射器代理类MapperProxy
  • 通过实现 InvocationHandler#invoke 代理类接口,封装操作逻辑,对外接口提供数据库操作对象。
  • 目前只是简单的封装了一个 sqlSession 的 Map 对象,你可以想象成所有的数据库语句操作,都是通过接口名称+方法名称作为key,操作作为值的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。当然这还只是最核心的简化流程,后续不断补充内容后,会看到对数据库的操作
  • 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加 Object.class.equals(method.getDeclaringClass()) 判断。
public class MapperProxy<T> implements InvocationHandler, Serializable {

    // sql会话.
    private Map<String, Object> sqlSession;

    // 映射器接口.
    private final Class<T> mapperInterface;

    public MapperProxy(Map<String,Object> sqlSession, Class<T> mapperInterface){
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(Object.class.equals(method.getDeclaringClass())){
            return method.invoke(this,args);
        } else {
            return sqlSession.get(mapperInterface.getName()+"."+method.getName());
        }
    }
}

2.代理类工厂MapperProxyFactory
  • 工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用 Proxy.newProxyInstance 进行处理,那么这样的操作方式就显得比较麻烦了。
public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface){
        this.mapperInterface = mapperInterface;
    }


    // 创建代理对象.
    public T newInstance(Map<String, Object> sqlSession){
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

}

测试

事先准备

定义一个数据库接口 IUserDao

IUserDao

public interface IUserDao {

    String queryUserName(String uid);

    Integer queryUserAge(String uid);
}

测试用例

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);


    // 测试mapperProxyFactory
    @Test
    public void testMapperProxyFactory(){
        //创建MapperProxyFactory
        MapperProxyFactory<IUserDao> mapperProxyFactory = new MapperProxyFactory<>(IUserDao.class);

        //模拟sqlSession, 方法名和方法调用响应.
        Map<String, Object> sqlSession = new HashMap<>();
        sqlSession.put("cn.suwg.mybatis.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名:小苏");
        sqlSession.put("cn.suwg.mybatis.test.dao.IUserDao.queryUserAge", 28);
        IUserDao userDao = mapperProxyFactory.newInstance(sqlSession);

        //调用方法
        String result = userDao.queryUserName("10001");

        Integer age = userDao.queryUserAge("10001");
        logger.info("测试结果:{}, 年龄:{}",result, age);
    }
}

测试结果

image-20241206182446023

从输出的结果来看,我们的接口已经被代理类实现了,可以在代理类中进行自己的操作封装。那么在我们后续实现的数据库操作中,就可以对这部分内容进行扩展了。

总结

  • 本章节我们初步对 Mybatis 框架中的数据库 DAO 操作接口和映射器通过代理类的方式进行链接,这一步也是 ORM 框架里非常核心的部分。后续就可以在代理类中进行自定义的逻辑扩展了。
  • 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节,这个设计模式的使用,也可以迁移到大家的日常开发中。
  • 目前内容还比较简单的,可以手动操作练习,随着我们内容的增加,会有越来越多的包和类引入,完善 ORM 框架功能。

参考书籍:《手写Mybatis渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/book-small-mybatis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值