Mybatis拦截器定义

说到拦截器,举个通俗的例子就能理解了:

        如果一个人要买票,可以直接自己买票,也可以通过中间代理买票,此时,可以把中间代理比做成一个拦截器,中间代理接受了顾客的委托,然后到火车站买票,买完票要将火车票交给顾客,由顾客拿着火车票去乘车,这是一个常规的流程。

       在上述例子中,顾客自己买票属于Mybatis的正常流程;顾客请中间代理买火车票则属于利用拦截器在Mabatis中添加了自己的行为,但是同样达到了目的。

通过上述的例子,我们可以知道,一个拦截器的基本特点,或要做的事情:

--------------------------------------------------------

1.  要实现拦截

2.  拦截下来做什么事情

3.  事情完成后要交回主权。

---------------------------------------------------------

为什么使用要用拦截器,使用拦截器有什么作用呢?

        在项目中,比如,各种不同类型的信息展示都需要分页功能等,如果对每类信息的展示都加入分页机制的代码,这样代码的重复率就会很高,功能的模块化下降,耦合性变高,所以,我们把这部分共同使用的功能用拦截器包装起来实现,然后规定此类功能的拦截器拦截哪些需要拦截的对象,那么,在执行这些对象的操作的时候,就可以自动实现拦截,加载此部分拦截内容执行,然后继续执行拦截的行为。当然,不局限于信息列表的分页功能,还有其他的公用功能也可以将其抽离出来,放在拦截器里面实现。

       然后我们要知道拦截器拦截什么样的对象,拦截对象的什么行为,什么时候拦截?

       在Mybatis框架中,已经给我们提供了拦截器接口,防止我们改动源码来添加行为实现拦截。说到拦截器,不得不提一下,拦截器是通过动态代理对Mybatis加入一些自己的行为。

 

拦截对象

       确立拦截对象范围:要拦对人,既要保证拦对人,又要保证对正确的人执行正确的拦截动作,不要乱掏别人东西,拿需要的正确的东西。

拦截地点

       在Mybatis的源码中 

拦截时机

如果mybatis为我们提供了拦截的功能,我们应该在Mybatis执行过程的哪一步拦截更加合适呢?

拦截某个对象干某件事的是偶,拦截的时机要对,不能过早的拦截,也不能拦截太晚,过早的拦截会耽误别人做自己的工作,拦截太晚会错失良机。

    Mybatis实际也是一个JDBC执行的过程,只不过被包装起来了而已,我们要拦截Mybatis的执行,最迟也要在获取PreparedStatement时拦截:

                   PreparedStatementstatement=conn.prepareStatement(sql.toString());

在此处偷偷的将sql语句换掉,就可以改变mybatis的执行,加入自己想要的执行行为。

       而获取Mybatis的Statement是在StatementHandler中进行的。

举个栗子来说明:

一个分页拦截器的例子:

**************拦截器定义代码******************

@Intercepts(
{@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class })}
)
public class PageInterceptor implements Interceptor{
    private String test;
 
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandlerstatementHandler =(StatementHandler)invocation.getTarget();
        MetaObjectmetaObject = MetaObject.forObject(statementHandler, 
SystemMetaObject.DEFAULT_OBJECT_FACTORY, 
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
//拦截的是RoutingStatemHandler,(RoutingStatemHandler下有一个BaseStatementHandler接口)
//delegate是第一层属性的属性名,mappedStatement:第二层属性的属性名
        MappedStatementmappedStatement =(MappedStatement)metaObject.getValue("delegate.mappedStatement");
        Stringid=mappedStatement.getId();
        //以ByPage结尾的做拦截。$是字符串结束符
        if(id.matches(".+ByPage$"))
        {
            BoundSqlboundSql=statementHandler.getBoundSql();
            //原始的Sql语句
            Stringsql=boundSql.getSql();
            //查询总条数的Sql语句
            StringcountSql = "select count(*) from (" + sql + ")a";
            Connectionconnection = (Connection)invocation.getArgs()[0];
            PreparedStatementcountStatement = connection.prepareStatement(countSql);
            ParameterHandlerparameterHandler =(ParameterHandler)metaObject.getValue("delegate.parameterHandler");
            parameterHandler.setParameters(countStatement);
            ResultSetrs = countStatement.executeQuery();
           
            //将泛型修改成?,则不会出现警告了。
            Map parameter = (Map)boundSql.getParameterObject();
            Pagepage = (Page)parameter.get("page");
            if(rs.next()) {
                page.setTotalNumber(rs.getInt(1));
            }
            //改造后的Sql语句
            //delete.boundSql.sql是三层关系:RoutingStatemHandler下的delegate
            StringpageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
            metaObject.setValue("delegate.boundSql.sql", pageSql);
        }
        //通过源码下的一个反射来调用被拦截的方法,让原本被拦截的方法继续执行
        return invocation.proceed();
    }
   
