• MyBatis源码分析(2)—— Plugin原理


    @(MyBatis)[Plugin]

    MyBatis源码分析——Plugin原理

    Plugin原理

    Plugin的实现采用了Java的动态代理,应用了责任链设计模式

    InterceptorChain

    拦截器链,用于保存从配置文件解析后的所有拦截器

    插件链的创建

    在Configuration解析配置文件的时候,XMLConfigBuilder.parseConfiguration中会调用pluginElement解析插件信息并实例化后,保存到插件链中

    // /configuration/plugins节点
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
    	  // 获取所有的插件定义
          for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            // 反射,实例化插件
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            // 保存到插件链中
            configuration.addInterceptor(interceptorInstance);
          }
        }
      }
    // Configuration.addInterceptor
    public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
    }
    
    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);
      }
      
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }
    

    插件拦截

    在MyBatis中,只能拦截四种接口的实现类:

    • Executor

    • ParameterHandler

    • ResultSetHandler

    • StatementHandler
      每种类型的拦截方式都是一样的,这里取executor为例:
      在创建SqlSession的时候,会需要创建Executor实现类,在创建时,会调用插件链的加载插件功能:executor = (Executor) interceptorChain.pluginAll(executor);,该方法会形成一个调用链。

          // 依次调用每个插件的plugin方法,如果该插件无需拦截target,则直接返回target
          public Object pluginAll(Object target) {
            for (Interceptor interceptor : interceptors) {
              target = interceptor.plugin(target);
            }
            return target;
          }
      
    Plugin

    插件代理的实现,这里应用了Java Dynamic Proxy

    	public class Plugin implements InvocationHandler {
    		// 需要被代理的实例
    		private Object target;
    		// 拦截器实例
    		private Interceptor interceptor;
    		// 拦截器需要拦截的方法摘要,这里Class键为Executor等上述的四个
    		// 值为需要被拦截的方法
    		private Map<Class<?>, Set<Method>> signatureMap;
    	
    		// 此类不能直接创建,需要通过静态方法wrap来创建代理类
    		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(value={@Signature(args={Void.class},method="query",type=Void.class)})*/
    			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.xml配置文件:

    <plugins>
    	<plugin interceptor="com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1"/>
    	<plugin interceptor="com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2"/>
    </plugins>
    

    插件实现

    @Intercepts(value={@Signature(args={MappedStatement.class,Object.class},method="update",type=Executor.class)})
    public class InterceptorDemo1 implements Interceptor {
    	private Logger logger = LoggerFactory.getLogger(InterceptorDemo1.class);
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		logger.debug(InterceptorDemo1.class.getName());
    		return invocation.proceed();
    	}
    	@Override
    	public Object plugin(Object target) {
    		return Plugin.wrap(target, this);
    	}
    	@Override
    	public void setProperties(Properties properties) {
    	}
    }
    
    
    @Intercepts(value={@Signature(args={MappedStatement.class,Object.class},method="update",type=Executor.class)})
    public class InterceptorDemo2 implements Interceptor {
    	private Logger logger = LoggerFactory.getLogger(InterceptorDemo2.class);
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		logger.debug(InterceptorDemo2.class.getName());
    		return invocation.proceed();
    	}
    	@Override
    	public Object plugin(Object target) {
    		return Plugin.wrap(target, this);
    	}
    	@Override
    	public void setProperties(Properties properties) {
    	}
    }
    

    main方法

    public static void main(String args[]) throws Exception {
    	
    	String resource = "mybatis.xml";
    	InputStream inputStream = Resources.getResourceAsStream(resource);
    	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    	
    	SqlSession session = sqlSessionFactory.openSession();
    	
    	ProductMapper productMapper = session.getMapper(ProductMapper.class);
    	
    	productMapper.updatePriceById("ANV01", BigDecimal.valueOf(6.99));
    }
    

    输出结果

    2016-07-09 16:59:00 [DEBUG]-[Thread: main]-[com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2.intercept()]: 
    com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2
    
    2016-07-09 16:59:00 [DEBUG]-[Thread: main]-[com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1.intercept()]: 
    com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1
    

    示例的调用序列图

    图片太大,可能需要另开一个页面单独看

  • 相关阅读:
    用call/apply实现bind
    FED1 修改 this 指向(中等)
    46. 全排列(中等)
    JavaScript 用七种方式教你判断一个变量是否为数组类型(转)
    179. 最大数(中等)
    125. 验证回文串(简单)
    执行git pull命令时出错
    前端修改滚动条样式
    js实现每日定时任务
    Vue实现验证码控件
  • 原文地址:https://www.cnblogs.com/jabnih/p/5656194.html
Copyright © 2020-2023  润新知