0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Mybatis框架和插件将动态代理玩出了新境界

麻辣软硬件 来源:YXQ 2019-07-10 17:43 次阅读

静态代理

又是一年毕业季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式。

ISubject:被访问者资源的抽象SubjectImpl:被访问者具体实现类(房东)SubjectProxy:被访问者的代理实现类(中介)

UML图如下

举个例子来理解一下这个设计模式老板让记录一下用户服务的响应时间,用代理模式来实现这个功能。

public interface IUserService { public void request();}

public class UserServiceImpl implements IUserService { @Override public void request() { System.out.println(“this is userService”); }}

public class UserServiceProxy implements IUserService { private IUserService userService; public UserServiceProxy(IUserService userService) { this.userService = userService; } @Override public void request() { long startTime = System.currentTimeMillis(); userService.request(); System.out.println(“reques cost :” + (System.currentTimeMillis() - startTime)); } public static void main(String[] args) { IUserService userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(userService); // this is userService // reques cost :0 userServiceProxy.request(); }}

一切看起来都非常的美好,老板又发话了,把产品服务的响应时间也记录一下吧。又得写如下3个类

IProductService ProductServiceImpl ProductServiceProxy

UserServiceProxy和ProductServiceProxy这两个代理类的逻辑都差不多,却还得写2次。其实这个还好,如果老板说,把现有系统的几十个服务的响应时间都记录一下吧,你是不是要疯了?这得写多少代理类啊?

动态代理

黑暗总是暂时的,终究会迎来黎明,在JDK1.3之后引入了一种称之为动态代理(Dynamic Proxy)的机制。使用该机制,我们可以为指定的接口在系统运行期间动态地生成代理对象,从而帮助我们走出最初使用静态代理实现AOP的窘境

动态代理的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

让我们用动态代理来改造一下上面记录系统响应时间的功能。虽然要为IUserService和IProductService两种服务提供代理对象,但因为代理对象中要添加的横切逻辑是一样的。所以我们只需要实现一个InvocationHandler就可以了。代码如下

public class RequestCostInvocationHandler implements InvocationHandler { private Object target; public RequestCostInvocationHandler(Object target) { this.target = target; } /** 被代理对象的任何方法被执行时,都会先进入这个方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals(“request”)) { long startTime = System.currentTimeMillis(); // 执行目标对象的方法 method.invoke(target, args); System.out.println(“reques cost :” + (System.currentTimeMillis() - startTime)); } return null; } public static void main(String[] args) { // 3个参数解释如下 // classloader,生成代理类 // 代理类应该实现的接口 // 实现InvocationHandler的切面类 IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, new RequestCostInvocationHandler(new UserServiceImpl())); IProductService productService = (IProductService) Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, new RequestCostInvocationHandler(new ProductServiceImpl())); // this is userService // reques cost :0 userService.request(); // this is productService // reques cost :0 productService.request(); }}

UML图如下。恭喜你,你现在已经理解了Spring AOP是怎么回事了,就是这么简单,今天先不展开谈Spring

先简单谈谈动态代理在Mybatis中是如何被大佬玩的出神入化的

Mybatis核心设计思路

相信用过mybatis的小伙伴都能理解下面这段代码,通过roleMapper这个接口直接从数据库中拿到一个对象

Role role = roleMapper.getRole(3L);

直觉告诉我,一个接口是不能运行的啊,一定有接口的实现类,可是这个实现类我自己没写啊,难道mybatis帮我们生成了?你猜的没错,mybatis利用动态代理帮我们生成了接口的实现类,这个类就是org.apache.ibatis.binding.MapperProxy,我先画一下UML图,MapperProxy就是下图中的SubjectProxy类

和上面的UML类图对比一下,发现不就少了一个SubjectImpl类吗?那应该就是SubjectProxy类把SubjectImple类要做的事情做了呗,猜对了。SubjectProxy通过SubjectImple和SubjectImple.xml之间的映射关系知道自己应该执行什么SQL。所以mybatis最核心的思路就是这么个意思,细节之类的可以看源码,理清最主要的思路,看源码就能把握住重点。

Mybatis插件原理

mybatis的插件也用到了动态代理,还用到了责任链模式,我就不从源码角度分析了。说一下大概实现,我们用插件肯定是为了在原先的基础上增加新功能。增加一个插件,mybatis就在原先类的基础上用动态代理生成一个代理对象,如果有多个插件,就在代理对象的基础上再生成代理对象,形式和如下函数差不多

plugin2( plugin1( start() ) )

我再给你写个例子,你再看看相关的源码分析文章(也许我以后会写),很快就能理解了。

在mybatis中要想用插件,有如下2个步骤1.在mybatis-config.xml中配置插件,如下所示

《plugins》 《plugin interceptor=“org.xrq.mybatis.plugin.FirstInterceptor” /》 《plugin interceptor=“org.xrq.mybatis.plugin.SecondInterceptor” /》《/plugins》

2.插件类还得实现Interceptor接口

我现在给一个需求,一个应用返回字符串0,我加一个插件在字符串的左右两边加plugin1,再加一个插件在字符串的左右两边加plugin2,开写

返回字符串的接口

public interface IGetStr { public String getStrZero(); public String getStrOne();}

返回字符串的实现类

public class GetStrImpl implements IGetStr { @Override public String getStrZero() { return “0”; } @Override public String getStrOne() { return “1”; }}

定义拦截器接口

public interface Interceptor { /** 执行拦截逻辑的方法 */ Object intercept(Invocation invocation); /** * target是被拦截的对象,它的作用是给被拦截对象生成一个代理对象,并返回它。 * 为了方便,可以直接使用Mybatis中org.apache.ibatis.plugin类的wrap方法生成代理对象 * 我这里也写了一个Plugin方法 */ Object plugin(Object target);}

看到一个不认识的类Invocation,定义如下

public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); }}