    //target被代理人
    /*
     * 达成协议,把具有代理权的要给普通的业务员this传进wrap实现的源码去做代理。
     *
     */
    public Object plugin(Object target) {
        System.out.println(this.test);
        return Plugin.wrap(target, this);   }
 
    public void setProperties(Properties proterties) {
        this.test=proterties.getProperty("test");
       
    }
}
 
**************拦截器配置******************

    
     
       
     

     定义一个拦截器,首先要在Mybatis中声明这是一个拦截器,要拦截的地点,在什么时候开始拦截,然后要在拦截器中做一些什么工作(属性设置,拦截过滤,拦截执行),拦截完让被拦截的对象继续执行原来要执行的动作。

 

先看拦截器声明:

@Intercepts(
{
     @Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class })}

)

      通过定义一个拦截器Interceptor的注解注解声明这是一个拦截器,用signature签名指向要拦截StatementHandler这个接口类下的prepare方法,新版本的Mybatis需要在该方法中带入两个参数:Connnection和Integer两个类型。

     在源码中,Parpare方法是通过BaseStatementHandler(Implementation实现)下的instantiateStatement方法获取,instantiateStatement是一个虚拟方法,是通过PreparedStatementHandler(Implementation)类来实现,该类下的instantiateStatement方法实现了Statement的获取。

Statement获取的实现语句是:return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

     上述一个注解声明即完成了注解的声明(@Intercepts)、拦截时机(获取Statement之前),拦截对象(对Statement进行拦截,或者说是对里面的sql语句进行拦截)、拦截的方法(prepare),需要带入的参数(Connection和Integer两个参数)。

再说拦截器:

public class PageInterceptor implements Interceptor{
    private String test;
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandlerstatementHandler =(StatementHandler)invocation.getTarget();
        MetaObjectmetaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
        //delegate是第一层属性的属性名,mappedStatement:第二层属性的属性名
        MappedStatementmappedStatement =(MappedStatement)metaObject.getValue("delegate.mappedStatement");
        Stringid=mappedStatement.getId();
        //以ByPage结尾的做拦截。$是字符串结束符
        if(id.matches(".+ByPage$"))
        {
            BoundSqlboundSql=statementHandler.getBoundSql();
            //原始的Sql语句
            Stringsql=boundSql.getSql();
            //查询总条数的Sql语句
            StringcountSql = "select count(*) from (" + sql + ")a";
            Connectionconnection = (Connection)invocation.getArgs()[0];
            PreparedStatementcountStatement = connection.prepareStatement(countSql);
            ParameterHandlerparameterHandler =(ParameterHandler)metaObject.getValue("delegate.parameterHandler");
            parameterHandler.setParameters(countStatement);
            ResultSetrs = countStatement.executeQuery();           
            //将泛型修改成?,则不会出现警告了。
            Map parameter = (Map)boundSql.getParameterObject();
            Pagepage = (Page)parameter.get("page");
            if(rs.next()) {
                page.setTotalNumber(rs.getInt(1));
            }
            //改造后的Sql语句
            //delete.boundSql.sql是三层关系:RoutingStatemHandler下的delegate
            StringpageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
            metaObject.setValue("delegate.boundSql.sql", pageSql);
        }
        //通过源码下的一个反射来调用被拦截的方法,让原本被拦截的方法继续执行
        return invocation.proceed();
    }
    public Object plugin(Object target) {
        System.out.println(this.test);
        return Plugin.wrap(target, this);   } 
    public void setProperties(Properties proterties) {
        this.test=proterties.getProperty("test");      
    }
}

          拦截器通过实现Mybatis提供的Interceptor拦截接口,重写了三个方法:setProperties/plugin/ intercept,三者执行顺序是setProperties—》plugin—》Interceptor。

         setProperties方法:该方法通过设置属性,将核心配置文件configuration.xml文件中对拦截器的配置项下的属性获取过来,便于在拦截器中使用。

         plugin方法:该方法用来协商,达成协议,把代理权给普通的业务员this,传进wrap方法实现的源码去做代理,没有获取代理权的代理人在这个地方就会停下,不会向下走了,获取代理权的代理人可以去做拦截代理。

        intercept方法:则是获取拦截对象下的要拦截的东西,然后对其加以改编,添加自己的行为,按照条件进行改编拦截对象,然后通过源码下的反射invocation来调用被拦截的方法,让原本被拦截的方法继续执行(invocation.proceed())。

Dao.xml文件:


  
  
  
  ID,COMMAND,DESCRIPTION,CONTENT 
其中,parameterType="java.util.Map"是参数类型的权限命名(包名.类名),很容易通过该名称反射出类名(Class.forName());当Mybatis拿到这个类名,后面就很容易操作了。

如果是数字类型就比较麻烦了,如何填写属性值表示数组类型呢?

即小问题:Mybatis是如何加载数组的Class的。

你可能感兴趣的:(【Mybatis】)