转自:http://zhangbo-peipei-163-com.iteye.com/blog/2033832?utm_source=tuicool&utm_medium=referral

我只是做个笔记,大家如果翻到这里了,最好还是去上面的链接中看原文吧。

最近在用mybatis做项目,需要用到mybatis的拦截器功能,就顺便把mybatis的拦截器源码大致的看了一遍,为了温故而知新,在此就按照自己的理解由浅入深的理解一下它的设计。 
和大家分享一下,不足和谬误之处欢迎交流。直接入正题。 
首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做。拦截器的实现都是基于代理的设计模式设计的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并拦截执行拦截器代码。 
那么我们就用JDK的动态代理设计一个简单的拦截器: 

将被拦截的目标接口:

  1. public interface Target {
  2. public void execute();
  3. }

目标接口的一个实现类:

  1. public class TargetImpl implements Target {
  2. public void execute() {
  3. System.out.println("Execute");
  4. }
  5. }

利用JDK的动态代理实现拦截器:

  1. public class TargetProxy implements InvocationHandler {
  2. private Object target;
  3. private TargetProxy(Object target) {
  4. this.target = target;
  5. }
  6. //生成一个目标对象的代理对象
  7. public static Object bind(Object target) {
  8. return Proxy.newProxyInstance(target.getClass() .getClassLoader(),
  9. target.getClass().getInterfaces(),
  10. new TargetProxy(target));
  11. }
  12. //在执行目标对象方法前加上自己的拦截逻辑
  13. public Object invoke(Object proxy, Method method,
  14. Object[] args) throws Throwable {
  15. System.out.println("Begin");
  16. return method.invoke(target, args);
  17. }
  18. }

