MyBatis从入门到精通—MyBatis插件原理探究和自定义插件实现

网友投稿 726 2022-10-30

MyBatis从入门到精通—MyBatis插件原理探究和自定义插件实现

MyBatis从入门到精通—MyBatis插件原理探究和自定义插件实现

插件简介

⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。以MyBatis为例,我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能。

Mybatis插件介绍

Mybati s作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插 件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是-,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象。MyBatis所允许拦截的⽅法如下:

执⾏器Executor (update、query、commit、rollback等⽅法); SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法); 参数处理器ParameterHandler (getParameterObject、setParameters⽅法); 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法); Mybatis插件原理 在四⼤对象创建的时候

每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler); 获取到所有的Interceptor (-)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象 插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

拦截插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说。

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){ ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }

interceptorChain保存了所有的-(interceptors),是mybatis初始化的时候创建的。调⽤-链中的-依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象如果我们想要拦截Executor的query⽅法,那么可以这样定义插件:

@Intercepts({ @Signature( type = Executor.class, method = "query", args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) }) public class ExeunplePlugin implements Interceptor { //省略逻辑 }

除此之外,我们还需将插件配置到sqlMapConfig.xml中。

这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,-链) 中。待准备⼯作做完后,MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后,MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在 Executor相关⽅法被调⽤前执⾏。以上就是MyBatis插件机制的基本原理。

⾃定义插件

插件接口

Mybatis 插件接⼝-Interceptor

Intercept⽅法,插件的核⼼⽅法 plugin⽅法,⽣成target的代理对象 setProperties⽅法,传递插件所需参数 ⾃定义插件 设计实现⼀个⾃定义插件 Intercepts ({//注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤这个- @Signature (type = StatementHandler .class , //这是指拦截哪个接⼝ method = "prepare",//这个接⼝内的哪个⽅法名,不要拼错了 args = { Connection.class, Integer .class}),//// 这是拦截的⽅法的⼊参,按顺序写到这,不要多也不要少,如果⽅法重载,可是要通过⽅法名和⼊参来确定唯⼀的 }) public class MyPlugin implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); // //这⾥是每次执⾏操作的时候,都会进⾏这个-的⽅法内 @Override public Object intercept(Invocation invocation) throws Throwable { //增强逻辑 System.out.println("对⽅法进⾏了增强...."); return invocation.proceed(); //执⾏原⽅法 } /** * //主要是为了把这个-⽣成⼀个代理放到-链中 * ^Description包装⽬标对象 为⽬标对象创建代理对象 * @Param target为要拦截的对象 * @Return代理对象 */ @Override public Object plugin(Object target) { System.out.println("将要包装的⽬标对象:"+target); return Plugin.wrap(target,this); } /**获取配置⽂件的属性**/ //插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来 @Override public void setProperties(Properties properties) { System.out.println("插件配置的初始化参数:"+properties ); } } sqlMapConfig.xml mapper接⼝ public interface UserMapper { List findByCondition(User user); } mapper.xml select * from user 测试类 public class PluginTest { @Test public void test() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); //condition.setId(1); condition.setUsername("zjq"); List byPaging = userMapper.findByCondition(condition); for (User user : byPaging) { System.out.println(user); } } } 源码分析 执⾏插件逻辑Plugin实现了 InvocationHandler接⼝,因此它的invoke⽅法会拦截所有的⽅法调⽤。invoke⽅法会 对所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下: // -Plugin public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { /* *获取被拦截⽅法列表,⽐如: * signatureMap.get(Executor.class), 可能返回 [query, update, commit] */ Set methods = signatureMap.get(method.getDeclaringClass()); //检测⽅法列表是否包含被拦截的⽅法 if (methods != null && methods.contains(method)) { //执⾏插件逻辑 return interceptor.intercept(new Invocation(target, method, args)); //执⾏被拦截的⽅法 return method.invoke(target, args); } catch(Exception e){ } } invoke⽅法的代码⽐较少,逻辑不难理解。⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的@Signature注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在intercept中,该⽅法的参数类型为Invocationo Invocation主要⽤于存储⽬标类,⽅法以及⽅法参数列表。下⾯简单看⼀下该类的定义 public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object targetf Method method, Object[] args) { this.target = target; this.method = method; //省略部分代码 public Object proceed() throws InvocationTargetException, IllegalAccessException { //调⽤被拦截的⽅法 关于插件的执⾏逻辑就分析结束 pageHelper分页插件 MyBatis可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿PageHelper是将分⻚的复杂操作进⾏封装,使⽤简单的⽅式即可获得分⻚的相关数据开发步骤:

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot测试时卡在Resolving Maven dependencies的问题
下一篇:GitDataV - 基于Vue框架构建的github数据可视化平台
相关文章

 发表评论

暂时没有评论,来抢沙发吧~