再次回顾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的,后面总结。
如有错误,欢迎批评指正!