欢迎光临必发888有限公司!
栏目
联系我们
公司地址:http://www.itanking.com
当前位置: 必发888 > 互联网 >
Mybatis还大概有这种操作?浅析为啥要看源码

诸几人都有三个纠缠,为何面试都开心问原理,问源码.可是其实工作一直用不上,也便是贵胄常说的,面试造火箭,进去拧螺丝钉.小编身边也是有成都百货上千有恋人问过作者,小编给他们的回答是.假若不看源码,不懂原理,出了难点你怎么消逝?他们给本人的对答基本都以多个字:"寻找"。

Mybatis Plugin 插件原理深入分析

方今在看mybatis 源码,见到了mybatis plugin部分,其实便是采用JDK动态代理和权利链设计格局的综合运用。选用权利链方式,通过动态代理组织四个拦截器,通过那些拦截器你能够做一些你想做的事。具体分析从叁个平日的必要功用发轫:将来要对负有的接口方法做四个日记记录和接口耗费时间记录。

看来那一个必要成效小编默默的笑了,这还不轻便,种种方法本人都增加不就可以了吗?就这么干,撸起袖子开头噼噼啪啪的敲打着键盘,逐步开采太特么多情势了,况且照旧基本重复的,那样写下去不是情势啊。这么多种复的是否足以抽出出来啊,要百折不回D福睿斯Y(Don't repeat yourself)原则。那个时候想到了代理设计格局,静态代理格局迟早不行,这么多接口,得写多少个代理类啊,依旧用JDK的动态代理吧

public interface Target { String execute(String name);}public class TargetImpl implements Target { @Override public String execute(String name) { System.out.println("execute() "+ name); return name; }}public class TargetProxy implements InvocationHandler { private Object target; public TargetProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(" 拦截前。。。"); Object result = method.invoke(target, args); System.out.println(" 拦截后。。。"); return result; } public static Object wrap(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new TargetProxy; }}public class Test { public static void main(String[] args) { Target target = new TargetImpl(); //返回的是代理对象,实现了Target接口, //实际调用方法的时候,是调用TargetProxy的invoke()方法 Target targetProxy =  TargetProxy.wrap; targetProxy.execute(" HelloWord "); }}

TargetProxy.wrap 实际再次来到的目的相符上面这样,这样看起来就便于驾驭点了

public class $Proxy implements Target { private InvocationHandler targetProxy @Override public String execute(String name) { return targetProxy.invoke(); }}

运作结果:

拦截前。。。execute() HelloWord 拦截后。。。

嗯,那思路是不错的了。但要么存在难点,execute(卡塔尔是事情代码,小编把具有的要阻拦管理的逻辑都写到invoke方法里面了,不合乎面向对象的思考,能够抽象一下管理。能够设计二个Interceptor接口,需求做怎么着阻挡管理实现接口就能够了。

public interface Interceptor { /** * 具体拦截处理 */ void intercept();}

intercept(卡塔尔国 方法就能够拍卖各个中期筹算了

public class LogInterceptor implements Interceptor { @Override public void intercept() { System.out.println; }}public class TransactionInterceptor implements Interceptor { @Override public void intercept() { System.out.println; }}

代办对象也做一下改造

public class TargetProxy implements InvocationHandler { private Object target; private List<Interceptor> interceptorList = new ArrayList<>(); public TargetProxy(Object target,List<Interceptor> interceptorList) { this.target = target; this.interceptorList = interceptorList; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 处理拦截 */ for (Interceptor interceptor : interceptorList) { interceptor.intercept(); } return method.invoke(target, args); } public static Object wrap(Object target,List<Interceptor> interceptorList) { TargetProxy targetProxy = new TargetProxy(target, interceptorList); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),targetProxy); }}

今后能够依据必要动态的丰裕拦截器了,在历次实施职业代码execute早前都会阻碍,看起来高级一丝丝了,来测量检验一下

public class Test { public static void main(String[] args) { List<Interceptor> interceptorList = new ArrayList<>(); interceptorList.add(new LogInterceptor; interceptorList.add(new TransactionInterceptor; Target target = new TargetImpl(); Target targetProxy =  TargetProxy.wrap(target,interceptorList); targetProxy.execute(" HelloWord "); }}

举行理并了结果:

