mybatis实现自定义插件
Posted 我爱看明朝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis实现自定义插件相关的知识,希望对你有一定的参考价值。
mybatis实现自定义插件
写本文的原因是在写制品库代码时遇到了下面了两个问题:
- Signature注解的各个属性的可选值
- 为什么会@component和@bean注释的拦截器的不生效
遂阅读了mybatis部分源码,找出问题2症结所在
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
type:
ParameterHanlder
ResultSetHanler
StatementHanlder
Executor
method:
四个被拦截接口的所有方法
ParameterHanlder: getParameterObject, setParameters
ResultSetHanler: handlerResultSets,handlerOutputParameters
StatementHanlder: prepare,parameterize,batch,update,query
Executor: update,query,flushStatements,commit,rollback,getTransaction,close,isClosed
args:
被拦截方法参数的类
自定义插件
@Intercepts(
{@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}
)
//
@Component
public class SQLStatsInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(SQLStatsInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
//自定义插件主要逻辑代码
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
logger.info(properties.toString());
}
}
加载自定义插件的三种方式
1. 手动使用sqlSessionFactory加载插件
public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addInterceptor(new MyInterceptor());
return "interceptor";
}
2. 使用@bean的方式加载插件
@Bean
public MyInterceptor generalPlugin() {
return new MyInterceptor();
}
3. @Component直接注解在自定义插件加载
@Intercepts({
@Signature(
.................
})
@Component
class MyInterceptor{
}
上面2,3中方式会在MybatisAutoConfiguration的构造器中获取到自定义插件,当用户没有自己
创建SqlSessionFactory时会执行sqlSessionFactory()方法,这个时候会加载自定义的插件。
```java
//执行完上面的会进入: MybatisAutoConfiguration的构造器加载插件
public MybatisAutoConfiguration() {
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
}
pageHelper的自定配置是在MybatisAutoConfiguration之后的。
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
public MybatisAutoConfiguration() {
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
}
}
@AutoConfigureAfter({MybatisAutoConfiguration.class})
public class PageHelperAutoConfiguration {
}
网上有文章说@Component和@Bean不生效的原因是pageHelper插件导致的。但是经过实测:发现是没有关系。
pageHelper的自定配置是在MybatisAutoConfiguration之后,我们打断点在MybatisAutoConfiguration,发现即使有
pageHeper但是@Component和@Bean注解的bean是被MybatisAutoConfiguration存储在interceptors属性可,
但是并没有被执行factory.setPlugins(this.interceptors);方法加载到sqlSessionFactory。因此不存在pageHelper影响了
自定义插件的加载。
当有开发者自己创建了sqlSessionFactory那么,自定义插件一定要通过上述方式1手动加载到sqlSessionFactory中.
源码
加载插件
sqlSessionFactory.getConfiguration().addInterceptor(new YourInterceptor());
public void addInterceptor(Interceptor interceptor) {
//添加插件到责任链中
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
// 四大金刚加入到插件通过责任链设计模式进行链式处理
// parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
// resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
// statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
// executor = (Executor) interceptorChain.pluginAll(executor);
public Object pluginAll(Object target) {
// 责任链模式, 多个插件按照加入在list中的顺序进行处理
for (Interceptor interceptor : interceptors) {
//注意这个target是每一上一步处理完返回的对象
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
public class Configuration{
public ParameterHandler newParameterHandler(){
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}
public ResultSetHandler newResultSetHandler(){
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}
public StatementHandler newStatementHandler(){
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
public Executor newExecutor(){
executor = (Executor) interceptorChain.pluginAll(executor);
}
}
时序
new出四大金刚 --> interceptorChain.pluginAll --> target = interceptor.plugin(target); —> Plugin.wrap(target, this); Plugin:动态代理 --> Plugin.invoke --> interceptor.intercept
public class Congiguration{
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//省略部分代码
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
//加载所有插件
target = interceptor.plugin(target);
}
return target;
}
}
// Plugin实现jdk的InvocationHandler接口表示这个是动态代理
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);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//最终通过invoke方法运行具体的代理逻辑
// method == interceptor(就是用户自己实现的自定义插件)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//调用用户自定义插件重写的intercept方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
扩展知识
@ConditionalOnClass
条件创建bean当,当SqlSessionFactory,SqlSessionFactoryBean这个两个类存在是,才会创建被注解的bean
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean
条件创建bean,当DataSource的bean存在,才可以创建被这个被注解的bean
@ConditionalOnBean({DataSource.class})
@ConditionalOnMissingBean
当指定的bean不存在,再执行代码,否则不执行。
下面代码的意思是:当spring的容器中没有SqlSessionFactory时才执行下面方法的代码
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory() {
}
总结
1.自定义插件可拦截处理的对象:mybatis四大金刚 ParameterHanlder、 ResultSetHanler、 StatementHanlder、 Executor
2.插件使用责任链模式:四大金刚加入到插件通过责任链设计模式进行链式处理
3.有三种方式可以把自定义插件加载到mybatis中
4.@component和@bean注释的拦截器不生效的原因是用户new sqlSessionFactory
5.@Signature注解的三个参数type、method、args分别对应的值是:四大金刚、要拦截type指定类的方法、指定methos方法的参数类
6.插件机制用到用jdk的动态代理:Plugin实现jdk的InvocationHandler接口表示这个是动态代理
7.扩展知识spring中的注解:@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean
https://mybatis.org/mybatis-3/configuration.html#plugins
http://www.mybatis.cn/726.html
http://www.mybatis.cn/archives/685.html
以上是关于mybatis实现自定义插件的主要内容,如果未能解决你的问题,请参考以下文章