就是简单的封装了一下目标对象,目标方法和目标方法的参数。proceed方法就是执行目标对象的目标方法

Plugin算是一个工具类,生成代理对象

public class Plugin implements InvocationHandler { /** 目标对象 */ private final Object target; /** Interceptor对象 */ private final Interceptor interceptor; public Plugin(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } /** 生成代理对象 */ public static Object wrap(Object target, Interceptor interceptor) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[]{IGetStr.class}, new Plugin(target, interceptor)); } /** 被代理对象的方法执行时,这个方法会被执行 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 只为方法getStrZero生成代理对象 if (method.getName().equals(“getStrZero”)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); }}

写第一个插件

public class FirstInterceptor implements Interceptor { /** 执行拦截逻辑的方法 */ @Override public Object intercept(Invocation invocation) { try { return “plugin1 ” + invocation.proceed() + “ plugin1”; } catch (Exception e) { return null; } } /** 为原先的类生成代理对象 */ @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }}

有2个方法

plugin是为插件生成代理对象,用了我自己写的Plugin工具类intercept是增加拦截逻辑,invocation.proceed()是执行目标对象的目标方法,前文说过了哈,这里我们只对输出做了改变

第二个插件和第一个插件类似

public class SecondInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) { try { return “plugin2 ” + invocation.proceed() + “ plugin2”; } catch (Exception e) { return null; } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }}

用一个容器保存插件,这里用到了责任链模式

public class InterceptorChain { /** 放拦截器 */ private final List《Interceptor》 interceptors = new ArrayList《Interceptor》(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }}

pluginAll方法是精髓,为每个插件一层一层的生成代理对象,就像套娃娃一样。

验证一下

public class Main { public static void main(String[] args) { // 配置插件 InterceptorChain interceptorChain = new InterceptorChain(); interceptorChain.addInterceptor(new FirstInterceptor()); interceptorChain.addInterceptor(new SecondInterceptor()); // 获得代理对象 IGetStr getStr = new GetStrImpl(); getStr = (IGetStr) interceptorChain.pluginAll(getStr); String result = getStr.getStrZero(); // plugin2 plugin1 0 plugin1 plugin2 System.out.println(result); result = getStr.getStrOne(); // 1 System.out.println(result); }}

大功告成,可以看到先定义的插件先执行。

类有点多,如果看的有点晕,多看几次,你就很容易理解了,我这里还是精简了很多。

一个InvocationHandler接口被大佬玩出了新境界,果然编程这件事还得靠想象力。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程
    +关注

