记得每次博主去面试时,都能想象到面试官会像一个饥渴难耐的硬汉,一见到面试者都会使用加特林连环炮疯狂地发问,势必要问到盲区为止,不为别的,就为了心中那种我会你不会的优越感,为了那种心理上的快感,哪怕是自己不会的也问,当面试者回答后若有所思,甚至假想点点头在那不懂装懂;
这不,面试题又来了:“我们都知道mybatis的mapper接口是没有实现类的,在使用的时候你知道它是如何实例化的吗?”
懵逼的我:“知道啊,用的是jdk自带的动态代理;”;
饥渴的面试官:“嗯,没错,继续说,它底层做了哪些事情?”;
懵逼的我:“就是动态代理啊,还有啥?”
得意的面试官:“这样子啊,那你回去等消息吧~”
原理
首先呢,mybatis的mapper接口确实是用jdk动态代理实现的,关键方法是这个newProxyInstance:
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h){
- }
这个方法有什么作用呢? 首先它除了能作为AOP动态代理实现之外,还能用来作为mybatis的mapper接口映射,先看看这个方法是怎么使用的
- package com.proxy_2.proxy;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
-
- /**
- * 生成mapper
- *
- * 面试官的问题: 既然myBatis的mapper映射是个接口,那么它是怎么实例化的呢?
- * 这个问题其实很好回答, 它就使用jdk自带的动态代理实现的,通过实现
- */
- public class MapperMappingTest {
-
-
- public static void main(String[] args) {
-
- Class<UserMapperExt> userMapperClass = UserMapperExt.class;
-
- ExtInvokeHandler handler = new ExtInvokeHandler();
-
- // 关键方法 调用一个方法后获得了
- UserMapperExt mapper = (UserMapperExt) Proxy.newProxyInstance(userMapperClass.getClassLoader(), new Class[]{userMapperClass}, handler);
- int x = mapper.addUser("1", 1);
- System.out.println(x);
- System.out.println(mapper);
- }
- }
-
- /*
- * -使用JDK自带动态代理实例化mapper接口,实现了InvocationHandler接口的调用处理器对象
- *
- * */
- class ExtInvokeHandler implements InvocationHandler {
-
- /// 只有执行mapper接口方法时才会走 invoke 方法
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
- if(Object.class.equals(method.getDeclaringClass())){
- // 如果调用了 Object 的方法,使用当前类的方法;,因为 UserMapperExt 没有实例化,不能直接调用UserMapperExt里的方法;
- return method.invoke(this,args);
- }
- // 不管走mapper的哪个方法,返回值都是 1 ,入股类型不匹配则会抛出异常
- return 1;
- }
- }
-
- // mapper 接口
- interface UserMapperExt {
- //添加用户方法
- public int addUser(String name, int age);
- }
-
从上面的代码可以看到,当我们调用 Proxy.newProxyInstance() 方法时,它并没有去实例化 UserMapperExt 接口,而是将实现了 InvokeHandler 接口的ExtInvokeHandler类作为 UserMapperExt 的实例化对象;眼见为实,我们打个断点看一下
但是当我们调用 UserMapperExt.addUser() 方法的时候,因为这个方法已经在ExtInvokeHandler.invoke() 方法被拦截了,就会直接使用ExtInvokeHandler.invoke方法的返回值,而不会去走真实的 UserMapperExt.addUser() 方法;