 记录日志 开启事务 execute() HelloWord 

诚如有哪儿不太对同一,遵照上边这种大家只能做前置拦截,何况拦截器并不知道拦截对象的新闻。应该做更一步的架空,把拦截对象信息进行打包,作为拦截器拦截方法的参数,把拦截指标对象真正的施行形式放到Interceptor中做到,这样就能够完毕内外拦截,而且仍可以对堵住对象的参数等做修改。设计三个Invocation 对象

public class Invocation { /** * 目标对象 */ private Object target; /** * 执行的方法 */ private Method method; /** * 方法的参数 */ private Object[] args; //省略getset public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } /** * 执行目标对象的方法 * @return * @throws Exception */ public Object process() throws Exception{ return method.invoke(target,args); }}

互联网,堵住接口做改进

public interface Interceptor { /** * 具体拦截处理 * @param invocation * @return * @throws Exception */ Object intercept(Invocation invocation) throws Exception;}

Invocation 类正是被代理对象的包裹,也等于要阻止的真正对象。TargetProxy修改如下:

public class TargetProxy implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxy(Object target,Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target,method,args); return interceptor.intercept(invocation); } public static Object wrap(Object target,Interceptor interceptor) { TargetProxy targetProxy = new TargetProxy(target, interceptor); return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),targetProxy); }}

public class TransactionInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception{ System.out.println; Object result = invocation.process(); System.out.println; return result; }}public class Test { public static void main(String[] args) { Target target = new TargetImpl(); Interceptor transactionInterceptor = new TransactionInterceptor(); Target targetProxy =  TargetProxy.wrap(target,transactionInterceptor); targetProxy.execute(" HelloWord "); }}

运营结果:

 开启事务 execute() HelloWord 提交事务 

那般就会实现内外拦截,並且拦截器能赢得拦截对象信息,这样扩大性就好过多了。然则测验例子的如此调用看着很别扭,对应目的类来讲,只供给驾驭对她插入了如何阻碍就好。再改革一下,在拦截器扩大三个插入目标类的章程

public interface Interceptor { /** * 具体拦截处理 * @param invocation * @return * @throws Exception */ Object intercept(Invocation invocation) throws Exception; /** * 插入目标类 * @param target * @return */ Object plugin(Object target);}public class TransactionInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception{ System.out.println; Object result = invocation.process(); System.out.println; return result; } @Override public Object plugin(Object target) { return TargetProxy.wrap(target,this); }}

像这种类型目的类仅仅必要在施行前,插入要求的拦截器就好了,测验代码:

public class Test { public static void main(String[] args) { Target target = new TargetImpl(); Interceptor transactionInterceptor = new TransactionInterceptor(); //把事务拦截器插入到目标类中 target =  transactionInterceptor.plugin; target.execute(" HelloWord "); }}

运维结果:

 开启事务 execute() HelloWord 提交事务 

到这里就大致落成了,只怕有同学也许会有问号,那自身要增添多个拦截器呢,怎么搞?

public class Test { public static void main(String[] args) { Target target = new TargetImpl(); Interceptor transactionInterceptor = new TransactionInterceptor(); target =  transactionInterceptor.plugin; LogInterceptor logInterceptor = new LogInterceptor(); target = logInterceptor.plugin; target.execute(" HelloWord "); }}

运维结果:

