原文链接:"犯罪心理"解读Mybatis拦截器

Mybatis拦截器执行过程解析 文章写过之后,我觉得 “Mybatis 拦截器案件”背后一定还隐藏着某种设计动机,里面大量的使用了 Java 动态代理手段,它是怎样应用这个手段优雅的设计出整个拦截事件的?就像抓到罪犯要了解它犯罪动机是什么一样,我们需要解读 Mybatis拦截器的设计理念:

设计解读

Java 动态代理我们都懂得,我们先用它设计一个基本拦截器
首先定义目标对象接口:

 public interface Target {public void execute();
}

然后,定义实现类实现其接口:

public class TargetImpl implements Target {public void execute() {System.out.println("Execute");}
}

最后,使用 JDK 动态代理定义一个代理类,用于为目标类生成代理对象:

public class TargetProxy implements InvocationHandler {private Object target;private TargetProxy(Object target) {this.target = target;}//代理对象生成目标对象public static Object bind(Object target) {return Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(),new TargetProxy(target));}//public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {System.out.println("Begin");return method.invoke(target, args);}
}

这时,客户端调用方式如下:

public class Client {public static void main(String[] args) {//没被代理之前Target target = new TargetImpl();target.execute(); //执行结果://Execute//被代理之后target = (Target)TargetProxy.bind(target);target.execute(); //执行结果://Begin//Execute}
}

应用上面的设计方式,拦截逻辑是写死在代理类中的:

public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {//拦截逻辑在代理对象中写死了,这样到这客户端没有灵活的设置来拦截其逻辑System.out.println("Begin");return method.invoke(target, args);
}

这样的设计方式不够灵活和高可用,可能满足 ClientA 的拦截需求,但是不能满足 ClientB 的拦截需求,这不是一个好的拦截方案,所以我们需要进一步更改设计方案:
将拦截逻辑封装成一个类,客户端绑定在调用TargetProxy()方法时将拦截逻辑一起作为参数,这样客户端可以灵活定义自己的拦截逻辑,为实现此功能,我们需要定一个拦截器接口 Interceptor

public interface Interceptor {public void intercept();
}

将代理类做一个小改动,在客户端实例化 TargetProxy 的时候可以传入自定义的拦截器:

public class TargetProxy implements InvocationHandler {private Object target;//拦截器private Interceptor interceptor;private TargetProxy(Object target, Interceptor interceptor) {this.target = target;this.interceptor = interceptor;}//通过传入客户端封装好 interceptor 的方式为 target 生成代理对象,使得客户端可以灵活使用不同的拦截器逻辑public static Object bind(Object target, Interceptor interceptor) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new TargetProxy(target, interceptor));}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//客户端实现自定义的拦截逻辑interceptor.intercept();return method.invoke(target, args);}
}

通过这样,就解决了“拦截内容固定死”的问题了,再来看客户端的调用方式:

//客户端可以在此处定义多种拦截逻辑
Interceptor interceptor = new Interceptor() {public void intercept() {System.out.println("Go Go Go!!!");}
};
target = (Target)TargetProxy.bind(target, interceptor);
target.execute();

上面的 interceptor() 是个无参方法,难道犯罪分子冒着生命危险拦截目标只为听目标说一句话 System.out.println(“Go Go Go!!!”)? 很显然它需要了解目标行为(Method)和注意目标的身外之物(方法参数),继续设置"圈套",将拦截接口做个改善:

public interface Interceptor {public void intercept(Method method, Object[] args);
}

同样需要改变代理类中拦截器的调用方式,将 method 和 args 作为参数传递进去

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//拦截器拿到method和args信息可以做更多事情,而不只是打招呼了interceptor.intercept(method, args);return method.invoke(target, args);
}

进行到这里,方案看似已经不错了,静待客户上钩,但这违背了做一名有追求罪犯的基本原则:「迪米特法则」

迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解, 不和陌生人说话。英文简写为: LoD,是一种解耦的方式.

上面代码中,method 需要知道 target 和 args;interceptor 需要知道 method 和 args,这样就可以在 interceptor 中调用 method.invoke,但是拦截器中并没有 invoke 方法需要的关键参数 target,所以我们将 target,method,args再进行一次封装成 Invocation类,这样拦截器只需要关注 Invocation 即可.

