Mybatis插件原理及案例

1,猜想

不修改代码如何增强功能

很容易能想到就是代理模式,这也是Mybatis插件的实现原理

多个插件怎么拦截

插件是层层拦截的,我们需要用到另一个设计模式-责任链模式

什么对象可以被拦截

 2,插件实现原理

1,代理类在什么时候创建?实在解析配置的时候,还是在获取会话的时候创建,还是在调用的时候创建

2,核心对象被代理后,调用的流程是怎么执行的呢?怎么依次执行多个插件逻辑?在执行完插件后怎么执行原来的逻辑

代理类什么时候创建

对Executor拦截的代理类是openSession()的时候创建的

 //创建 Executor
 Executor executor = this.configuration.newExecutor(tx, execType)

代理怎么创建

 遍历interceptorChain,使用Interceptor的实现类plugin()方法,对目标核心对象进行代理。

这个plugin方法是我们要实现的返回一个代理对象

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    default void setProperties(Properties properties) {
    }
}

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}

被代理后,调用的流程?

如果被拦截的方法不为空,进入Plugin的invoke()方法,调用interceptor的intercept方法,也就走到了我们自己实现的拦截逻辑(比如:PageInterceptor的intercept方法)

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
        return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
    } catch (Exception var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

3,案例实现(慢sql检测)

package com.boe.itserviceportal.config.thread;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;
/**
 * 慢sql查询插件
 */
@Intercepts({@Signature(type = StatementHandler.class, method ="prepare", args = {Connection.class, Integer.class})})
public class SlowSqlPlugin implements Interceptor {
    private long slowTime;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        long startTime = System.currentTimeMillis();
        Object proceed = invocation.proceed();
        long endTime = System.currentTimeMillis();
        long f = endTime - startTime;
        System.out.println("sql查询耗时" + f);
        if (f > slowTime) {
            System.out.println("本次数据库操作是慢查询,sql是:");
            System.out.println(boundSql.getSql());
        }
        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        //触发intercept方法
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //获取我们定义的慢sql的时间阈值slowTime
        this.slowTime = Long.parseLong(properties.getProperty("slowTime"));
    }
}
 

package com.boe.itserviceportal;
import com.boe.itserviceportal.config.thread.SlowSqlPlugin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Properties;
/**
 * @author 10522120c
 */
@SpringBootApplication
@EnableSwagger2
@MapperScan({"com.boe.itserviceportal.**.mapper"})
public class ItServicePortalApplication {

    public static void main(String[] args) {
        SpringApplication.run(ItServicePortalApplication.class, args);
    }

    //注入bean

    @Bean
    public SlowSqlPlugin sqlPlugin() {
        SlowSqlPlugin slowSqlPlugin = new SlowSqlPlugin();
        Properties properties = new Properties();
        properties.setProperty("slowTime", "10");
        slowSqlPlugin.setProperties(properties);
        return slowSqlPlugin;
    }
}

4,源码分析

 

        到了intercept()方法,也就走到了我们自己实现的拦截逻辑(例如Pagelnterceptor的intercept()方法)。
        注意参数是new出来的Invocation对象,它是对被拦截对象、被拦截方法、被拦截参数的一个封装。为什么要传这样一个参数呢?

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

 当然,在代理逻辑执行完了之后,比如继续执行被代理对象(四大核心对象)的原方法,也就是说要有一行这样的代码:

return method.invoke(target, args);


        或者拿到被代理的核心对象,继续执行它的方法(比如executor.query())。这个时候,我们要拿到被代理对象和它的参数,去哪里拿?
        就是上面创建的Invocation对象。它简化了参数的传递,而且直接提供了一个proceed()方法,也就是说继续执行原方法可以写成:
 

return invocation.proceed();

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值