简单了解mybatis拦截器实现原理及实例
这篇文章主要介绍了简单了解mybatis拦截器实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
例行惯例,先看些基本概念:
1拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑
2Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。
自定义拦截器
/**
*mybatis自定义拦截器
*三步骤:
*1实现{@linkInterceptor}接口
*2添加拦截注解{@linkIntercepts}
*3配置文件中添加拦截器
*
*1实现{@linkInterceptor}接口
*具体作用可以看下面代码每个方法的注释
*2添加拦截注解{@linkIntercepts}
*mybatis拦截器默认可拦截的类型只有四种,即四种接口类型Executor、StatementHandler、ParameterHandler和ResultSetHandler
*对于我们的自定义拦截器必须使用mybatis提供的注解来指明我们要拦截的是四类中的哪一个类接口
*具体规则如下:
*a:Intercepts拦截器:标识我的类是一个拦截器
*b:Signature署名:则是指明我们的拦截器需要拦截哪一个接口的哪一个方法
*type对应四类接口中的某一个,比如是Executor
*method对应接口中的哪类方法,比如Executor的update方法
*args对应接口中的哪一个方法,比如Executor中query因为重载原因,方法有多个,args就是指明参数类型,从而确定是哪一个方法
*3配置文件中添加拦截器
*拦截器其实就是一个plugin,在mybatis核心配置文件中我们需要配置我们的plugin:
*
*
*
*
*
*拦截器顺序
*1不同拦截器顺序:
*Executor->ParameterHandler->StatementHandler->ResultSetHandler
*
*2对于同一个类型的拦截器的不同对象拦截顺序:
*在mybatis核心配置文件根据配置的位置,拦截顺序是从上往下
*/
@Intercepts({
@Signature(method="update",type=Executor.class,args={MappedStatement.class,Object.class}),
@Signature(method="query",type=StatementHandler.class,args={Statement.class,ResultHandler.class})
})
publicclassMyInterceptorimplementsInterceptor{
/**
*这个方法很好理解
*作用只有一个:我们不是拦截方法吗,拦截之后我们要做什么事情呢?
*这个方法里面就是我们要做的事情
*
*解释这个方法前,我们一定要理解方法参数{@linkInvocation}是个什么鬼?
*1我们知道,mybatis拦截器默认只能拦截四种类型Executor、StatementHandler、ParameterHandler和ResultSetHandler
*2不管是哪种代理,代理的目标对象就是我们要拦截对象,举例说明:
*比如我们要拦截{@linkExecutor#update(MappedStatementms,Objectparameter)}方法,
*那么Invocation就是这个对象,Invocation里面有三个参数targetmethodargs
*target就是Executor
*method就是update
*args就是MappedStatementms,Objectparameter
*
*如果还是不能理解,我再举一个需求案例:看下面方法代码里面的需求
*
*该方法在运行时调用
*/
@Override
publicObjectintercept(Invocationinvocation)throwsThrowable{
/*
*需求:我们需要对所有更新操作前打印查询语句的sql日志
*那我就可以让我们的自定义拦截器MyInterceptor拦截Executor的update方法,在update执行前打印sql日志
*比如我们拦截点是Executor的update方法:intupdate(MappedStatementms,Objectparameter)
*
*那当我们日志打印成功之后,我们是不是还需要调用这个query方法呢,如何如调用呢?
*所以就出现了Invocation对象,它这个时候其实就是一个Executor,而且method对应的就是query方法,我们
*想要调用这个方法,只需要执行invocation.proceed()
*/
/*因为我拦截的就是Executor,所以我可以强转为Executor,默认情况下,这个Executor是个SimpleExecutor*/
Executorexecutor=(Executor)invocation.getTarget();
/*
*Executor的update方法里面有一个参数MappedStatement,它是包含了sql语句的,所以我获取这个对象
*以下是伪代码,思路:
*1通过反射从Executor对象中获取MappedStatement对象
*2从MappedStatement对象中获取SqlSource对象
*3然后从SqlSource对象中获取获取BoundSql对象
*4最后通过BoundSql#getSql方法获取sql
*/
MappedStatementmappedStatement=ReflectUtil.getMethodField(executor,MappedStatement.class);
SqlSourcesqlSource=ReflectUtil.getField(mappedStatement,SqlSource.class);
BoundSqlboundSql=sqlSource.getBoundSql(args);
Stringsql=boundSql.getSql();
logger.info(sql);
/*
*现在日志已经打印,需要调用目标对象的方法完成update操作
*我们直接调用invocation.proceed()方法
*进入源码其实就是一个常见的反射调用method.invoke(target,args)
*target对应Executor对象
*method对应Executor的update方法
*args对应Executor的update方法的参数
*/
returninvocation.proceed();
}
/**
*这个方法也很好理解
*作用就只有一个:那就是Mybatis在创建拦截器代理时候会判断一次,当前这个类MyInterceptor到底需不需要生成一个代理进行拦截,
*如果需要拦截,就生成一个代理对象,这个代理就是一个{@linkPlugin},它实现了jdk的动态代理接口{@linkInvocationHandler},
*如果不需要代理,则直接返回目标对象本身
*
*Mybatis为什么会判断一次是否需要代理呢?
*默认情况下,Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler
*通过{@linkIntercepts}和{@linkSignature}两个注解共同完成
*试想一下,如果我们开发人员在自定义拦截器上没有指明类型,或者随便写一个拦截点,比如Object,那Mybatis疯了,难道所有对象都去拦截
*所以Mybatis会做一次判断,拦截点看看是不是这四个接口里面的方法,不是则不拦截,直接返回目标对象,如果是则需要生成一个代理
*
*该方法在mybatis加载核心配置文件时被调用
*/
@Override
publicObjectplugin(Objecttarget){
/*
*看了这个方法注释,就应该理解,这里的逻辑只有一个,就是让mybatis判断,要不要进行拦截,然后做出决定是否生成一个代理
*
*下面代码什么鬼,就这一句就搞定了?
*Mybatis判断依据是利用反射,获取这个拦截器MyInterceptor的注解Intercepts和Signature,然后解析里面的值,
*1先是判断要拦截的对象是四个类型中Executor、StatementHandler、ParameterHandler、ResultSetHandler的哪一个
*2然后根据方法名称和参数(因为有重载)判断对哪一个方法进行拦截Note:mybatis可以拦截这四个接口里面的任一一个方法
*3做出决定,是返回一个对象呢还是返回目标对象本身(目标对象本身就是四个接口的实现类,我们拦截的就是这四个类型)
*
*好了,理解逻辑我们写代码吧~~~What!!!要使用反射,然后解析注解,然后根据参数类型,最后还要生成一个代理对象
*我一个小白我怎么会这么高大上的代码嘛,怎么办?
*
*那就是使用下面这句代码吧哈哈
*mybatis早就考虑了这里的复杂度,所以提供这个静态方法来实现上面的逻辑
*/
returnPlugin.wrap(target,this);
}
/**
*这个方法最好理解,如果我们拦截器需要用到一些变量参数,而且这个参数是支持可配置的,
*类似Spring中的@Value("${}")从application.properties文件获取
*这个时候我们就可以使用这个方法
*
*如何使用?
*只需要在mybatis配置文件中加入类似如下配置,然后{@linkInterceptor#setProperties(Properties)}就可以获取参数
*
*
*
*
*方法中获取参数:properties.getProperty("username");
*
*问题:为什么要存在这个方法呢,比如直接使用@Value("${}")获取不就得了?
*原因是mybatis框架本身就是一个可以独立使用的框架,没有像Spring这种做了很多依赖注入的功能
*
*该方法在mybatis加载核心配置文件时被调用
*/
@Override
publicvoidsetProperties(Propertiesproperties){
Stringusername=properties.getProperty("username");
Stringpassword=properties.getProperty("password");
//TODO:2019/2/28业务逻辑处理...
}
}
三个核心方法都加了详细的注释,而且结合案例需求说明问题
那么多文字不想行看,没关系有概括
总结:
1.在mybatis中可被拦截的类型有四种(按照拦截顺序):
- Executor:拦截执行器的方法。
- ParameterHandler:拦截参数的处理。
- ResultHandler:拦截结果集的处理。
- StatementHandler:拦截Sql语法构建的处理。
2.各个参数的含义:
- @Intercepts:标识该类是一个拦截器;
- @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
2.1type:对应四种类型中的一种;
2.2method:对应接口中的哪类方法(因为可能存在重载方法);
2.3args:对应哪一个方法;
不知道能否帮助你理解,我的表达能力有限~~~
接下来我们看看Plugin类
packageorg.apache.ibatis.plugin;
/**
*Plugin类其实就是一个代理类,因为它实现了jdk动态代理接口InvocationHandler
*我们核心只需要关注两个方法
*wrap:
*如果看懂了代码案例1的例子,那么这个方法很理解,这个方法就是mybatis提供给开发人员使用的一个工具类方法,
*目的就是帮助开发人员省略掉反射解析注解Intercepts和Signature,有兴趣的可以去看看源码Plugin#getSignatureMap方法
*
*invoke:
*这个方法就是根据wrap方法的解析结果,判断当前拦截器是否需要进行拦截,
*如果需要拦截:将目标对象+目标方法+目标参数封装成一个Invocation对象,给我们自定义的拦截器MyInterceptor的intercept方法
*这个时候就刚好对应上了上面案例1中对intercept方法的解释了,它就是我们要处理自己逻辑的方法,
*处理好了之后是否需要调用目标对象的方法,比如上面说的打印了sql语句,是否还要查询数据库呢?答案是肯定的
*如果不需要拦截:则直接调用目标对象的方法
*比如直接调用Executor的update方法进行更新数据库
*
*/
classPluginimplementsInvocationHandler{
publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){
//省略
}
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
//省略
}
}
贴一段网上的通用解释吧:
Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。
所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。
这就是Mybatis中实现Interceptor拦截的一个思想
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。