public class Invocation {private Object target;private Method method;private Object[] args;public Invocation(Object target, Method method, Object[] args) {this.target = target;this.method = method;this.args = args;}//成员变量尽可能在自己的内部操作,而不是 Intereptor 获取自己的成员变量来操作他们public Object proceed() throws InvocationTargetException, IllegalAccessException {return method.invoke(target, args);}public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public Method getMethod() {return method;}public void setMethod(Method method) {this.method = method;}public Object[] getArgs() {return args;}public void setArgs(Object[] args) {this.args = args;}
}

这样拦截器接口变了样子:

public interface Interceptor {public Object intercept(Invocation invocation)throws Throwable ;
}

代理类也随之做了改变:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return interceptor.intercept(new Invocation(target, method, args));
}

这样客户端调用,在拦截器中,拦截器写了自己拦截逻辑之后,执行 invocation.proceed() 即可触发原本 target 的方法执行:

Interceptor interceptor = new Interceptor() {public Object intercept(Invocation invocation)  throws Throwable {System.out.println("Go Go Go!!!");return invocation.proceed();}
};

到这里,我们经过一系列的调整和设计,结果已经很好了,但仔细想,这种拦截方式会拦截 target 的所有方法,假如 Target 接口有多个方法:

public interface Target {/*** 去银行存款*/public void execute1();/*** 倒垃圾*/public void execute2();
}

以上两个方法,当然是拦截 target 去银行存款才是利益价值最大化的拦截,拦截 target 去倒垃圾有什么用呢?(避免没必要的拦截开销),所以我们标记拦截器只有在发生去银行存款的行为时才采取行动,先自定义一个注解用来标记拦截器

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MethodName {public String value();
}

在拦截器实现类上添加该标识:

//去银行存款时拦截
@MethodName("execute1")
public class InterceptorImpl implements Interceptor {...
}

修改代理类,如果注解标记的方法是否与 method 的方法一致,则执行拦截器:

public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);if (ObjectUtils.isNull(methodName)){throw new NullPointerException("xxxx");}//如果方法名称和注解标记的方法名称相同,则拦截String name = methodName.value();if (name.equals(method.getName())){return interceptor.intercept(new Invocation(target,    method, args));}return method.invoke(this.target, args);
}

到这里,户端的调用变成了这个样子:

Target target = new TargetImpl();
Interceptor interceptor = new InterceptorImpl();
target = (Target)TargetProxy.bind(target, interceptor);
target.execute();

从上面可以看出,客户端第一步创建 target 对象和 interceptor 对象,通过传入 target 和 interceptor 调用 bind 方法生成代理对象,最终代理对象调用 execute 方法,根据迪米特法则,客户端不需要了解 TargetProxy,只需要关注拦截器的内部逻辑和可调用的方法即可,所以我们需要继续修改设计方案,添加 register(Object object)方法,:

public interface Interceptor {public Object intercept(Invocation invocation)  throws Throwable ;public Object register(Object target);
}

修改拦截器的实现,拦截器对象通过调用 register 方法为 target 生成代理对象:

@MethodName("execute1")
public class InterceptorImpl implements Interceptor {public Object intercept(Invocation invocation)throws Throwable {System.out.println("Go Go Go!!!");return invocation.proceed();}public Object register(Object target) {return TargetProxy.bind(target, this);}
}

现在,客户端调用变成了这个样子:

Target target = new TargetImpl();
Interceptor interceptor = new InterceptorImpl();target = (Target)interceptor.register(target);
target.execute1();

客户端只需要实例化拦截器对象,并调用拦截器相应的方法即可,非常清晰明朗
一系列的设计改变,恰巧符合 Mybatis拦截器的设计思想,我们只不过用一个非常简单的方式去理解它
Mybatis 将自定义的拦截器配置添加到 XML 文件中,或者通过注解的方式添加到上下文中,以 XML 形式举例:

 <plugins><plugin interceptor="com.gs.cvoud.dao.interceptor.MapInterceptor" /></plugins>

