zoukankan      html  css  js  c++  java
  • 关于struts2的过滤器和mybatis的插件的分析

    网上一搜,发现一篇写的非常棒的博文,就直接复制过来了,供以后复习使用。

    前辈博文链接:共三篇:

    http://jimgreat.iteye.com/blog/1616671;

    http://jimgreat.iteye.com/blog/1594981;

    http://jimgreat.iteye.com/blog/1594982;

    以下为第一篇:

    其实无论是AOP、拦截器还是Plugin 都是通过对目标点,一般来说就是对函数的拦截,扩展原有的功能,增加切面逻辑(日志,权限验证),修改上下文运行数据(实现Mybatis物理分页)。

    Spring-AOP是个通用的框架,通过配置可以对任意函数进行拦截

    Struts2是Web框架,它的拦截器就只针对它的Action

    Mybatis的Plugin是针对它封装的JDBC各个环节进行拦截(http://www.mybatis.org/core/configuration.html#plugins)

    注:中文的拦截器、通知、插件指的都是拦截器

    实现原理上看,都是通过Java的动态代理机制,在运行时加载拦截器(按AOP的规范也叫通知器),对目标对象生成代理,在特定接口对应的函数调用时,实施拦截,调用拦截器的逻辑。

    Spring-AOP和Struts2都是将多个拦截器组织到数组中,在每个拦截方法调用时以责任链的形式,会有一个中央调度器,触发下一个拦截器。

    这里面,Struts2需要拦截器在实现时来组织调用逻辑,比如是在目标对象前还是后来执行拦截的逻辑。而Spring-AOP对不同的拦截器又进行了细分,有BeforeAdvice、AfterreturningAdvice、AroundAdvice在Spring中叫通知,会和Pointcut切点结合生成Advisor通知器,最后通过相应的适配器都会转成拦截器。

    Spring-AOP中的Pointcut可以看成是对拦截点过滤机制的一种抽象和对象化表示形式,也就是指定在哪些类和哪些方法上进行拦截。Struts2也有类似的机制,但过滤的只是Action中的方法。

    Mybatis也是责任链,动态代理,可过滤拦截点,和Spring-AOP、Struts2有个理念上的差别是,它在组织多个拦截器时使用的是层层代理,就是第一个插件代理目标实例 、第二个插件再生成第一个代理的代理、第三个插件再生成第二个代理的代理......    这个真是原生AOP呀!!! 

    好,下面一一分析。

    MyBatis

    我们先看一下这个层层代理是怎么生成的

    下面是一个 Mybatis Plugin 的简单例子

    1、函数上的注解是指定拦截方法的签名  [type,method,args] 

    2、Object intercept(Invocation invocation)  是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器或拦截的目标方法。

    3、Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target, this) 来完成的,把目标target和拦截器this传给了包装函数。

    Java代码  收藏代码
    1. // ExamplePlugin.java  
    2. @Intercepts({@Signature(  
    3.   type= Executor.class,  
    4.   method = "update",  
    5.   args = {MappedStatement.class,Object.class})})  
    6. public class ExamplePlugin implements Interceptor {  
    7.   public Object intercept(Invocation invocation) throws Throwable {  
    8.     return invocation.proceed();  
    9.   }  
    10.   public Object plugin(Object target) {  
    11.     return Plugin.wrap(target, this);  
    12.   }  
    13.   public void setProperties(Properties properties) {  
    14.   }  
    15. }  

    在下面的代码中可以看出   Plugin.wrap 从拦截器中取出拦截点方法签并生成对应的接口类,再通过Proxy生成代理对象。这个代理的InvocationHandler就是Plugin,里面封装了target, interceptor, signatureMap,并实现invoke方法,后面会分析。

    Java代码  收藏代码
    1. public static Object wrap(Object target, Interceptor interceptor) {  
    2.  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
    3.  Class<?> type = target.getClass();  
    4.  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
    5.  if (interfaces.length > 0) {  
    6.    return Proxy.newProxyInstance(  
    7.        type.getClassLoader(),  
    8.        interfaces,  
    9.        new Plugin(target, interceptor, signatureMap));  
    10.  }  
    11.  return target;  

    Mybatis的插件是针对它封装的处理类进行拦截的。这些处理类都是在org.apache.ibatis.session.Configuration中生成的,在下面这些生成函数中,都调用了 interceptorChain.pluginAll 对目标处理类附加拦截器。

    Java代码  收藏代码
    1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
    2.   ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);  
    3.  <strong> </strong>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 = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,  
    10.       rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
    11. <strong> </strong> resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
    12.   return resultSetHandler;  
    13. }  
    14.   
    15. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
    16.   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);  
    17. <strong>  </strong>statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);  
    18.   return statementHandler;  
    19. }   
    Java代码  收藏代码
    1. public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {  
    2.    ......  
    3.    executor = (Executor) interceptorChain.pluginAll(executor);  
    4.    return executor;  
    5.  }  

    我们看一下这个pluginAll做了什么:

    Java代码  收藏代码
    1. public Object pluginAll(Object target) {  
    2. <strong> </strong> for (Interceptor interceptor : interceptors) {  
    3.     target = interceptor.plugin(target);  
    4.   }  
    5.   return target;  
    6. }  

    遍历拦截器,调用拦截器的plugin,把拦截器附加到target上。第一次执行后,这个target就变成了原始处理类实例的代理,到最后这个target就变成被拦截器层层代理的代理实例了。

    就是这个for实现了前面说的层层代理 【第一个插件代理目标实例 、第二个插件再生成第一个代理的代理、第三个插件再生成第二个代理的代理......】

    下面说一下代理入口和责任链的推进

    每个代理的InvocationHandler都是org.apache.ibatis.plugin.Plugin类,它的invoke方法也是代理执行的入口

    Java代码  收藏代码
    1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    2.   try {  
    3.     Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
    4.     if (methods != null && methods.contains(method)) {  
    5.       return interceptor.intercept(new Invocation(target, method, args));  
    6.     }  
    7.     return method.invoke(target, args);  
    8.   } catch (Exception e) {  
    9.     throw ExceptionUtil.unwrapThrowable(e);  
    10.   }  
    11. }  

    在invoke里,如果方法签名和拦截中的签名一致,就调用拦截方法,并将下一个目标target(如果有多个拦截器,就是一下个代理)、拦截的method和arg 封装到Invocation中,传给下一个拦截器。

    invocation.proceed()就是简单调用下一个target的对应方法,如果一下个还是代理,就由回到上面的invoke方法了。

    这里就解释了上面说的 【Object intercept(Invocation invocation)  是实例拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器或拦截的目标方法。】

    Java代码  收藏代码
    1. public Object proceed() throws InvocationTargetException, IllegalAccessException {  
    2.   return method.invoke(target, args);  
    3. }  

      

    总结:

     

    我们假设在MyBatis配置了一个插件,在运行时会发生什么?

    1、所有可能被拦截的处理类都会生成一个代理

    2、处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

    3、执行插接中的拦截方法后,推进目标的执行

    如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑

    这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

    不过一般来说使用MyBatis也不会用很多插件,也可能是因为这个原因,它的拦截机制实现的不是很精细。如果实现情况中一定要有好多插件,我认为可以参照下面Struts2 和 Spring-AOP 的实现,将拦截器由中央调度器统一调度,这样只需一个代理(插件)来启动调度逻辑就行,每次都是调用中央调度器推进责任链的调度,也就是向前推进。

    以上。

    以简单的话来说:mybatis是按顺序,以target为核心,包含关系的产生一个又一个的代理对象;

    struts2是将所有的过滤器放在一个集合里,然后遍历集合,期间,每次都对请求做加工处理。

    毫无疑问,mybatis加载插件的方式效率更低。

  • 相关阅读:
    读书笔记之理想设计的特征
    一些javascript 变量声明的 疑惑
    LINQ 使用方法
    Google MySQL tool releases
    读书笔记之设计的层次
    EF之数据库连接问题The specified named connection is either not found in the configuration, not intended to be used with the Ent
    转载 什么是闭包
    javascript面向对象起步
    Tips
    数据结构在游戏中的应用
  • 原文地址:https://www.cnblogs.com/zqsky/p/6170905.html
Copyright © 2011-2022 走看看