插件
使用
- 使用插件意味着在修改mybatis的底层封装,虽然灵活但是也可能导致mybatis出现重大bug
在mybatis中使用插件就必须实现Interceptor接口
public interface Interceptor { //直接覆盖所拦截对象原有的方法,通过Invocation反射调用原来对象的方法 Object intercept(Invocation invocation) throws Throwable; // target指的是被拦截的对象,对拦截对象生成一个代理对象,并返回 Object plugin(Object target); //在plugin元素中配置所需要的参数,在插件初始化的时候调用一次 void setProperties(Properties properties); } 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; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } }
mybatis提供了一个工具类Plugin,实现了InvocationHandler接口,采用jdk动态代理
public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = 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); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } 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)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { // issue #251 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<Class<?>>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); } }
确定需要拦截的签名
- Mybatis插件可以拦截Executor、StatementHandler、ParameterHandler、ResulteSetHandler中的任意一个。
* Executor是执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程
* StatementHandler是执行SQL的过程,可以重写执行SQL的过程,这是常用的拦截对象
* ParameterHandler,很明显它主要是拦截执行SQL的参数组装,你可以重写组装参数规则
* ResultSetHandler用于拦截执行结果的组装,你可以重写组装结果的规则 案例
配置插件,注意顺序 * Mybatis3.X 的版本使用的 dtd 作为 XML 的格式校验文档。 * 而在 XML 规范中,dtd 是有严格的顺序的,在报错的异常中已经列出了对应的顺序, * 应该为:(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?, objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?) <plugins> <plugin interceptor="com.jannal.mybatis.plugin.MyPlugin"> <property name="jannal" value="jannalpro" /> </plugin> </plugins> /** * @Intercepts 标识它是一个拦截器 * @Signature 注册拦截器签名的地方 */ @Intercepts(value = { @Signature(type = StatementHandler.class, // 确定要拦截的对象 method = "prepare", // 确定要拦截的方法 args = { Connection.class }// 拦截方法的参数 ) }) public class MyPlugin implements Interceptor { private Properties properties; @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("开始拦截....."); Object proceed = invocation.proceed(); System.out.println("结束拦截....."); return proceed; } @Override public Object plugin(Object target) { System.out.println("生成代理对象...."); return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { System.out.println(properties.get("jannal")); this.properties = properties; } } 生成代理对象.... 生成代理对象.... 生成代理对象.... 生成代理对象.... 2017-06-23 11:50:50.790 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - Openning JDBC Connection 2017-06-23 11:50:50.998 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - Created connection 982757413. 2017-06-23 11:50:51.001 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3a93b025] 开始拦截..... 2017-06-23 11:50:51.002 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - ==> Preparing: select id ,mobile_no_internet ,cust_no ,mes_code ,mes_title ,mes_content ,create_time ,inserttime ,updatetime from t_user_message t where t.id=?; 结束拦截.....