mybatis插件机制

目录 mybatis插件机制 主要 类/接口 和 方法 mybatis插件机制实现 mybatis插件机制 mybatis的插件机制使用动态代理实现,不了解的朋友请先了解代理模式和动态代理;插件本质是功能增强,那么它如果需要对某个方法进行增强,首先要拦截这个方法,其实也就类似于拦截器,mybatis的插件在代码中定义为Interceptor,也就是拦截器;后面统一称作拦截器; 主要 类/接口 和 方法 Interceptor 方法 主要功能 Object intercept(Invocation invocation) 拦截器功能具体实现 Object plugin(Object target) 为被代理类生成代理对象 void setProperties(Properties properties) 获取自定义配置 InterceptorChain 方法 主要功能 public Object pluginAll(Object target) 生成代理对象,通过代理的方式注入拦截器功能 public void addInterceptor(Interceptor interceptor) 注册插件 public List 获取一个不可修改的拦截器集合 Plugin 方法 主要功能 public static Object wrap(Object target, Interceptor interceptor) 使用动态代理生成代理对象 public Object invoke(Object proxy, Method method, Object[] args) 调用拦截器的intercept方法或者直接调用被代理对象的当前方法 private static Map, Set 根据拦截器上的注解反射获取目标方法 mybatis插件机制实现 InterceptorChain中维护了一个拦截器列表,也就说所有的拦截器都要在这里注册; InterceptorChain中关键的一个方法是pluginAll: public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } pluginAll方法中是调用了Interceptor的plugin方法, 这个plugin方法是一个覆写方法,需要插件开发者自己实现,但是Mybatis已经提供了相应的实现: @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } 打开Plugin这个工具类发现它实现了InvocationHandler接口,再看wrap方法: public static Object wrap(Object target, Interceptor interceptor) { //获取此拦截器想要代理的目标方法 Map, Set> 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; } 实际上就是使用jdk动态代理为target创建了一个代理对象,并且这个被代理的对象必须是一个接口的实现类(jdk动态代理必须有接口),否则不会生成代理对象,也就是说拦截器只能拦截接口的方法;既然是动态代理肯定要看一下invoke方法: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set methods = signatureMap.get(method.getDeclaringClass()); //如果当前执行的方法是拦截器想要拦截的方法则执行拦截器intercept方法否则,执行原方法; 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); } } 可以看到invoke方法中signatureMap是否有存在当前调用的Method决定了是否调用Interceptor的intercept方法,也就是说Interceptor只对signatureMap中的方法生效,那么再来看signatureMap是怎么来的: private static Map, Set> getSignatureMap(Interceptor interceptor) { //获取拦截器Intercepts注解 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, Set> signatureMap = new HashMap, Set>(); for (Signature sig : sigs) { Set methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet(); 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; } 到这里可以看到,Interceptor需要拦截的方法通过@Intercepts注解申明,这里根据注解中的方法签名(类类型,方法名,方法参数)反射获取具体方法并添加到signatureMap中;Interceptor中的Intercepts注解就是定义需要拦截的方法集合; 那么再回过头看InterceptorChain的pluginAll方法,方法内遍历所有的拦截器并调用plugin方法,实际上就是每个插件都是一层代理,通过多层代理来绑定多个插件;换句话说,某个对象要想被InterceptorChain中的拦截器拦截,那么此对象必须经过InterceptorChain的pluginAll(Object target)方法的包装,当然由于jdk动态代理的关系必须是接口对象; 比如mybatis分页插件: @SuppressWarnings({"rawtypes", "unchecked"}) @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}), } ) public class PageInterceptor implements Interceptor { //缓存count查询的ms protected Cache msCountMap = null; private Dialect dialect; private String default_dialect_class = "com.github.pagehelper.PageHelper"; private Field additionalParametersField; @Override public Object intercept(Invocation invocation) throws Throwable { //分页逻辑 } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //获取配置 } } 通过上文我们知道它需要拦截Executor的query方法,Executor是mybatis运行sql的核心组件,之所以能够被拦截是因为: 在Configuration类中创建Executor之后,有这样一句代码: executor = (Executor) interceptorChain.pluginAll(executor); 完~ *个人认为博客内容主观性比较强且没有经过严格审校,仅供参考交流,学习需以权威资料为准。 *若有不当之处,欢迎指正,不胜感激。 *Email:fengwbetter@163.com 分类: Java,mybatis 标签: java, mybatishttps://www.cnblogs.com/fengwbetter/p/10106522.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信