通过读取配置文件,将所有拦截器都添加到 InterceptorChain 中

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {// 该方法和我们上面自定义拦截器中 register 方法功能一致target = interceptor.plugin(target);}return target;}public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}

但 Mybatis 框架逻辑限制,只能为:ParameterHandler,ResultSetHandler,StatementHandler 和 Executor 创建代理对象
我们在此将我们的简单实现与 Mybatis 实现的核心内容做个对比:
生成代理对象:

拦截指定方法,如果找不到方法,抛出异常:

执行目标方法:

到这里,没错,犯罪现场完美推测出,真相就是这样!!!
墙裂建议先看 Mybatis拦截器执行过程解析 ,然后回看该文章,了解 Mybatis 拦截器的整个设计动机与理念,大道至简.

灵魂追问

  1. 除了迪米特设计原则,你还知道哪些设计基本原则?
  2. 你在编写代码时,考虑过怎样利用那些设计原则来规范自己代码吗?
  3. ...

推荐阅读

  1. 不得不知的责任链设计模式
  2. Mybatis拦截器执行过程解析
  3. 如何使用Mybatis的拦截器实现数据加密与解密
  4. 如何设计好的RESTful API
  5. 轻松高效玩转DTO(Data Transfer Object)

那些可以提高效率的工具

关注公众号了解更多可以提高工作效率的工具,同时带你像看侦探小说一样趣味学习 Java 技术


转载于:https://www.cnblogs.com/FraserYu/p/11095078.html

犯罪心理解读Mybatis拦截器相关推荐

  1. MySQL拦截器获取xml id_关于mybatis拦截器,有谁知道怎么对结果集进行拦截,将指定字段查询结果进行格式化...

    用MyBatis结果集拦截器做过这样一个需求: 由于项目需求经常变动,项目MySQL数据库都是存放JSON字符串,例如:用户的基本信息随着版本升级可能会有变动 数据表 CREATE TABLE `ac ...

  2. list mybatis 接收 类型_基于mybatis拦截器实现的一款简易影子表自动切换插件

    近期因工作需要,小编基于mybatis拦截器开发了一款简易影子表自动切换插件,可以根据配置实现动态修改表名,即将对原source table表的操作自动切换到对target table表的操作.该插件 ...

  3. 面试官:你能说说MyBatis拦截器原理吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Format cnblogs.com/fangjian042 ...

  4. MyBatis拦截器原理探究MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截 ...

  5. MyBatis拦截器有哪些以及分析

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中 ...

  6. Mybatis拦截器 mysql load data local 内存流处理

    Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...

  7. MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...

  8. insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成

    点击上方蓝字关注我们 1 01 前言 前阵子和朋友聊天,他说他们项目有个需求,要实现主键自动生成,不想每次新增的时候,都手动设置主键.于是我就问他,那你们数据库表设置主键自动递增不就得了.他的回答是他 ...

  9. Mybatis 拦截器介绍

    Mybatis 拦截器介绍 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 拦截器的一个作用就是我们 ...

最新文章

  1. 洞悉2020年数据团队建设,我们和清华、领英一起搞了个大事情,你也可以参与!...
  2. Mac NIFI 安装
  3. 设计模式——开发常用的设计模式梳理
  4. unity_小功能实现(敌人巡逻功能)
  5. django-模板过滤器
  6. c++用什么软件_html用什么软件编写
  7. vcneter5.5添加域用户权限
  8. 浏览器展示CSS伪类的动画和过渡效果应用
  9. centos7 kvm虚拟机安装黑屏
  10. OpenCV 与 Matlab 中最小二乘法拟合直线数据不一致的问题
  11. Session过期处理
  12. c语言皮尔森系数程序,【R语言编程】---根据表达量计算mRNA与lncRNA的皮尔森相关系数...
  13. Udacity CH2中.bag 文件解析
  14. 关于《网上购书系统》
  15. 为找房方便,将sohu房产的rss 转成全文的!
  16. IB学校书单合集请收藏好
  17. java将图片url转换成数据流输出到前端
  18. 索尼美能达50微-版本区别及实拍测评(sony/minolta)50 f2.8 macro
  19. 计算机网络学习笔记:基础知识
  20. linux挂载img镜像文件,如何挂载.img格式的镜像

热门文章

  1. (译)如何使用cocos2d来制作一个打地鼠的游戏:第一部分
  2. 微信小程序 倒计时实现
  3. scrapy爬虫框架
  4. C 语言获取系统时间
  5. python调用nacos账号密码,Python脚本,使用私钥(如果可用)或用户名密码
  6. 在线录音机 html5,recorder
  7. Python 懂车帝车友圈--分析与实现
  8. Python 机动车强制报废公告--参数分析与实现
  9. asp图片截图代码并且利用asp上传图片压缩
  10. Excel 下来公式 内容却一样