mybatis框架(七)——插件

来源:互联网 发布:简易财务记账软件 编辑:程序博客网 时间:2024/06/18 05:14

引言

            插件——用来解决特定功能需求的配置拦截器的一种方式,起到相当于插件的作用。

概述

            插件定义:在sqlSession四大对象调度的过程中,插入自定义代码执行特殊的功能满足特殊的需求。

内容

           1 接口:mybatis使用插件,必须实现接口Interceptor   

public interface Interceptor{Object intercept(Invocation invocation) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);}说明,方法定义1)intercept方法:直接覆盖拦截对象的原有方法,是插件的核心方法;参数Invocation对象,通过它反射调用原来对象的方法。2)plugin方法:target是被拦截对象,它的作用是给被拦截对象生成一个代理对象,并返回它3)setProperties方法:为plugin元素配置属性参数,插件初始化时调用,将插件对象存入到配置中,便于后面使用。

         2 初始化:MyBatis初始化开始就会对插件进行初始化

private void pluginElement(XNode parent) throw Exception{if(parent != null){for(XNode child : parent.getChildren()){String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();//通过反射生成插件实例Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();//设置配置参数interceptorInstance.setProperties(properties);//将插件实例保存到配置对象中configuration.addInterceptor(interceptorInstance);}}        }
         3 保存:插件保存在Configuration对象中

public void addInterceptor(Interceptor interceptor){interceptorChain.addInterceptor(interceptor);}interceptorChain是Configuration的属性对象,包含一个addInterceptor方法,初始化之后插件就保存在List对象interceptors里面,使用时取出来即可。private final List<Interceptor> interceptors = new ArrayList<Interceptor>();......public void addInterceptor(Interceptor interceptor){interceptors.add(interceptor);}
         4 常用工具类(MetaObject)

1)MetaObject forObject(Object object、ObjectFactory objectFactory、ObjectWrapperFactory objectWrapperFactory)方法用于包装对象。           本方法不再使用,通过MyBatis为我们提供的SystemMetaObject.forObject(Object obj)。2)Object getValue(String name)方法用于获取对象属性值,支持OGNL3)void setValue(String name、Object value)方法用于修改对象属性值,支持OGNL
        5 插件开发过程

              (1)确定需要拦截的签名:运行插件需要注册签名

                      1)根据功能确定需要拦截的对象

                           Executor:执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程,都可以拦截;

                           StatementHandler:执行SQL的过程,可以重写SQL执行过程,常用的拦截对象;

                           ParameterHandler:主要拦截执行SQL的参数组装,可以重写组装参数规则;

                           ResultSetHandler:用于拦截执行结果的组装,也可以重写组装结果的规则;

                           需要拦截的是StatementHandler对象,在预编译SQL之前,修改SQL使得结果返回数量被限制。

                      2)拦截方法和参数:查询的过程是通过Executor调度StatementHandler来完成的,StatementHandler的prepare方法预编译SQL,于是我们需要拦截的方法便是prepare方法,在这之前重写SQL语句。

                          StatementHandler接口的定义:

public interface StatementHandler{Statement prepare(Connection connection) throws SQLException;void parameterize(Statement statement) throws SQLException;void batch(Statement statement) throws SQLException;int update(Statement statement) throws SQLException;<E> List<E> query(Statement statement,ResultHandler resultHandler) throws SQLException;BoundSql getBoundSql();ParameterHandler getParameterHandler();}
                        以上的任何方法都可以拦截,从接口定义而言,prepare方法有一个参数Connection对象,那么如何设计一个拦截器?
         @Intercepts({ @Signature(type = StatementHandler.class,method = "prepare" , args={Connection.class}) })          public class MyPlugin implements Intercept{ ........ } 

                其中,@Intercepts说明它是一个拦截器。@Signature用来注册拦截器签名,只要签名满足条件才能拦截,type可以是四大对象中的一个,这里是StatementHandlermethod代表要拦截四大对象的某一种接口方法, 而args则表示该方法的参数,需要根据拦截对象的方法参数进行设置。

              (2)实现拦截方法:实现了简单的打印顺序功能

@Intercepts({@Signature(type = Executor.class,      //确定要拦截的对象method="update",            //确定要拦截的方法args = {MappedStatement.class,Object.class}  //拦截方法的参数)})public class MyPlugin implements Intercept{Properties props = null;/** * 代替拦截对象方法的内容 * @param invocation 责任链对象 */@Overridepublic Object intercept(Invocation invocation) throws Throwable{System.err.println("before....");//如果当前代理的是一个非代理对象,那么它就会调用真实拦截对象的方法,如果不是它会调用下个插件代理对象的invoke方法Object obj = invocation.proceed();System.err.println("after......");return obj;}/** * 生成对象的代理,这里常用MyBatis提供的Plugin类的wrap方法 * @param target 被代理的对象 */@Overridepublic Object plugin(Object target){//使用MyBatis提供的Plugin类生成代理对象System.err.println("使用生成代理对象....");return Plugin.wrap(target,this);}/** * 获取插件配置的属性,我们在MyBatis的配置文件里面去配置 * @param props 是MyBatis配置的参数 */public void setProperties(Properties props){System.err.println(props.get("dbType"));this.props = props;}}
              (3)配置插件

<plugins><plugin interceptor = "xxx.MyPlugin"><property name="dbType" value="mysql" /></plugin></plugins>
              (4)插件实例

                      1)实例场景:大型的互联网系统,假如我们数据库使用的MySQL数据库,想要对数据库查询返回数据量需要限制,以避免数据量过大造成网站瓶颈,配置限制50条数据。

                      2)实现步骤:首先我们先确定需要拦截四大对象中的哪一个,根据功能需要修改SQL的执行。由SqlSession运行原理表明拦截对象是StatementHandler,因为它的prepare方法用来编译SQL语句,我们可以在预编译前修改语句来满足我们的需求。通过StatementHandler的prepare()方法,在它预编译前,需要重写SQL,达到限制数据量的要求,它有一个参数(Connection connection),我们就轻易地得到了签名注解。

                     3)代码实现:                            

