您的位置:首页 > 游戏 > 游戏 > 百度街景地图网页版_便宜模板网站建设_百度人工服务热线24小时_整合营销传播策略

百度街景地图网页版_便宜模板网站建设_百度人工服务热线24小时_整合营销传播策略

2024/11/17 22:09:30 来源:https://blog.csdn.net/wyy546792341/article/details/142704843  浏览:    关键词:百度街景地图网页版_便宜模板网站建设_百度人工服务热线24小时_整合营销传播策略
百度街景地图网页版_便宜模板网站建设_百度人工服务热线24小时_整合营销传播策略

2.2Mybatis——代理与SQL映射

  • 1.代理模式
  • 2.如何执行接口方法
    • 示例demo
  • 3.Mybatis是如何做的
    • 3.1猜想
    • 3.2源码探究
  • 梳理与总结

“Java中非静态方法的运行需要实例对象才能运行(即对象点方法),Mybatis中的Mapper都是接口,也没有实现类,那么接口中的方法怎么就被调用执行了呢?”

这是当时用Mybatis时最困扰我的一个问题,搜的资料博客中,大多来一句“动态代理”一笔带过。留我一人风中凌乱,难道"动态代理"四个字这么形象生动、易于理解吗…
也因为这个疑问,了解了代理模式,动态代理,以及Mybatis如何使用动态代理完成mapper接口方法的调用。

1.代理模式

这里简单说一下,例子就不举了,网上都是。说一下工作中的使用场景,一般是某个类无法修改或不敢改动,那就在目标类外面包一层代理类,即不用修改目标类,又可以对目标类做功能的增强。其原理就是:通过实现和目标类相同的接口来平替目标类,然后通过构造函数获取目标类的对象引用,增强的功能由代理类完成,核心的逻辑依旧通过目标类进行调用。(我记得好像写过对应的设计模式笔记,感兴趣的可以翻一下)

2.如何执行接口方法

前面提到的代理模式和文章开头的问题有关系吗?当然有关系,如果你理解了什么是代理,文章开头的问题就可以通过代理来实现。
即使接口没有实现类,也可以通过InvocationHandler为其创建代理类,通过代理类执行接口方法。这是java反射包下的一个接口,可以为接口生成代理对象。简单代码示例:

示例demo

// 定义接口
public interface Interface01 {String m1();int m2();
}
// 定义调用处理器
public class Interface01Handler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("m1")) {return "hello world";}else  {return 1024;}}
}
// 创建代理对象测试
public static void main(String[] args) {// 生成代理对象Object proxy = Proxy.newProxyInstance(Interface01.class.getClassLoader(),new Class[]{Interface01.class}, new Interface01Handler());if(proxy instanceof Interface01) {// 调用接口方法Interface01 a = (Interface01) proxy;System.out.println(a.m1());System.out.println(a.m2());}
}

上述代码中,接口并没有实现类,但接口方法可以被调用。当然你也可以理解成:接口是以另一种形式被实现,接口方法以另一种形式被定义。这种在JVM运行期间动态的为接口创建代理对象的过程,我们称为JDK动态代理。

3.Mybatis是如何做的

通过反射可以为接口创建代理对象。知道了Java中的这个机制后,回到Mybatis的疑问,mapper也是接口,也没有实现类但最终其方法可以被调用,类比一下,你应该可以猜到Mybatis是如何来实现的。

3.1猜想

我们只定义了接口和方法,在未提供接口实现类的情况下却可以直接对接口方法进行调用。基于Mybatis的这个特性,再结合demo示例中Java反射提供的机制。于是我们猜想:

  1. Mybatis内部应该也是使用了JDK动态代理来执行mapper接口中的方法;
  2. 由于JDK动态代理中需要调用处理器去执行方法逻辑,我们虽然没有为mapper接口编写对应的调用处理器。但是我们为每个mapper接口定义了同名了mapper.xml,而且mapper.xml中的SQL片段其实就是这个方法的执行逻辑。所以Mybatis内部应该做了某些处理,调用处理器的invoke方法执行的应该就是对应的SQL片段:
// 调用处理器 InvocationHandler.invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// <select id="">// <insert id="">// <update id="">// <delete id="">
}

上面是我们合理的猜想,如果得到印证,那么Mybatis内部使用动态代理的过程也就清晰了。接下来我们通过调试源码进行印证。

3.2源码探究

@SpringBootTest
public class SqlMappingTest {@ResourceAddressMapper addressMapper;@Testpublic void test(){Address address = addressMapper.selectById(1L);System.out.println(address);}
}

以查询为例,我们都知道,spring容器为我们注入了一个代理对象addressMapper,代理对象类型为MapperProxy,刚好实现了InvocationHandler接口:

public class MapperProxy<T> implements InvocationHandler, Serializable {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}
}

再看一下invoke的执行逻辑是否是调用mapper.xml中的SQL片段,以及它是如何进行调用:

public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {...}public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {...break;}case UPDATE: {...break;}case DELETE: {...break;}case SELECT:...break;case FLUSH:...break;default:throw new BindingException("Unknown execution method for: " + command.getName());}return result;}
}

上述代码中可以看到:
mapper.xml中的SQL片段最终被加载成MapperMethod对象,InvocationHandler.invoke的调用是执行MapperMehtod.execute方法。execute方法就是将SQL分成CRUD几个类型,然后根据当前MapperMehtod的类型进行SQL的执行,显然后面还有SQL参数、结果集的处理等逻辑,但我们的猜想到这里其实就差不多可以被印证了。

扩展:虽然关于Mybatis中未实现的Mapper接口如何被调用执行 的猜想已经被印证,其过程和我们的猜想符合。但在过程中可能会引出其他的思考,比如:

  1. 代理对象是如何创建的;
  2. mapper.xml中的方法如何被转换为MapperMethod对象;
  3. execute方法中,确定SQL类型后,SQL的处理、结果集的处理等等

这些如果有精力的话,后面会陆续展开描述。

梳理与总结

  1. JDK的动态代理是通过实现InvocationHandler接口的形式来实现的。即使接口没有定义实现类,也可以通过JDK动态代理的方式为接口创建代理对象。就接口实现而言,你确实可以把InvocationHandler的这种机制看成是另一种接口实现,所以,既然可以直接去实现接口,为什么还要多此一举去使用InvocationHandler呢?如果你有此疑问,Mybatis给出了很好的答案:接口一定需要显式定义实现类吗?接口和实现可以解耦,定义的接口不再局限于通过接口实现类去实现,可以根据需要以其他方式(mapper.xml)去实现接口;
  2. Mybatis中,MapperProxy通过实现InvocationHandler接口为mapper接口创建代理对象 ;
  3. 根据InvocationHandler的调用机制,对mapper接口中方法的调用,最终都将交给调用处理器的invoke方法,Mybatis在该方法中完成了对mapper.xml SQL方法的实际调用

  • 如有理解错误或不足之处,欢迎大家留言讨论

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com