前言
MyBatis 开放用户实现自己的插件, 从而对整个调用过程进行个性化扩展.
这是 MyBatis 整个调用流程的主要参与者.
我们可以对其中的一些过程进行拦截, 添加自己的功能, 比如重写 Sql 添加分页参数.
拦截的接口
MyBatis 允许拦截的接口如下
- Executor
- public interface Executor {
- ResultHandler NO_RESULT_HANDLER = null;
- int update(MappedStatement var1, Object var2) throws SQLException;
- <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
- <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
- <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
- List<BatchResult> flushStatements() throws SQLException;
- void commit(boolean var1) throws SQLException;
- void rollback(boolean var1) throws SQLException;
- CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
- boolean isCached(MappedStatement var1, CacheKey var2);
- void clearLocalCache();
- void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
- Transaction getTransaction();
- void close(boolean var1);
- boolean isClosed();
- void setExecutorWrapper(Executor var1);
- }
- ParameterHandler
- public interface ParameterHandler {
- Object getParameterObject();
- void setParameters(PreparedStatement var1) throws SQLException;
- }
- ResultSetHandler
- public interface ResultSetHandler {
- <E> List<E> handleResultSets(Statement var1) throws SQLException;
- <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
- void handleOutputParameters(CallableStatement var1) throws SQLException;
- }
- StatementHandler
- public interface StatementHandler {
- Statement prepare(Connection var1, Integer var2) throws SQLException;
- void parameterize(Statement var1) throws SQLException;
- void batch(Statement var1) throws SQLException;
- int update(Statement var1) throws SQLException;
- <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
- <E> Cursor<E> queryCursor(Statement var1) throws SQLException;
- BoundSql getBoundSql();
- ParameterHandler getParameterHandler();
- }
只要拦截器定义了拦截的接口和方法, 后续调用该方法时, 将会被拦截.
拦截器实现
如果要实现自己的拦截器, 需要实现接口 Interceptor
- @Slf4j
- @Intercepts(@Signature(type = Executor.class,
- method ="update",
- args ={MappedStatement.class,Object.class} ))
- public class MyIntercetor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- log.info("MyIntercetor ...");
- Object result = invocation.proceed();
- log.info("result =" + result);
- return result;
- }
- @Override
- public Object plugin(Object o) {
- return Plugin.wrap(o,this);
- }
- @Override
- public void setProperties(Properties properties) {
- }
- }
1. 拦截方法配置
- Intercepts,Signature
- public @interface Intercepts {
- Signature[] value();
- }
- public @interface Signature {
- Class<?> type();
- String method();
- Class<?>[] args();
- }
配置
- @Intercepts(@Signature(type = Executor.class,
- method ="update",
- args ={MappedStatement.class,Object.class} ))
我们知道 Java 中方法的签名包括所在的类, 方法名称, 入参.
@Signature 定义方法签名
type: 拦截的接口, 为上节定义的四个接口
method: 拦截的接口方法
args: 参数类型列表, 需要和方法中定义的顺序一致.
也可以配置多个
- @Intercepts({@Signature(
- type = Executor.class,
- method = "query",
- args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
- ), @Signature(
- type = Executor.class,
- method = "query",
- args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
- )})
- 2. intercept(Invocation 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;
- }
- public Object getTarget() {
- return this.target;
- }
- public Method getMethod() {
- return this.method;
- }
- public Object[] getArgs() {
- return this.args;
- }
- public Object proceed() throws InvocationTargetException, IllegalAccessException {
- return this.method.invoke(this.target, this.args);
- }
- }
通过 Invocation 可以获取到被拦截的方法的调用对象, 方法, 参数.
proceed() 用于继续执行并获得最终的结果.
这里使用了设计模式中的责任链模式.
3. 这里不能返回 null.
用于给被拦截的对象生成一个代理对象, 并返回它.
- @Override
- public Object plugin(Object o) {
- return Plugin.wrap(o,this);
- }
可以看下 wrap 方法, 其实现了 JDK 的接口 InvocationHandler, 也就是为传入的 target 创建了一个代理对象. 这里使用了 JDK 动态代理方式. 也可以自己实现其他代理方式, 比如 cglib.
- public class Plugin implements InvocationHandler {
- private final Object target;
- private final Interceptor interceptor;
- private final Map<Class<?>, Set<Method>> signatureMap;
- 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;
- }
- 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);
- }
- }
- }
由于使用了动态代理, 方法执行时, 将会被调用 invoke 方法, 会先判断是否设置了拦截器: methods != null && methods.contains(method),
如果设置了拦截器, 则调用拦截器 this.interceptor.intercept(new Invocation(this.target, method, args))
否则直接调用 method.invoke(this.target, args);
4. 拦截器在执行前输出 "MyIntercetor ...", 在数据库操作返回后输出 "result =xxx"
- log.info("MyIntercetor ...");
- Object result = invocation.proceed();
- log.info("result =" + result);
插件实现完成!
测试
在 Spring 中引入很简单.
第一种方式:
创建拦截器的 bean
- @Slf4j
- @Configuration
- public class IntercetorConfiguration {
- @Bean
- public MyIntercetor myIntercetor(){
- return new MyIntercetor();
- }
- }
注意第一种方式和第二种方式仅适用于 SpringBoot 应用, 并且引入以下依赖
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
第二种方式
手动往 Configuration 中添加拦截器.
- @Slf4j
- @Configuration
- public class IntercetorConfiguration {
- @Autowired
- private List<SqlSessionFactory> sqlSessionFactoryList;
- @PostConstruct
- public void addPageInterceptor() {
- MyIntercetor interceptor = new MyIntercetor();
- Iterator var3 = this.sqlSessionFactoryList.iterator();
- while(var3.hasNext()) {
- SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next();
- sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
- }
- }
- }
第三种方式
如果是纯 Spring 应用, 可在 mybatis 配置文件中配置
- <plugins>
- <plugin intercetor="xxx.xxx.MyIntercetor">
- <property name="xxx" value="xxx">
- </plugin>
- </plugins>
由于上面定义的拦截器是拦截 Executor 的 update 方法, 所以在执行 insert,update,delete 的操作时, 将会被拦截.
本例子使用 insert 来测试. 具体代码查看: GitHub
- 2019-06-10 16:08:03.109 INFO 20410 --- [nio-8110-exec-1] c.m.user.dao.intercetor.MyIntercetor : MyIntercetor ...
- 2019-06-10 16:08:03.166 INFO 20410 --- [nio-8110-exec-1] com.alibaba.druid.pool.DruidDataSource : {
- dataSource-1
- } inited
- 2019-06-10 16:08:03.267 DEBUG 20410 --- [nio-8110-exec-1] o.m.s.t.SpringManagedTransaction : JDBC Connection [com.MySQL.cj.jdbc.ConnectionImpl@5cb1c36e] will not be managed by Spring
- 2019-06-10 16:08:03.274 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : ==> Preparing: insert into user (name) values (?) , (?) , (?)
- 2019-06-10 16:08:03.307 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : ==> Parameters: name:58(String), name:64(String), name:69(String)
- 2019-06-10 16:08:03.355 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : <== Updates: 3
- 2019-06-10 16:08:03.358 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : ==> Preparing: SELECT LAST_INSERT_ID()
- 2019-06-10 16:08:03.358 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : ==> Parameters:
- 2019-06-10 16:08:03.380 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : <== Total: 1
- 2019-06-10 16:08:03.381 INFO 20410 --- [nio-8110-exec-1] c.m.user.dao.intercetor.MyIntercetor : result = 3
可以看到拦截器被调用了.
如果需要对查询进行拦截, 可以拦截以下方法, 比如通过 MappedStatement 修改 sql 语句.
- <E>
- List
- <E>
- query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler
- var4, CacheKey var5, BoundSql var6) throws SQLException;
- <E>
- List
- <E>
- query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler
- var4) throws SQLException;
结束!!!!!
来源: https://www.cnblogs.com/lgjlife/p/10998363.html