 开始记录日志 开启事务 execute() HelloWord 提交事务 结束记录日志 

实际上那正是代理嵌套再代理,下图是施行的时序图,种种步骤就不做详细的求证了

互联网 1时序图.png

实在上边已经落成的没难点了,只是还差那么一丢丢,增多七个拦截器的时候不太美貌,让大家重新使用面向对象理念封装一下。我们规划多个InterceptorChain 拦截器链类

public class InterceptorChain { private List<Interceptor> interceptorList = new ArrayList<>(); /** * 插入所有拦截器 * @param target * @return */ public Object pluginAll(Object target) { for (Interceptor interceptor : interceptorList) { target = interceptor.plugin; } return target; } public void addInterceptor(Interceptor interceptor) { interceptorList.add(interceptor); } /** * 返回一个不可修改集合,只能通过addInterceptor方法添加 * 这样控制权就在自己手里 * @return */ public List<Interceptor> getInterceptorList() { return Collections.unmodifiableList(interceptorList); }}

实则正是透过pluginAll()方法包一层把装有的拦截器插入到目的类去而已。测量试验代码:

public class Test { public static void main(String[] args) { Target target = new TargetImpl(); Interceptor transactionInterceptor = new TransactionInterceptor(); LogInterceptor logInterceptor = new LogInterceptor(); InterceptorChain interceptorChain = new InterceptorChain(); interceptorChain.addInterceptor(transactionInterceptor); interceptorChain.addInterceptor(logInterceptor); target =  interceptorChain.pluginAll; target.execute(" HelloWord "); }}

透过地点的分析,再去看mybastis plugin 源码的时候就很自在了。

互联网 2mybatis-plugin代码图

有未有以为似曾相同的认为啊,对的你的认为是没错,那基本和大家地点的最后兑现是同一的,Plugin 也正是大家的TargetProxy。

也确实,职业中山高校部难题通过复制错误消息搜索都能一下子就解决了,加上现在框架进一层多,拼积木式的编程方式充分寻找引擎,让越来越多个人发出了费用是件相当轻巧的事的错觉.小编也直接想举一个招来差相当少搜不到,要看源码才具弄懂当中原因的例子.

Mybatis Plugin 介绍及配置利用

MyBatis 允许你在已映射语句实行进程中的某一点开展拦阻调用。默许意况下,MyBatis允许行使插件来堵住的艺术调用包罗:

1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法;2.ParameterHandler (getParameterObject, setParameters) 拦截参数的处理;3.ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理;4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

仿照效法Mybatis 官方的八个例证:

  1. 在大局配置文件mybatis-config.xml 增加

    <plugins> <plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin></plugins>
    
  2. 写好拦截类代码

    @Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})public class ExamplePlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { }}
    

    本条拦截器会拦截Executor接口的update方法

互联网 3

源码简要解析

先是从构造文件深入分析开始