客户端调用:

  1. public class Client {
  2. public static void main(String[] args) {
  3. //没有被拦截之前
  4. Target target = new TargetImpl();
  5. target.execute(); //Execute
  6. //拦截后
  7. target = (Target)TargetProxy.bind(target);
  8. target.execute();
  9. //Begin
  10. //Execute
  11. }

上面的设计有几个非常明显的不足,首先说第一个,拦截逻辑被写死在代理对象中:

  1. public Object invoke(Object proxy, Method method,
  2. Object[] args) throws Throwable {
  3. //拦截逻辑被写死在代理对象中,导致客户端无法灵活的设置自己的拦截逻辑
  4. System.out.println("Begin");
  5. return method.invoke(target, args);
  6. }

我们可以将拦截逻辑封装到一个类中,客户端在调用TargetProxy的bind()方法的时候将拦截逻辑一起当成参数传入: 
定义一个拦截逻辑封装的接口Interceptor,这才是真正的拦截器接口。

  1. public interface Interceptor {
  2. public void intercept();
  3. }

那么我们的代理类就可以改成:

  1. public class TargetProxy implements InvocationHandler {
  2. private Object target;
  3. private Interceptor interceptor;
  4. private TargetProxy(Object target, Interceptor interceptor) {
  5. this.target = target;
  6. this.interceptor = interceptor;
  7. }
  8. //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。
  9. public static Object bind(Object target, Interceptor interceptor) {
  10. return Proxy.newProxyInstance(target.getClass().getClassLoader(),
  11. target.getClass().getInterfaces(),
  12. new TargetProxy(target, interceptor));
  13. }
  14. public Object invoke(Object proxy, Method method,
  15. Object[] args) throws Throwable {
  16. //执行客户端定义的拦截逻辑
  17. interceptor.intercept();
  18. return method.invoke(target, args);
  19. }

客户端调用代码:

  1. //客户端可以定义各种拦截逻辑
  2. Interceptor interceptor = new Interceptor() {
  3. public void intercept() {
  4. System.out.println("Go Go Go!!!");
  5. }
  6. };
  7. target = (Target)TargetProxy.bind(target, interceptor);
  8. target.execute();

当然,很多时候我们的拦截器中需要判断当前方法需不需要拦截,或者获取当前被拦截的方法参数等。我们可以将被拦截的目标方法对象,参数信息传给拦截器。 
拦截器接口改成:

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

在代理类执行的时候可以将当前方法和参数传给拦截,即TargetProxy的invoke方法改为:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. interceptor.intercept(method, args);
  3. return method.invoke(target, args);
  4. }

在Java设计原则中有一个叫做迪米特法则,大概的意思就是一个类对其他类知道得越少越好。其实就是减少类与类之间的耦合强度。这是从类成员的角度去思考的。 
什么叫越少越好,什么是最少?最少就是不知道。 
所以我们是不是可以这么理解,一个类所要了解的类应该越少越好呢? 
当然,这只是从类的角度去诠释了迪米特法则。 
甚至可以反过来思考,一个类被其他类了解得越少越好。 
A类只让B类了解总要强于A类让B,C,D类都去了解。 

举个例子: 
我们的TargetProxy类中需要了解的类有哪些呢? 
1. Object target 不需要了解,因为在TargetProxy中,target都被作为参数传给了别的类使用,自己不需要了解它。 
2. Interceptor interceptor 需要了解,需要调用其intercept方法。 
3. 同样,Proxy需要了解。 
4. Method method 参数需要了解,需要调用其invoke方法。 
同样,如果interceptor接口中需要使用intercept方法传过去Method类,那么也需要了解它。那么既然Interceptor都需要使用Method,还不如将Method的执行也放到Interceptor中,不再让TargetProxy类对其了解。Method的执行需要target对象,所以也需要将target对象给Interceptor。将Method,target和args封装到一个对象Invocation中,将Invocation传给Interceptor。 
Invocation:

  1. public class Invocation {
  2. private Object target;
  3. private Method method;
  4. private Object[] args;
  5. public Invocation(Object target, Method method, Object[] args) {
  6. this.target = target;
  7. this.method = method;
  8. this.args = args;
  9. }
  10. //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们,
  11. //除非这样的操作需要Interceptor的其他支持。然而这儿不需要。
  12. public Object proceed() throws InvocationTargetException, IllegalAccessException {
  13. return method.invoke(target, args);
  14. }
  15. public Object getTarget() {
  16. return target;
  17. }
  18. public void setTarget(Object target) {
  19. this.target = target;
  20. }
  21. public Method getMethod() {
  22. return method;
  23. }
  24. public void setMethod(Method method) {
  25. this.method = method;
  26. }
  27. public Object[] getArgs() {
  28. return args;
  29. }
  30. public void setArgs(Object[] args) {
  31. this.args = args;
  32. }
  33. }

Interceptor就变成:

  1. public interface Interceptor {
  2. public Object intercept(Invocation invocation)throws Throwable ;
  3. }

TargetProxy的invoke方法就变成:

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

那么就每一个Interceptor拦截器实现都需要最后执行Invocation的proceed方法并返回。 
客户端调用:

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

好了,通过一系列调整,设计已经挺好了,不过上面的拦截器还是有一个很大的不足, 
那就是拦截器会拦截目标对象的所有方法,然而这往往是不需要的,我们经常需要拦截器 
拦截目标对象的指定方法。 
假设目标对象接口有多个方法:

  1. public interface Target {
  2. public void execute1();
  3. public void execute2();
  4. }

利用在Interceptor上加注解解决。 
首先简单的定义一个注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. public @interface MethodName {
  4. public String value();
  5. }

在拦截器的实现类加上该注解:

  1. @MethodName("execute1")
  2. public class InterceptorImpl implements Interceptor {...}

在TargetProxy中判断interceptor的注解,看是否实行拦截:

  1. public Object invoke(Object proxy, Method method,
  2. Object[] args) throws Throwable {
  3. MethodName methodName =
  4. this.interceptor.getClass().getAnnotation(MethodName.class);
  5. if (ObjectUtils.isNull(methodName))
  6. throw new NullPointerException("xxxx");
  7. //如果注解上的方法名和该方法名一样,才拦截
  8. String name = methodName.value();
  9. if (name.equals(method.getName()))
  10. return interceptor.intercept(new Invocation(target,    method, args));
  11. return method.invoke(this.target, args);
  12. }

最后客户端调用:

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

从客户端调用代码可以看出,客户端首先需要创建一个目标对象和拦截器,然后将拦截器和目标对象绑定并获取代理对象,最后执行代理对象的execute()方法。 
根据迪米特法则来讲,其实客户端根本不需要了解TargetProxy类。将绑定逻辑放到拦截器内部,客户端只需要和拦截器打交道就可以了。 
即拦截器接口变为:

  1. public interface Interceptor {
  2. public Object intercept(Invocation invocation)  throws Throwable ;
  3. public Object register(Object target);

拦截器实现:

  1. @MethodName("execute1")
  2. public class InterceptorImpl implements Interceptor {
  3. public Object intercept(Invocation invocation)throws Throwable {
  4. System.out.println("Go Go Go!!!");
  5. return invocation.proceed();
  6. }
  7. public Object register(Object target) {
  8. return TargetProxy.bind(target, this);
  9. }
  10. }

客户端调用:

  1. Target target = new TargetImpl();
  2. Interceptor interceptor = new InterceptorImpl();
  3. target = (Target)interceptor.register(target);
  4. target.execute1();

OK,上面的一系列过程其实都是mybatis的拦截器代码结构,我只是学习了之后用最简单的方法理解一遍罢了。 
上面的TargetProxy其实就是mybatis的Plug类。Interceptor和Invocation几乎一样。只是mybatis的Interceptor支持的注解 
更加复杂。 
mybatis最终是通过将自定义的Interceptor配置到xml文件中:

  1. <!-- 自定义处理Map返回结果的拦截器 -->
  2. <plugins>
  3. <plugin interceptor="com.gs.cvoud.dao.interceptor.MapInterceptor" />
  4. </plugins>

通过读取配置文件中的Interceptor,通过反射构造其实例,将所有的Interceptor保存到InterceptorChain中。

  1. public class InterceptorChain {
  2. private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  3. public Object pluginAll(Object target) {
  4. for (Interceptor interceptor : interceptors) {
  5. target = interceptor.plugin(target);
  6. }
  7. return target;
  8. }
  9. public void addInterceptor(Interceptor interceptor) {
  10. interceptors.add(interceptor);
  11. }
  12. public List<Interceptor> getInterceptors() {
  13. return Collections.unmodifiableList(interceptors);
  14. }
  15. }

mybatis的拦截器只能代理指定的四个类:ParameterHandler、ResultSetHandler、StatementHandler以及Executor。 
这是在mybatis的Configuration中写死的,例如(其他三个类似):

  1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  2. ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  3. //将配置文件中读取的所有的Interceptor都注册到ParameterHandler中,最后通过每个Interceptor的注解判断是否需要拦截该ParameterHandler的某个方法。
  4. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  5. return parameterHandler;
  6. }

所以我们可以自定义mybatis的插件(拦截器)修改mybatis的很多默认行为, 
例如, 
通过拦截ResultSetHandler修改接口返回类型; 
通过拦截StatementHandler修改mybatis框架的分页机制; 
通过拦截Executor查看mybatis的sql执行过程等等。

【转载】由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理的更多相关文章

  1. Mybatis使用动态代理实现拦截器功能

    1.背景介绍 拦截器顾名思义为拦截某个功能的一个武器,在众多框架中均有“拦截器”.这个Plugin有什么用呢?或者说拦截器有什么用呢?可以想想拦截器是怎么实现的.Plugin用到了Java中很重要的一 ...

  2. java 动态代理—— Mybaties 拦截器链基本原理实现

    1.摘要 Mybaties 中有个分页插件,之前有特意的去了解了一下原理 :https://www.cnblogs.com/jonrain0625/p/11168247.html,从了解中得知分页插件 ...

  3. 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)

    代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...

  4. (十二)mybatis之动态代理

    mybatis之动态代理的应用 在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我们知道了,Mybatis的使用需要用到Mapper映射文件, ...

  5. Mybatis mapper动态代理的原理详解

    在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出my ...

  6. MyBatis学习(三)MyBatis基于动态代理方式的增删改查

    1.前言 上一期讲到MyBatis-Statement版本的增删改查.可以发现.这种代码写下来冗余的地方特别多.写一套没啥.如果涉及到多表多查询的时候就容易出现问题.故.官方推荐了一种方法.即MyBa ...

  7. 十八、泛型 l 注解 l Servlet3.0 l 动态代理 l 类加载器基础加强

    l 泛型 l 注解 l Servlet3.0 l 动态代理 l 类加载器 泛型 1 回顾泛型类 泛型类:具有一个或多个泛型变量的类被称之为泛型类. public class A<T> { ...

  8. mybatis由浅入深day01_5.3 Mapper动态代理方法

    5.3 Mapper动态代理方法(程序员只需要写mapper接口(相当于dao接口)) 5.3.1 实现原理(mapper代理开发规范) 程序员还需要编写mapper.xml映射文件 程序员编写map ...

  9. mybatis源码分析(5)-----拦截器的实现原理(动态代理+责任链)

    写在前面 MyBatsi 的拦截器模式是基于代理的代理模式.并且myBatis 的插件开发也是以拦截器的形式集成到myBatis 当中. MyBatis 的拦截器已经插件是在org.apache.ib ...

随机推荐

  1. JavaWeb 后端 <十二> 之 过滤器 filter 乱码、不缓存、脏话、标记、自动登录、全站压缩过滤器

    一.过滤器是什么?有什么? 1.过滤器属于Servlet规范,从2.3版本就开始有了. 2.过滤器就是对访问的内容进行筛选(拦截).利用过滤器对请求和响应进行过滤

  2. 基于Windows服务的聊天程序(一)

    本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网.采用TCP协议,单一服务器连接模式为一对多:多台服务器的情况下,当客户端连接数超过预设值时可自动进行负 ...

  3. 修改tomcat编码方式

    打开server.xml <Connector URIEncoding="UTF-8" connectionTimeout="20000" port=&q ...

  4. JavaScript DOM编程艺术读后感(1)—— 平稳退化

    最近,在读<JavaScript DOM编程艺术(第二版)>这本书,想着将自己的读后感记录下来,作为记忆吧. 其实我并不是最近才刚开始读这本书的,我读了有一段时间了.我是一名web前端开发 ...

  5. Ant Design UI组件

    Ant Design 是面向中台的 UI 设计语言.  http://ant.design/

  6. 暑假学习计划:Day_1.JSP&Servlet&Tocat 环境搭建到基础的认识。

    1.了解JSP和Servlet(百度了解即可). 2.了解B/S和C/S.分别是  浏览器/服务器  和  客户端/服务器. 其中 B/S 被称为瘦模式(主流模式). 3.了解并下载Tomcat服务器 ...

  7. 准备 overlay 网络实验环境 - 每天5分钟玩转 Docker 容器技术(49)

    为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络.VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 ...

  8. 实例甜点 Unreal Engine 4迷你教程(1)之如何用C++将纹理绘制在UserWidget的Image小部件上

    完成本迷你教程之前,请前往完成以下迷你教程: 无前置教程待完成. 本教程适合的人群: 初学者,具有开发经验两周: 本示例的目的:为了在代码中实现UMG中的这个功能: 说明:这是一些列迷你教程的首篇,所 ...

  9. CentOS下安装Nginx服务器

    一.nginx安装环境 nginx是C语言开发,建议在linux上运行,本教程使用Centos7作为安装环境. 1.1  gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果 ...

  10. Spring中使用Map、Set、List、数组、属性集合的注入方法配置文件

    (1)下边的一个Java类包含了所有Map.Set.List.数组.属性集合等这些容器,主要用于演示spring的注入配置: package com.lc.collection; import jav ...