@Intercepts({@Signature(type = Executor.class,      //确定要拦截的对象method="update",            //确定要拦截的方法args = {MappedStatement.class,Object.class}  //拦截方法的参数)})public class MyPlugin implements Intercept{Properties props = null;/** * 代替拦截对象方法的内容 * @param invocation 责任链对象 */@Overridepublic Object intercept(Invocation invocation) throws Throwable{System.err.println("before....");//如果当前代理的是一个非代理对象,那么它就会调用真实拦截对象的方法,如果不是它会调用下个插件代理对象的invoke方法Object obj = invocation.proceed();System.err.println("after......");return obj;}/** * 生成对象的代理,这里常用MyBatis提供的Plugin类的wrap方法 * @param target 被代理的对象 */@Overridepublic Object plugin(Object target){//使用MyBatis提供的Plugin类生成代理对象System.err.println("使用生成代理对象....");return Plugin.wrap(target,this);}/** * 获取插件配置的属性,我们在MyBatis的配置文件里面去配置 * @param props 是MyBatis配置的参数 */public void setProperties(Properties props){System.err.println(props.get("dbType"));this.props = props;}}<plugins><plugin interceptor = "xxx.MyPlugin"><property name="dbType" value="mysql" /></plugin></plugins>@Intercepts({ @Signature(type = StatementHandler.class, //确定要拦截的对象method = "prepare",  //确定要拦截的方法args = { Connection.class}) //拦截方法的参数})public class QueryLimitPlugin implements Interceptor{//默认限制查询返回行数private int limit;private String dbType;//限制表中间别名,避免表重名起得比较特殊private static final String LMT_TABLE_NAME = "limit_Table_Name_person";@Overridepublic Object intercept(Invocation invocation) throws Throwable{//取出被拦截对象StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();MetaObject metaStmtHandler = SystemMetaObject.forObject(stmtHandler);// 分离代理对象,从而形成多次代理,通过俩次循环最原始的被代理类,MyBatis使用的是JDK代理while (metaStmtHandler.hasGetter("h")){Object object = metaStmtHandler.getValue("h");metaStmtHandler = SystemMetaObject.forObject(object);}// 分离最后一个代理对象的目标类while (metaStmtHandler.hasGetter("target")){Object object = metaStmtHandler.getValue("target");metaStmtHandler = SystemMetaObject.forObject(object);}// 取出即将要执行的SQLString sql = (String)metaStmtHandler.getValue("delegate.boundSql.sql");String limitSql;//判断参数是不是MySQL数据库且SQL有没有被插件重写过if ("mysql".equals(this.dbType) && sql.indexOf(LMT_TABLE_NAME) == -1){//去掉前后空格sql = sql.trim();//将参数写入SQLlimitSql = "select * from (" + sql +") " + LMT_TABLE_NAME + " limit " + limit;//重写要执行的SQLmetaStmtHandler.setValue("delegate.boundSql.sql", limitSql);}//调用原来对象的方法,进入责任链的下一层级return invocation.proceed();}@Overridepublic Object plugin(Object target){//使用默认的MyBatis提供的类生成代理对象return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties props){String strLimit = (String)props.getProperty("limit", "50");this.limit = Integer.parseInt(strLimit);//读取设置的数据库类型this.dbType = (String) props.getProperty("dbType", "mysql");}}                说明:在setProperties方法中可以读入配置给插件的参数,“limit”是数据库的名称,“50”是限制记录数;                      在MyBatis初始化的时候就已经被设置好,需要的时候直接使用即可;                      在plugin方法里,使用MyBatis提供的类target来生成代理对象,插件进入plugin的invoke方法;                  最后使用到拦截器的intercept方法;                      插件QueryLimitPlugin的intercept方法就会覆盖掉StatementHandler的prepare方法,先从代理对象                  分离出真实对象,然后根据需要修改SQL,来达到限制返回行数的目的;                      再然后使用invocation.proceed()来调度真实StatementHandler的prepare方法完成SQL预编译;                      最后需要在MyBatis配置文件里才能运行这个插件<plugins><plugin interceptor="com.learn.chapter7.plugin.QueryLimitPlugin"><property name="dbType" value="mysql"/><property name="limit" value="50"/></plugin></plugins>

 总结

          (1)插件修改MyBatis的底层设计,尽量少用插件;

        (2)插件生成的原理是层层代理对象的责任链模式,通过反射方法运行,性能不高,减少插件就能减少代理,提高系统性能;

        (3)编写插件需要了解MyBatis的运行原理,了解四大对象及其方法的作用,准备判断需要拦截什么对象,什么方法,参数是什么,才能确定如何编写签名;

        (4)插件需要读取和修改MyBatis映射器中的对象属性

        (5)多个插件层层代理,保证逻辑的正确性

        (6)尽量少改动MyBatis底层,减少错误


            

0 0