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

示例的调用序列图

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

posted @ 2016-07-09 17:15  Jabnih  阅读(1298)  评论(1编辑  收藏  举报