转载

从 Spring 集成 MyBatis 到浅析 Java 动态代理

点击上方 蓝色字体 ,选择“设置星标”

优质文章,第一时间送达

从 Spring 集成 MyBatis 到浅析 Java 动态代理

小匠自己P的图怎么样?

前言

因为 MyBatis 的易上手性和可控性,使得它成为了 ORM 框架中的首选。近日新起了一个项目,所以重新搭建了一下 Spring-mybatis , 下面是搭建笔记和从 Spring-mybatis 源码分析其如何使用 Java动态代理 ,希望对大家有帮助。

Spring 集成 Mybatis

Spring 集成 Mybatis 的方式有很多种,大家耳熟能详的 xml 配置方式或者本文的采用的方式:

首先需要添加 MyBatis 的和 MyBatis-Spring 的依赖,本文使用的 Spring-mybatis 版本是1.3.1。在 mvnrepository 里面我们可以找到当前 Spring-mybatis 依赖的 springmybatis 版本,最好是选择匹配的版本以避免处理不必要的兼容性问题。因为 MyBatis-Spring 中对 mybatis 的依赖选择了 provided 模式,所以我们不得不额外添加 mybatis 依赖,依赖配置如下。

接下来会我们要创建工厂bean,放置下面的代码在 Spring 的 XML 配置文件中:

这个工厂需要一个 DataSource ,就是我们熟知的数据源了。这里我们选择了阿里的 Druid ,同样我们需要引入两个配置

添加 Spring 配置如下

接下来我们要编写数据库访问对象,大多数人会把它叫做 DAO 或者 Repository ,在这里其被称为

Mapper ,也是因为它的实现方式所决定。要注意的是所指定的映射器类必须是一个接口,而不是具体的实现类。这便因为 Mybatis 的内部实现使用的是 Java动态代理 ,而 Java动态代理 只支持接口,关于 动态代理 我们下文有更详细的描述。

接下来可以使用 MapperFactoryBean ,像下面这样来把接口加入到 Spring 中,这样就把 UserMapperSessionFactory 关联到一起了,原来使用 xml 配置的时候还需要Dao继承 SqlSessionDaoSupport 才能注入 SessionFactory ,这种方式直接通过 Java动态代理SqlSessionFactory 代理给了 UserMapper ,使得我们直接使用 UserMapper 即可。配置如下。

这样我们已经完成了90%,就差调用了,前提是你 Spring 环境是OK的。调用 MyBatis 数据方法现在只需一行代码:

那么问题又来了,每次写一个DAO都需要为其写一个 Bean 配置,那不是累死?于是我们又寻找另一种方案,代替手动声明 *MapperMapperScannerConfigurer 的出现解决了这个问题, 它会根据你配置的包路径自动的扫描类文件并自动将它们创建成 MapperFactoryBean ,可以在 Spring 的配置中添加如下代码:

basePackage 属性是让你为映射器接口文件设置基本的包路径。你可以使用分号或逗号作为分隔符设置多于一个的包路径。这个时候如果想自定义 sqlSessionFactory 可以添加如下配置:

这样以后还有一点点小瑕疵,如果我们数据的 column 名字是 _ 连接的,那么它不会那么聪明自动转换为驼峰的变量,所以我们需要对 SqlSessionFactoryBean 做如下配置,但是在1.3.0以后才可以通过xml配置,如果用早起版本的需要注意了。

至此关于 SpringMyBatis 的配置已经全部结束,后面我们会简单说下 SpringMyBatis 中的动态代理。

浅析 Java 动态代理

JDK 自带的动态代理需要了解InvocationHandler接口和Proxy类,他们都是在java.lang.reflect包下。

InvocationHandler 是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的 InvocationHandler 。对代理实例调用方法时,这个方法会调用 InvocationHandlerinvoke 方法。 Proxy 提供静态方法用于创建动态代理类和实例,同时后面自动生成的代理类都是 Proxy 对象。下面我们直接通过代码来分析 Java动态代理InvocationInterceptor 实现 InvocationHandler 接口,用于处理具体的代理逻辑。

UserUserImpl 是被代理对象的接口和类

DynamicProxyTest 是测试类,用于创建 InvocationInterceptorProxy 类以便测试。

输入结果如下:

很明显,我们通过proxyInstance这个代理类进行方法调用的时候,会在方法调用前后进行输出打印,这样就简单的实现了一个 Java动态代理 例子。动态代理不仅仅是打印输出这么简单,我们可以通过它打印日志,打开关闭事务, 权限检查了等等。当然它更是许多框架的钟爱,就如下文我们要说的 MyBatisJava动态代理 的实现。再多说一句 SpringAOP 也是使用动态代理实现的,当然它同时使用了 Java动态代理CGLib 两种方式。不过 CGLIB 不是本文要讨论的范围。

注意观察的同学看到上面代码的时候可能发现 invoke 方法的 proxy 参数并没有被使用,笔者查阅了一些相关文档也没有找到合理的说法,只能在源码中看看究竟喽,笔者当前的JDK版本是1.8。我们从入口开始, Proxy.newProxyInstance :

