尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MyMyBatis ④:手写插件责任链——Interceptor 与 Plugin.wrap

MyMyBatis ④:手写插件责任链——Interceptor 与 Plugin.wrap 系列第 1 篇手写骨架系列第 2 篇ParameterHandler系列第 3 篇MappedStatement 与三种 Executor源码https://github.com/OrigamiOrigamiOrigami/MyMyBatis前言有了Executor之后横切能力不该继续写进BaseExecutor。官方做法是插件用 JDK 动态代理把Executor也可以是 StatementHandler 等包一层按签名拦截指定方法。本篇实现迷你版Interceptor、Intercepts/Signature、Plugin.wrap、InterceptorChain并附带一个慢 SQL 示例。本篇目标类作用Interceptorintercept/pluginIntercepts / Signature声明拦截哪个接口的哪个方法PluginInvocationHandler决定进拦截还是放行InterceptorChain按注册顺序多层wrapSlowSqlInterceptor统计query/update耗时1. 注解怎么声明拦截点Intercepts({Signature(typeExecutor.class,methodquery,args{MappedStatement.class,Object.class}),Signature(typeExecutor.class,methodupdate,args{MappedStatement.class,Object.class})})publicclassSlowSqlInterceptorimplementsInterceptor{...}type必须是接口。代理只会实现签名里出现过的接口。2. Plugin.wrap 做了什么publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){MapClass?,SetMethodsignatureMapgetSignatureMap(interceptor);Class?[]interfacesgetAllInterfaces(target.getClass(),signatureMap);if(interfaces.length0){returnProxy.newProxyInstance(target.getClass().getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}调用时SetMethodmethodssignatureMap.get(method.getDeclaringClass());if(methods!nullmethods.contains(method)){returninterceptor.intercept(newInvocation(target,method,args));}returnmethod.invoke(target,args);Invocation.proceed()就是method.invoke(target, args)继续往下一层走。3. 责任链一层包一层publicObjectpluginAll(Objecttarget){for(Interceptorinterceptor:interceptors){targetinterceptor.plugin(target);}returntarget;}创建 Executor 时ExecutorexecutornewSimpleExecutor(...);return(Executor)interceptorChain.pluginAll(executor);注册插件Configuration.builder().database(jdbc.properties).plugin(newSlowSqlInterceptor()).build();4. 慢 SQL 示例publicObjectintercept(Invocationinvocation)throwsThrowable{longstartSystem.currentTimeMillis();try{returninvocation.proceed();}finally{longcostSystem.currentTimeMillis()-start;MappedStatementms(MappedStatement)invocation.getArgs()[0];if(costthresholdMs){System.out.println([SlowSQL] costms idms.getId());}else{System.out.println([SQL] costms idms.getId());}}}阈值可用setProperties配置。分页插件同理在query前改BoundSql这里先不做避免一篇写爆。5. 为什么一定要挂在 Executor 上第 3 篇之前逻辑在 Session 里横切只能改 Session。收口到 Executor 后慢 SQL、审计、租户改写都有统一挂点换 SIMPLE / BATCH 不影响插件接口和官方阅读源码时的路径一致和官方的差距官方还能拦StatementHandler/ParameterHandler/ResultSetHandler我们目前主要示范 Executor没有Plugin对内部类、桥接方法的全部边角处理分页改写、拦截顺序与Signature冲突检测未做强校验总结插件 带签名的 JDK 代理多个插件 多层代理业务扩展优先写 Interceptor而不是改 Executor 源码建议对照plugin/Plugin.java、SlowSqlInterceptor.java、Configuration.newExecutor。下篇预告MyMyBatis ⑤动态 SQL、TypeHandler 与 XML Mapper。
返回列表