作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
回答
Mybatis 的运行涉及到 Executor、StatementHandler、ParameterHandler、ResultSetHandler 等组件,插件的工作原理就是在这些组件执行过程中,插入一些自定义的代码逻辑。
MyBatis 插件机制允许用户自定义拦截器(实现Interceptor
接口),以在执行 SQL 的各个阶段进行额外的处理。
实现机制
Mybatis 使用 JDK 动态代理,为目标对象生成代理对象。从而在目标对象方法调用时被拦截器拦截处理。
public class Plugin implements InvocationHandler {
/**
* 创建代理对象
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 获取拦截器关注的类和方法签名的映射
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标对象实现的所有接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// JDK 动态代理创建目标对象
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 {
// 从签名 Map 获取方法
Set<Method> 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) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
扩展
Mybatis 插件的实现步骤
- 定义拦截器:实现
Interceptor
接口。
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 添加自定义的拦截逻辑
System.out.println("Before executing SQL");
Object returnValue = invocation.proceed(); // 继续执行原方法
System.out.println("After executing SQL");
return returnValue;
}
@Override
public Object plugin(Object target) {
// 创建代理对象。
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置插件属性
}
}
- 配置拦截器:在 MyBatis 配置文件中注册拦截器。
方法一
<configuration>
<plugins>
<plugin interceptor="com.damingge.interceptor.MyInterceptor"/>
</plugins>
</configuration>
方法二
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
// 设置自定义插件
Interceptor[] plugins = {new MyInterceptor()};
sessionFactoryBean.setPlugins(plugins);
return sessionFactoryBean.getObject();
}
}
备注:在 MyBatis 中,支持多个拦截器。拦截器之间的执行顺序由它们在配置中的声明顺序决定。
- 拦截目标方法:在拦截器中定义要拦截的方法和逻辑。
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Intercepts
和 @Signature
注解
type
:拦截的对象类型。method
:拦截的方法名称。args
:方法的参数类型。