MapperProxyFactory(映射器代理工厂)的实现原理

再次回顾Mybatis的基本用法

1、定义Mapper接口

2、在xml(或注解)中写sql

mybatis帮我们屏蔽了所有和数据库相关的操作,我们只需要给他提供参数、sql、标注返回值的类型即可。

通过mapper接口我们可以传递参数、获取返回值;通过xml或者注解我们可以提供需要执行的sql。那么问题来了,究竟是谁在干活?我们经常在service中注入的dao来自于哪里?

答案自然是————代理

代理
那些年我对代理的偏见
代理在我的印象中一直使用来增强对象的,通常这么用

XXXXXX在目标对象方法执行前,做一些事(如日志记录)XXXXXXXX

调用目标对象的方法
method.invoke(target, args)

XXXXXX在目标对象方法执行后,做一些事(如日志记录)XXXXXXXX
大概是这个流程。

但实际上,代理不仅仅可以增强被代理对象的功能,也可以“无中生有”————给它一个接口,它能还你一个实实在在的对象。

实战
在不看mybatis源码的基础上,实现一个功能。

定义一个Dao接口,接口中至少得有一个方法。

我们通过jdk代理获得代理对象,并在service中使用这个代理对象。

Service大概如下

public class HelloService {
private HelloDao helloDao;
 
    public HelloService(HelloDao helloDao) {
        this.helloDao = helloDao;
    }

    public void test() {
        helloDao.sayHello("小明同学");
    }

    public static void main(String[] args) {
        HelloService helloService = new HelloService(ProxyUtils.proxy(HelloDao.class, new MapperHandler(HelloDao.class)));
        helloService.test();
    }
}

补充上图用到的一些组件

接口

public interface HelloDao {
    @Select("select * from hello")
    void sayHello(String name);
    }

自定义的注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
    String value();
}

代理工具类

public class ProxyUtils {
public static <T> T proxy(Class<T> clazz, InvocationHandler invocationHandler) {
    return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, invocationHandler);
	}
}

InvocationHandler实现

public class MapperHandler implements InvocationHandler {
	private Class<?> clazz;
 
public MapperHandler(Class<?> helloDao) {
    this.clazz = helloDao;
}
 
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if(args != null) {
        for (Object arg : args) {
            System.out.println("通过代理拿到了参数:" + arg);
        }
    }
 
    for (Method declaredMethod : clazz.getDeclaredMethods()) {
        for (Parameter parameter : declaredMethod.getParameters()) {
            System.out.println("通过反射拿到了参数以及类型等:" + parameter.getName() + " , " + parameter.getType());
        }
        for (Annotation annotation : declaredMethod.getAnnotations()) {
            if(annotation instanceof Select) {
                Select select = (Select) annotation;
                String value = select.value();
                System.out.println("拿到了sql: " + value);
            }
        }
    }
    return "执行结果";
}

}

运行:

通过反射拿到了接口标注的所有信息,并且成功的生成了代理类。

总结一下如何通过代理实现“无中生有”。或者说通过接口直接生成代理对象,与通过类生成代理对象有何区别?

第一点,“无中生有”在invoke方法中不需要调用被代理对象的方法(因为压根没有被代理对象)

第二点,创建代理对象时,因为clazz是接口,所以newProxyInstance的第二个参数直接传递的是clazz本身,而非之前的 clazz.getInterfaces()

MapperProxyFactory
回头看Mybatis的代理工厂,似乎就很清晰了

似乎与我们的做法一致

mapperProxy其实就是InvocationHandler

至于MapperMethod是什么,以及代理对象如何执行sql的,后面总结。

如有错误,欢迎批评指正!

posted @ 2023-05-25 10:15  戒爱学Java  阅读(87)  评论(0编辑  收藏  举报