• Mybatis之拦截器原理(jdk动态代理优化版本)


    在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化

    其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方的非面向接口的cglib.

    我们现在说的是jdk的动态代理,因为mybatis拦截器也是基于这个实现的。

    简单介绍就是建立一个目标类的代理类。在执行目标类的方法前先执行代理类的方法,目标类的方法是在代理类中执行的,所以目标类方法执行前后,可以再代理类中进行其他操作。

    简单版:

    1 public interface Target {
    2     void work();
    3 }
    1 public class TargetImpl implements Target {
    2     @Override
    3     public void work() {
    4         System.out.println("我就只能做这么多了");
    5     }
    6 }

    下面创建一个代理类

     1 public class TargetProxy implements InvocationHandler {
     2 
     3     private Object target;
     4 
     5     public TargetProxy(Object target){
     6         this.target = target;
     7     }
     8 
     9     /**
    10      *   缺点   代理需要做的事情不是很灵活。直接在这里面写死了。
    11      * @param proxy
    12      * @param method
    13      * @param args
    14      * @return
    15      * @throws Throwable
    16      */
    17     @Override
    18     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    19         System.out.println("代理在前面做事情了");
    20         try {
    21             return method.invoke(target, args);
    22         } catch (InvocationTargetException e) {
    23             throw e.getCause();
    24         }
    25     }
    26 
    27     public static Object getProxyObject(Object target){
    28         return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TargetProxy(target));
    29     }
    30 }
     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         Target target = new TargetImpl();
     5         target.work();
     6         System.out.println("-----------------------------");
     7         Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl());
     8         target1.work();
     9     }
    10 }

    结果:

    我就只能做这么多了
    -----------------------------
    代理在前面做事情了
    我就只能做这么多了

    ———————————————————————————————————————————————————————————————————————————————

    这样是最常见的代理了,但是有个缺点,代理类要做的事情在代理类写死了,要换就得多写一个代理类。那下面我们就把代理的事项单独拿出来。

    增加拦截器接口和实现类

    1 public interface Interceptor {
    2     void doOtherThings();
    3 }
    1 public class InterceptorImpl implements  Interceptor {
    2     @Override
    3     public void doOtherThings() {
    4         System.out.println("还可以灵活地做其他事情");
    5     }
    6 }

    代理类变一下:

     1 public class TargetProxy implements InvocationHandler {
     2 
     3     private Object target;
     4 
     5     private Interceptor interceptor;
     6 
     7     public TargetProxy(Object target, Interceptor interceptor) {
     8         this.interceptor = interceptor;
     9         this.target = target;
    10     }
    11 
    12     @Override
    13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    14         interceptor.doOtherThings();
    15         return method.invoke(target, args);
    16     }
    17 
    18     /**
    19      * 获取代理对象的时候顺便把拦截逻辑对象也传过来
    20      *
    21      * @param interceptor
    22      * @return
    23      * @paramarget
    24      */
    25     public static Object getProxyObject(Object target, Interceptor interceptor) {
    26         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
    27     }
    28 
    29 
    30 }
     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         Target target = new TargetImpl();
     5         target.work();
     6         System.out.println("-----------------------------");
     7         Interceptor interceptor = new InterceptorImpl();
     8         Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor);
     9         target1.work();
    10         System.out.println("-----------------------------");
    11         Interceptor interceptor1 = new Interceptor() {
    12             @Override
    13             public void doOtherThings() {
    14                 System.out.println("换个拦截方式?");
    15             }
    16         };
    17         Target target2 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor1);
    18         target2.work();
    19     }
    20 }

    结果:

    我就只能做这么多了
    -----------------------------
    还可以灵活地做其他事情
    我就只能做这么多了
    -----------------------------
    换个拦截方式?
    我就只能做这么多了

    ———————————————————————————————————————————————————————————————————————————————

    这样写是不是感觉挺好了。但是你是不是要拦截所有方法的呢?正常场景是不是只需要拦截method中某个某几个方法呢?那这样就把拦截器加个参数,method好了。

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

    注意:java设计模式中有一个规则就迪米特法则,也叫最少知识法则,意思应该就是一个类知道的越少越好,对一个对象知道的越少越好。

    那这样看反正拦截器都需要method还不如让代理类也不知道method好了,method执行需要代理类的target那就把target也传过去。

    传了这么多参数,我们可以不可以把这些参数封装成一个对象传过去,暂时就叫Invocation

    然后method.invoke执行就需要这三个参数,那么这三个操作就放在Invocation里面好了,要不然你还得让拦截器去获取这些属性

     1 public class Invocation {
     2 
     3     private Object target;
     4 
     5     private Method method;
     6 
     7     private Object[] args;
     8 
     9     public Invocation(Object target, Method method, Object[] args) {
    10         this.target = target;
    11         this.method = method;
    12         this.args = args;
    13     }
    14 
    15     public Object proceed() throws InvocationTargetException, IllegalAccessException {
    16         return method.invoke(target,args);
    17     }
    18 
    19     public Object getTarget() {
    20         return target;
    21     }
    22 
    23     public void setTarget(Object target) {
    24         this.target = target;
    25     }
    26 
    27     public Method getMethod() {
    28         return method;
    29     }
    30 
    31     public void setMethod(Method method) {
    32         this.method = method;
    33     }
    34 
    35     public Object[] getArgs() {
    36         return args;
    37     }
    38 
    39     public void setArgs(Object[] args) {
    40         this.args = args;
    41     }
    42 }

    下面看拦截器怎么实现

    1 public interface Interceptor {
    2 
    3     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
    4 }
     1 public class InterceptorImpl implements Interceptor {
     2     @Override
     3     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
     4         if(invocation.getMethod().getName().equals("work")){
     5             System.out.println("真的假的");
     6             return invocation.proceed();
     7         }else{
     8             return null;
     9         }
    10 
    11     }
     1 public class TargetProxyTwo implements InvocationHandler {
     2 
     3     private Object target;
     4 
     5     private Interceptor interceptor;
     6 
     7     public TargetProxyTwo(Object target, Interceptor interceptor) {
     8         this.target = target;
     9         this.interceptor = interceptor;
    10     }
    11 
    12     @Override
    13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    14         return interceptor.intercept(new Invocation(target,method,args));
    15     }
    16 
    17     public static Object getProxyObj(Object target,Interceptor interceptor){
    18         return Proxy.newProxyInstance(target.getClass().getClassLoader(),
    19                 target.getClass().getInterfaces(),
    20                 new TargetProxyTwo(target, interceptor));
    21     }
    22 }
    1 public class Client {
    2 
    3     public static void main(String[] args) {
    4         Target target = (Target) TargetProxyTwo.getProxyObj(new TargetImpl(),new InterceptorImpl());
    5         target.work();
    6     }
    7 }

    结果:

    真的假的
    我就只能做这么多了

    ———————————————————————————————————————————————————————————————————————————————

    迪米特法则来看,客户端现在需要知道 拦截器,和代理类。 那么能不能把代理类的注册放到拦截器里面呢?可以的。来看下

    1 public interface Interceptor {
    2 
    3     public Object register(Object target);
    4 
    5     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
    6 }
     1 public class InterceptorImpl implements Interceptor {
     2 
     3 
     4     @Override
     5     public Object register(Object target) {
     6         return TargetProxyTwo.getProxyObj(target,this);
     7     }
     8 
     9     @Override
    10     public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
    11         if(invocation.getMethod().getName().equals("work")){
    12             System.out.println("真的假的");
    13             return invocation.proceed();
    14         }else{
    15             return invocation.procedd();
    16         }
    17 
    18     }
    19 }
    1 public class Client {
    2 
    3     public static void main(String[] args) {
    4 
    5         Target target = (Target) new InterceptorImpl().register(new TargetImpl());
    6         target.work();
    7     }
    8 }

    这样是不是很完美?

    这样写是有问题的

    1         if(invocation.getMethod().getName().equals("work")){
    2             System.out.println("真的假的");
    3             return invocation.proceed();
    4         }else{
    5             return invocation.proceed();
    6         }

    把判断方法的逻辑方法拦截方法里面,那么假如十个方法,十个拦截逻辑呢?你是不是要写大一堆if else?这样是美观的。怎么解决呢?注解啊!!!

    在拦截器上添加要拦截的方法注解不就好了嘛。

    来看代码:

    1 @Retention(RetentionPolicy.RUNTIME)
    2 @java.lang.annotation.Target(ElementType.TYPE)
    3 public @interface MethodName {
    4     public String value();
    5 }
    1 @MethodName("work")
    2 public class InterceptorImpl implements Interceptor
     1 public class TargetProxy implements InvocationHandler {
     2 
     3     private Object target;
     4 
     5     private Interceptor interceptor;
     6 
     7     public TargetProxy(Object target, Interceptor interceptor) {
     8         this.interceptor = interceptor;
     9         this.target = target;
    10     }
    11 
    12     @Override
    13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    14         MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);
    15         if (methodName == null) {
    16             throw new NullPointerException("拦截器注解方法名字为空");
    17         }
    18         String name = methodName.value();
    19         if (name.equals(method.getName())) {
    20             return interceptor.intercept(new Invocation(target, method, args));
    21         }
    22         return method.invoke(target, args);
    23     }
    24 
    25     /**
    26      * 获取代理对象的时候顺便把拦截逻辑对象也传过来
    27      *
    28      * @param interceptor
    29      * @return
    30      * @paramarget
    31      */
    32     public static Object getProxyObject(Object target, Interceptor interceptor) {
    33         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
    34     }
    35 }

    怎么样,这样是不是就很完美了。

    先预告下: 上面的类对应Mybatis的类:

    Invocation,Interceptor完全一样。TargetProxy对应Plugin类。regist方法对应wrap方法

    好的下面来看Mybatis的拦截器了

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    2. ParameterHandler (getParameterObject, setParameters)
    3. ResultSetHandler (handleResultSets, handleOutputParameters)
    4. StatementHandler (prepare, parameterize, batch, update, query)

    Mybatis的拦截器的使用方式是通过xml配置的

    <plugins>
        <plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
    </plugins>

    通过这样的配置,就有了默认的拦截器:

    那么这四种拦截器分别在什么时候添加的?

    XMLConfigBuilder

     1   private void pluginElement(XNode parent) throws Exception {
     2     if (parent != null) {
     3       for (XNode child : parent.getChildren()) {
     4         String interceptor = child.getStringAttribute("interceptor");
     5         Properties properties = child.getChildrenAsProperties();
     6         Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
     7         interceptorInstance.setProperties(properties);
     8         configuration.addInterceptor(interceptorInstance);
     9       }
    10     }
    11   }

    Mybatis中有一个拦截器链,典型的责任链模式

    那么这四种拦截器分别在什么时候开始执行拦截呢?

    先介绍下责任链获取恰当的拦截器的方法,Configuration类中

     1   public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
     2     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
     3     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
     4     return parameterHandler;
     5   }
     6 
     7   public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
     8       ResultHandler resultHandler, BoundSql boundSql) {
     9     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    10     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    11     return resultSetHandler;
    12   }
    13 
    14   public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    15     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    16     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    17     return statementHandler;
    18   }
    19 
    20   public Executor newExecutor(Transaction transaction) {
    21     return newExecutor(transaction, defaultExecutorType);
    22   }
    23 
    24   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    25     executorType = executorType == null ? defaultExecutorType : executorType;
    26     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    27     Executor executor;
    28     if (ExecutorType.BATCH == executorType) {
    29       executor = new BatchExecutor(this, transaction);
    30     } else if (ExecutorType.REUSE == executorType) {
    31       executor = new ReuseExecutor(this, transaction);
    32     } else {
    33       executor = new SimpleExecutor(this, transaction);
    34     }
    35     if (cacheEnabled) {
    36       executor = new CachingExecutor(executor);
    37     }
    38     executor = (Executor) interceptorChain.pluginAll(executor);
    39     return executor;
    40   }
     1 public class InterceptorChain {
     2 
     3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
     4 
     5   public Object pluginAll(Object target) {
    //遍历所有的拦截器看那个拦截器适用。plugin方法就是每个拦截器的适配方法
    6 for (Interceptor interceptor : interceptors) { 7 target = interceptor.plugin(target); 8 } 9 return target; 10 } 11 12 public void addInterceptor(Interceptor interceptor) { 13 interceptors.add(interceptor); 14 } 15 16 public List<Interceptor> getInterceptors() { 17 return Collections.unmodifiableList(interceptors); 18 } 19 20 }

    下面开始看那块开始拦截的

    1.Executor接口的拦截器:

      获取SqlSession的时候,就获取了代理拦截器:

      类:DefaultSqlSessionFactory

     1   private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
     2     Transaction tx = null;
     3     try {
     4       final Environment environment = configuration.getEnvironment();
     5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     7       final Executor executor = configuration.newExecutor(tx, execType);
     8       return new DefaultSqlSession(configuration, executor, autoCommit);
     9     } catch (Exception e) {
    10       closeTransaction(tx); // may have fetched a connection so lets call close()
    11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    12     } finally {
    13       ErrorContext.instance().reset();
    14     }
    15   }

    2 StatementHandler

    处理SQL逻辑等方法

    具体类:SimpleExecutor

     1   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.<E>query(stmt, resultHandler);
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11   }

    3.parameterHandler;resultSetHandler

    一个参数的一个结果集的。

    都在statementHandler确定后在类:BaseStatementHandler构造器中初始化的

     1   protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
     2     this.configuration = mappedStatement.getConfiguration();
     3     this.executor = executor;
     4     this.mappedStatement = mappedStatement;
     5     this.rowBounds = rowBounds;
     6 
     7     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
     8     this.objectFactory = configuration.getObjectFactory();
     9 
    10     if (boundSql == null) { // issue #435, get the key before calculating the statement
    11       generateKeys(parameterObject);
    12       boundSql = mappedStatement.getBoundSql(parameterObject);
    13     }
    14 
    15     this.boundSql = boundSql;
    16 
    17     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    18     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    19   }

    这下总体应该了解了,这四类接口都是在获取实现类之前,先通过代理获取对象。如果存在拦截器,则执行拦截器的方法,否则直接返回对象本身。

    Mybatis好的地方在于他把四类拦截器用一个拦截器链管理了起来。 用责任链模式解决了要单独判断哪类拦截逻辑用什么拦截器的判断逻辑。

  • 相关阅读:
    springboot +mybatis 使用PageHelper实现分页,并带条件模糊查询
    jQuery设置点击选中样式,onmouseover和onmouseout事件
    Ajax跨域设置
    Java获取文章的上一篇/下一篇
    Python str / bytes / unicode 区别详解
    Python bytes 和 string 相互转换
    Python bytearray/bytes/string区别
    Python eval 与 exec 函数区别
    Python eval 与 exec 函数
    Python set list dict tuple 区别和相互转换
  • 原文地址:https://www.cnblogs.com/haoerlv/p/9946535.html
Copyright © 2020-2023  润新知