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)方法用于修改对象属性值,支持OGNL5 插件开发过程
(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可以是四大对象中的一个,这里是StatementHandler。method代表要拦截四大对象的某一种接口方法, 而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底层,减少错误
- mybatis框架(七)——插件
- MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码
- 【Mybatis】深入浅出Mybatis(七)——别名使用
- 深入理解MyBatis(七)—MyBatis事务
- 【Mybatis】深入浅出Mybatis(二)——Mybatis的框架
- 实用插件(七)视频播放插件——ckplayer
- 深入理解MyBatis(五)—MyBatis的插件机制
- HTML学习(七)——框架
- Mybatis学习总结(七).Mybatis插件之分页插件
- MyBatis 学习总结(七)——模糊查询
- springboot系列教程(七)——整合mybatis
- 【MyBatis框架点滴】——MyBatis二级缓存
- Mybatis步步进阶(七)——Mybatis实体关联映射
- MyBatis学习总结(七)——Mybatis缓存
- MyBatis学习总结(七)——Mybatis缓存
- MyBatis学习总结(七)——Mybatis缓存
- mybatis(七)——mybatis的一二级缓存
- MyBatis学习总结(七)——Mybatis缓存
- TCP与UDP的区别
- mybatis框架(六)——核心技术与原理
- Volley框架的学习
- 算法训练 Anagrams问题
- Spring AOP编程
- mybatis框架(七)——插件
- 【小e1开发板操作全过程】最全的小e板操作流程
- 顺序表的静态和动态实现
- 操作系统与我们编程的关系(抽象和封装的运用)
- Hibernate的简单配置和使用
- JAVA Map的四种遍历比较
- Mac的Sierra安装TensorFlow
- 算法训练 出现次数最多的整数
- Memcached之——Windows下安装Memcached