如上代码由此可见,它调用了 getProxyClass0 来获取 ProxyClass ,那我们继续往下看。

其实上面写的已经很简单了,如果存在就在 proxyClassCache 里面获取到,如果不存在就使用 ProxyClassFactory 创建一个。当然我们如果看一下 proxyClassCache 变量的话其也是 ProxyClassFactory 对象。

那么我们直接就去查看 ProxyClassFactory 的实现问题不就解决了吗?

由上代码便一目了然了,为什么我们 Debug 的时候 Proxy 对象是 $Proxy0 ,是因为他通过 $ProxyAtomicLong 拼起来的类名,其实这不是重点。重点是 ProxyGenerator.generateProxyClass(proxyName,interfaces,accessFlags) 。这就是生成 class 的地方,它把所有的条件组合好,生成 class 文件,然后再加载到内存里面以供使用。有兴趣的同学可以继续往深处查看。而我们需要做的是获取到他生成的字节码,看一下里面到底是什么?当 saveGeneratedFilestrue 的时候会保存 class 文件,所以我们在 DynamicProxyTestmain 函数添加一行即可:

通过 Debug 我们可以发现,它存储 class 文件的路径是 com/sun/proxy/$Proxy0.class ,所以直接在我们项目的目录下面就能找到它,然后通过 Idea 打开便得到如下代码:

这样好多问题就迎刃而解。

为什么 Java动态代理 必须是接口,因为生成的类要去实现这个接口。

invoke 方法的 proxy 是干嘛的,通过 super.h.invoke(this,m3,(Object[])null); 我们可以发现传递给 invoke 方法的就是 Proxy 本身。

同时 Proxy 类也通过反射实现了 toString , equals ,和 hashcode 等方法。

自此关于 Java动态代理 的讲解已经告段落,下面让我们简单看一下 Spring-mybatis 中关于 Java动态代理 的使用。

Java动态代理在Spring-mybatis中的实现

关于 Spring-mybatis 的实现我们得从 MapperScannerConfigurer 说起,首先 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口。而 BeanDefinitionRegistryPostProcessor 依赖于 Spring 框架,简单的说 BeanDefinitionRegistryPostProcessor 使得我们可以将 BeanDefinition 添加到 BeanDefinitionRegistry 中,而 BeanDefinition 描述了一个Bean实例所拥有的实例、结构参数和参数值,简单点说拥有它就可以实例化 Bean 了。 BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 方法在 Bean 被定义但还没被创建的时候执行,所以 Spring-mybatis 也是借助了这一点。需要想需要更深入的了解可以查看 Spring 的生命周期。

由上代码我们可以看到在 postProcessBeanDefinitionRegistry 里面得到 registry 然后使用 ClassPathMapperScanner 开始扫描包路径得到的 Bean 并且注册到 registry 里面。我们接着往里面看。

ClassPathMapperScanner 继承了 SpringClassPathBeanDefinitionScanner 所以调用父类的 doScan 方法就可以加载 Bean 然后再通过 processBeanDefinitions 方法加工成 MyBatis 需要的 Bean

如上代码循环了所有由 Spring 容器解析出来的 beanDefinitions 然后把他们的 BeanClass 修改为 mapperFactoryBean ,这就进入了行文的重点。我们翻看到 MapperFactoryBean :

其调用了 ConfigurationaddMapper 方法,这样就把 Bean 交给 MyBatis 管理了。那么 checkDaoConfig 是什么时候调用的呢?我们翻看其父类 DaoSupport 可以看到:

因为 DaoSupport 实现了 InitializingBean 并重写 afterPropertiesSet 方法,了解 Spring 生命周期的同学知道 afterPropertiesSet 方法会在资源加载完以后,初始化bean之前执行。我们继续查看 addMapper 方法。

addMapper 方法最终创建了 MapperProxyFactory 对象,在 MapperProxyFactory 里面我们两眼泪汪汪地发现了似曾相识的代码:

MapperProxy 实现了 InvocationHandler 方法,最终实现对 Bean 的代理,同时获取到上下文的 sqlSession 以供使用。具体生成过程我们不再累述,直接通过其源码结束本篇文章:

遗漏热文?赶紧标星

从 Spring 集成 MyBatis 到浅析 Java 动态代理

1.  阿里社招面试指南

2.  阿里应届生面试指南

3.  探寻线程池是如何工作的

4.  到底线程池应该设置多少合适?

5.  跳槽的必备条件是有一份好的简历

6.  不是所有的 Github 都适合写在简历上

7.  所没有项目经验找工作处处碰壁怎么办

8.  每一个开发人员都应该懂得的 UML 规范

9.  工作环境没机会接触高并发、分布式怎么办?

10.  这算是有史以来讲数据库连接池数最清楚的文章了

从 Spring 集成 MyBatis 到浅析 Java 动态代理

原文  http://mp.weixin.qq.com/s?__biz=MzIyNzc1ODQ0MQ==&mid=2247484164&idx=1&sn=71d4a868e0733869a480c9b5aab3b0bd
正文到此结束
Loading...