public class XMLConfigBuilder extends BaseBuilder { //解析配置 private void parseConfiguration(XNode root) { try { //省略部分代码 pluginElement(root.evalNode("plugins")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } private void pluginElement(XNode parent) throws 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); //调用InterceptorChain.addInterceptor configuration.addInterceptor(interceptorInstance); } } }}

地方的代码重倘使剖判配置文件的plugin节点,根据布置的interceptor 属性实例化Interceptor 对象,然后加多到Configuration 对象中的InterceptorChain 属性中

public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) { //循环调用每个Interceptor.plugin方法 for (Interceptor interceptor : interceptors) { target = interceptor.plugin; } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); }}

其一就和大家地点达成的是同一的。定义了阻止器链,起头化配置文件的时候就把持有的拦截器增加到拦截器链中,下边来看一下什么样时候把拦截器插入到须要拦截的接口中

public class Configuration { protected final InterceptorChain interceptorChain = new InterceptorChain(); //创建参数处理器 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { //创建ParameterHandler ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); //插件在这里插入 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } //创建结果集处理器 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { //创建DefaultResultSetHandler ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); //插件在这里插入 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } //创建语句处理器 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //创建路由选择语句处理器 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //插件在这里插入 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } //产生执行器 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; //这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null? executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //如果要求缓存,生成另一种CachingExecutor,装饰者模式,所以默认都是返回CachingExecutor if (cacheEnabled) { executor = new CachingExecutor; } //此处调用插件,通过插件可以改变Executor行为 executor =  interceptorChain.pluginAll; return executor; }}

从代码能够看看mybatis 在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口对象的时候调用interceptorChain.pluginAll(State of Qatar方法插入进去的。其实正是循环实行拦截器链所有的拦截器的plugin(卡塔尔(قطر‎方法,mybatis官方推荐的plugin方法是Plugin.wrap(State of Qatar方法,那些类正是我们地点的TargetProxy类

public class Plugin implements InvocationHandler { public static Object wrap(Object target, Interceptor interceptor) { //从拦截器的注解中获取拦截的类名和方法信息 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); //取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor) Class<?> type = target.getClass(); //取得接口 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); //产生代理,是Interceptor注解的接口的实现类才会产生代理 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //获取需要拦截的方法 Set<Method> methods = signatureMap.get(method.getDeclaringClass; //是Interceptor实现类注解的方法才会拦截处理 if (methods != null && methods.contains { //调用Interceptor.intercept,也即插入了我们自己的逻辑 return interceptor.intercept(new Invocation(target, method, args)); } //最后还是执行原来逻辑 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable; } } //取得签名Map,就是获取Interceptor实现类上面的注解,要拦截的是那个类(Executor,ParameterHandler, ResultSetHandler,StatementHandler)的那个方法 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { //取Intercepts注解,例子可参见ExamplePlugin.java Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 //必须得有Intercepts注解,没有报错 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName; } //value是数组型,Signature的数组 Signature[] sigs = interceptsAnnotation.value(); //每个class里有多个Method需要被拦截,所以这么定义 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type; if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args; methods.add; } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } //取得接口 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<Class<?>>(); while (type != null) { for (Class<?> c : type.getInterfaces { //拦截其他的无效 if (signatureMap.containsKey { interfaces.add; } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size; }}

正好这事发生在了二零一八年12月份,笔者一个很好的心上人问了自身这样个难点,他说为什么作者传的是空字符串,可是用Mybatis的if标签决断该空字符串 == 0 竟然是确立的

总结

Mybatis 拦截器的利用是促成Interceptor接口

public interface Interceptor { //拦截 Object intercept(Invocation invocation) throws Throwable; //插入 Object plugin(Object target); //设置属性 void setProperties(Properties properties);}

通过上边的剖判能够领略,全体希望被阻止的拍卖类都会变卦二个代理类,借使有N个拦截器,就能够有N个代理,层层生成动态代理是比较耗质量的。并且虽然能钦命插件拦截的地点,但以此是在实行办法时利用反射动态决断的,初阶化的时候正是简单的把拦截器插入到了独具能够阻碍的地点。所以尽或许不要编写不必要的拦截器。

互联网 4

从大家的体味上的话,二个 空字符串 和 三个数字0是不只怕相当于的.所以作者首先感应是,他是或不是用法不对?或许是他的专门的学业代码别的地点苦恼到了? 于是本人主宰写了个最简便的demo来进展测量试验.如下

互联网 5

接下来输出结果如下:

惊诧的开采,这么些if标签果然把空字符串和数字0剖断成了相等.

此处自个儿并不想骗我们,遭受这种难题,坦白说第一反应自然不是看源码啦,当然是开荒浏览器寻觅一下.大家探究的来头入眼有三个,贰个是mybatis if标签的论断原理,叁个是为什么mybatis if标签空字符串和0是万分的.结果开采,并不曾找到大家要想的答案(我们能够自行检索一下State of Qatar.

本来纵然从未搜索到称心的答案,不过大家却开采了另叁个例子.

本身言听事行相仿这种判别的代码大家项目中应该现身了非常多.

小编们常常开支中,非常多同事都以赏识复制黏贴!

互联网 6

那么不假考虑的复制黏贴到底会有什么样难点啊,大家来看下边那几个事例

互联网 7

那么些决断尽管是复制黏贴一把梭出去的,但是从大家的心得上的话,这么些目的真正不是null,也不等于空字符串,所以那么些论断相应是true的,可是运转结果如下:

果真,这么些又倾覆了大家的咀嚼,然而只要你境遇的是案例2这种状态还相比好寻找,仍是可以搜到实施方案,如下图

互联网 8

事实上那八个案例都以叁个难题,那正是以此if标签,把0和空字符串决断成了相等.

本条时候要敲黑板划入眼了,民间语说一朝被蛇咬十年怕井绳,固然第二个例子大家有了缓和方案,可是那几个建设方案都以治标不治本,如若大家没弄懂这几个中的法规,那么您心里恒久是有一块疙瘩的.你惊惶下三遍,又有奇奇异怪的事情时有爆发,唯有弄懂原理,手艺从根源解决难点,也等于减轻一类难点,并非某一个难题.

与此同期小编也意识到,机缘来了,终于找到八个为何要看源码的相比适当例子驾驭析源码

是因为链路相比较长.这里就不把debug进度显得了(对Mybatis施行流程面生的,能够看看自家在此之前的别怕看源码,一张图化解Mybatis的Mapper原理,然后沿着实行流程debug

作者们拿第多个例证来深入分析,因为五个案例其实碰到的难题都以一成不变的.

互联网 9