当前位置: 首页 > 科技观察

超全MyBatis动态代理详解!

时间:2023-03-20 14:41:32 科技观察

本文转载自微信公众号“源码兴趣圈”,作者:龙泰。转载本文请联系源码兴趣圈公众号。前言如果有人问你这几个问题,看看你能不能回答。MybatisMapper接口没有实现类,如何实现动态代理JDK动态代理为什么类不能被代理(收话费的问题)JDK能否动态实现抽象类代理的铁汁(补充问题)不能回答证明Proxy和Mybatis的源码还没看过。不过没关系,继续往下看,了解动态代理的实战。众所周知,Mybatis底层包使用的JDK动态代理。在说Mybatis动态代理之前,先来看看我们平时写的动态代理demo。一般来说,定义一个JDK动态代理分为三个步骤。如下图,定义代理接口,定义代理接口实现类,定义动态代理,调用处理器。步骤代码如下,玩过动态代理的朋友看完就能明白publicinterfaceSubject{//定义代理接口StringsayHello();}publicclassSubjectImplimplementsSubject{//定义代理接口实现类out.println("HelloWorld");return"success";}}publicclassProxyInvocationHandlerimplementsInvocationHandler{//定义动态代理调用处理器privateObjecttarget;publicProxyInvocationHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("🧱🧱🧱进入代理调用处理器");returnmethod.invoke(target,args);}}1.写一个测试程序,运行看看效果,也是分三步2.创建代理接口的实现类创建动态代理类,说说三参数类加载器中代理类实现的接口数组3.使用处理器(调用代理类方法,每次都通过它)由代理实现类调用publicclassProxyTest{publicstaticvoidmain(String[]args){Subjectsubject=newSubjectImpl();Subjectproxy=(Subject)Proxy.newProxyInstance(subject.getClass().getClassLoader(),ssubject.getClass().getInterfaces(),newProxyInvocationHandler(subject));代理.sayHello();代理实现:HelloWorld*/}}Demo功能已经实现,大致的操作流程也清楚了。下面分析一下原理实现。从原理的角度分析动态代理原理。上面的动态代理测试程序怎么样?执行的第一步简单明了。Subject接口的实现类就创建好了,这也是我们常规的实现。第二步创建代理对象的动态代理对象这里有朋友问,如何证明这是一个动态代理对象呢?如图所示,JDK动态代理对象的名称是有规则的。对于任何通过Proxy类生成的动态代理对象,前缀必须是$Proxy,后面的数字也是名称。如果有小伙伴想一探究竟,关注Proxy内部类ProxyClassFactory,这里会有你想要的答案返回正题,继续看ProxyInvocationHandler,内部维护了代理接口实现类的引用,而invoke方法内部使用了反射调用代理接口的实现类方法可以看出生成的动态代理类继承了Proxy类,然后实现了Subject接口,实现方法sayHello实际上调用了ProxyInvocationHandler的invoke方法.无意中发现JDK动态代理不能代理类的原因^^也就是说,当我们调用Subject#sayHello时,方法调用链是这样的。但是Demo中有proxied接口的实现类,而MybatisMapper没有。怎么玩都无所谓。我知道我可能再也看不到这个了。我们来看看mybatis源码是如何工作的。Mybatis版本:3.4.xMybatis源码实现不知道大家有没有考虑过这样的问题。为什么MybatisMapper不需要实现类?如果说,我们项目中采用的三层设计,Controller控制请求接收,Service负责业务处理,Mapper负责数据库交互。Mapper层就是我们常说的数据库映射层,负责对数据库的操作,比如查询数据或者增删改查等。大胆想象一下,项目不用Mybatis,需要写数据库交互在Mapper实现层。会写什么?会写一些常规的JDBC操作,比如://加载Mysql驱动Class.forName(driveName);//获取连接con=DriverManager.getConnection(url,user,pass);//CreateStatementStatementstate=con.createStatement();//构建SQL语句StringstuQuerySqlStr="SELECT*FROMSTUDENT";//执行SQL返回结果ResultSetresult=state.executeQuery(stuQuerySqlStr);...如果项目中所有的Mapper实现层都要这样玩,那岂不是很想打人...于是结合痛点Mybatis应运而生项目的要点。怎么做?与JDBC交互的操作在底层由JDK动态代理封装。用户只需要自定义Mapper和.xml文件即可。SQL语句在.xml文件或Mapper中定义。项目启动时,解析器解析SQL语句,组装成Java中的对象。解析器有很多种,因为Mybatis不仅有静态语句,还包含动态SQL语句。这也是为什么Mapper接口不需要实现类的原因,因为它们已经被Mybatis通过动态代理进行了封装。如果每个Mapper自带一个Implementation类,臃肿无用经过这次操作,展现在我们面前的是,项目中使用的Mybatis框架铺了这么久,终于要上主角了,为什么MybatisMapper接口不用实现类也能实现动态代理。想要严格按序介绍Mybatis动态不引用事先没有介绍过的术语,几乎不可能不引用代理过程。作者尽量说通俗易懂,没有实现类来完成动态代理的核心点。让我们拿本小书坐在黑板上。普通的动态代理是不是可以不实现类,只依赖接口来完成129521;🧱🧱输入代理呼叫处理器");return"success";}}根据代码可以看出我们没有实现接口Subject,继续看动态代理publicclassProxyTest{publicstaticvoidmain(String[]args){Subjectproxy=(Subject)Proxy.newProxyInstance(subject.getClass().getClassLoader(),newClass[]{Subject.class},newProxyInvocationHandler());proxy.sayHello();Processor:🧱🧱🧱Entertheproxytocalltheprocessor*/}}可以看到,对比文章开头的Demo,Proxy.newProxyInstance方法的参数为这里改在实现类之前获取实现接口的Class数组,这里是把接口本身放到Class数组中,方法是一样的。实现类接口生成的动态代理类和非实现类接口有什么区别?实现类接口用于InvocationHandler#invoke方法调用,invoke方法通过反射调用代理对象(SubjectImpl)方法(sayHello)。非实现类接口只调用InvocationHandler#invoke,所以实现类接口的返回值是代理对象接口的返回值,非实现类接口的返回值只有invoke方法的返回值.InvocationHandler#invoke方法的返回值是一个成功字符串。定义一个字符串变量,是否可以成功返回。现在第一个问题的答案已经浮出水面。Mapper没有实现类,调用JDBC等所有操作都在Mybatis中进行既然解决了InvocationHandler实现的问题,给人的感觉就是没那么难,但是你不好奇,底层是怎么实现的Mybatis层做的吗?先抛出一道题,然后带着题看源码,或许会让你对Double记忆深刻。我们Demo中的接口是固定的,但是MybatisMapper是不固定的。该怎么办?Mybatis是这么说的。看看它是如何在Mybatis底层实现动态接口代理的。小伙伴们只需要注意标记处的代码即可。它可以与我们的演示代码非常相似。核心点在于mapperInterface是如何赋值的。首先说一下Mybatis代理工厂中动态代理类的具体生成。具体逻辑是根据.xml中关联的命名空间,通过Class#forName反射返回Class。对象(.xml命名空间不止一种方式)将获取到的Class对象(实际上是接口对象)传递给Mybatis代理工厂生成代理对象,也就是刚刚揭开mapperInterface属性的玄机,Mybatis使用通过Class#forName对象生成Class的接口全限定名,这个Class对象类型就是接口。为了方便大家理解,以Mybatis源码提供的测试类为例。假设有一个现有的接口AutoConstructorMapper和对应的.xml。如下执行第一步,根据.xml命名空间获取Class对象。第一步是获取.xml上mapper标签的namespace属性,获取mapper接口的全限定信息。根据mapper的全限定信息获取Class对象,添加到对应的mapper容器中,等待生成动态代理对象。如果此时调用生成动态代理对象,代理工厂的newInstance方法如下:至此,文章开头提到的Proxy和Mybatis动态代理的相关问题已经全部回答完毕。抽象类能不能是JDK动态代理说的先结论后代码!publicabstractclassAbstractProxy{abstractvoidsayHello();}AbstractProxyproxyInterface=(AbstractProxy)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),newClass[]{AbstractProxy.class},newProxyInvocationHandler());代理接口。问好();毫无疑问,报错是不可避免的。JDK不能代理类。带着一点疑惑,我们来看一下Proxy源码的错误位置。JDK动态代理会在生成代理类的过程代码中进行接口验证。抽象类毕竟是类,加了抽象也做不出接口(就像我,虽然胖了60斤,但还是个帅哥)。本文描述了与JDK动态代理相关的问题。这里总结一下Q:JDK动态代理可以作为类的代理吗?因为JDK动态代理生成的代理类都会继承Proxy类。由于Java不能多继承,所以不能Proxybyclass问:抽象类可以被JDK动态代理吗?不,抽象类本质上是一个类。Proxy在生成代理类时,会检查传入的Class是否为接口。Q:MybatisMapper接口没有实现类,如何实现动态代理?Mybatis会通过Class#forname获取Mapper接口Class对象,并生成对应的动态代理对象。核心业务处理将在InvocationHandler#invoke用于处理。希望看过的朋友能够有所收获。如对文章内容有疑问,可通过留言或加作者好友进行交流。祝你好运!