    关注

    88

    文章

    3615

    浏览量

    93710

原文标题:Mybatis框架和插件将动态代理玩出了新境界

文章出处:【微信号:VOSDeveloper,微信公众号:麻辣软硬件】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    JDK动态代理的原理

    在Java中,动态代理是一种机制,允许在运行时动态地创建代理对象来代替某个实际对象,从而在其前后执行额外的逻辑。 为什么JDK动态
    的头像 发表于 09-30 10:51 579次阅读

    数据库整合Mybatis框架

    微服务 SpringBoot 20(九):整合Mybatis
    发表于 07-16 11:03

    请问mybatis分页插件有哪些使用案列?

    mybatis分页插件使用案例
    发表于 11-09 06:21

    java动态代理分析

    定义:为其他对象提供一种代理以控制对这个对象的访问。 动态代理使用 java动态代理机制以巧妙的方式实现了
    发表于 09-27 15:14 0次下载

    mybatis是什么_MyBatis的优缺点详解_mybatis框架入门详解

    Mybatis框架是别人开发的一种半成品软件,可以用来通过定制辅助快速开发是工具。MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在
    发表于 02-24 09:16 2w次阅读

    mybatis动态sql详解

    本文详细介绍了mybatis执行动态sql语句的方法。
    发表于 02-24 11:37 3836次阅读

    easy-mybatis Mybatis的增强框架

    ./oschina_soft/gitee-easy-mybatis.zip
    发表于 06-14 09:45 1次下载
    easy-<b class='flag-5'>mybatis</b> <b class='flag-5'>Mybatis</b>的增强<b class='flag-5'>框架</b>

    Fluent Mybatis、原生MybatisMybatis Plus对比

    mapper中再组装参数。那对比原生Mybatis, Mybatis Plus或者其他框架,FluentMybatis提供了哪些便利呢?
    的头像 发表于 09-15 15:41 1433次阅读

    一文掌握MyBatis动态SQL使用与原理

    摘要:使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
    的头像 发表于 01-06 11:27 987次阅读

    MyBatis-Plus为什么不支持联表

    MyBatis Plus Join`一款专门解决MyBatis Plus 关联查询问题的扩展框架,他并不一款全新的框架,而是基于`MyBatis
    的头像 发表于 02-28 15:19 2466次阅读
    <b class='flag-5'>MyBatis</b>-Plus为什么不支持联表

    Rust构建QEMU插件框架

    Cannonball 是一个用 Rust 构建 QEMU 插件框架!您可以在 C 语言的 QEMU TCG 插件中执行的任何操作,都可以使用cannonball。编写以最小的开销和尽可能多的功能运行的
    的头像 发表于 07-21 16:57 899次阅读

    MyBatis动态sql是什么?MyBatis动态SQL最全教程

    动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中,开发人员通常需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。
    的头像 发表于 08-10 10:18 956次阅读

    mybatis接口动态代理原理

    MyBatis是一款轻量级的Java持久化框架,它通过XML或注解配置的方式,数据库操作与SQL语句解耦,提供了一种简单、灵活的数据访问方式。在MyBatis中,使用
    的头像 发表于 12-03 11:52 942次阅读

    mybatis和mybatisplus的区别

    MyBatisMyBatis Plus是两个非常受欢迎的Java持久层框架。这两个框架在设计和功能上有一些区别,下面我详细介绍它们之间的
    的头像 发表于 12-03 11:53 2553次阅读

    mybatis框架的主要作用

    MyBatis框架是一种流行的Java持久化框架,主要用于简化数据库操作和管理。它提供了一种简洁的方式来访问数据库,并将SQL语句从Java代码中分离出来,从而提高了代码的可维护性和可读性
    的头像 发表于 12-03 14:49 2029次阅读