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加载插件的方式效率更低。

  • 相关阅读:
    PDO 数据访问抽象层
    递归例子
    Windows对象操作:浏览器窗口信息
    练习题:选择器和选择好友
    搭建wamp环境,数据库基础知识
    堆表空间管理
    vc弹出USB的方法. 附试验通过的代码!
    javascript 调用C++函数
    [转]在Win7 64位注册ActiveX控件
    SetForegroundWindow的正确用法
  • 原文地址:https://www.cnblogs.com/zqsky/p/6170905.html
Copyright © 2011-2022 走看看