我们说了一些经典的设计模式,我们来说一下MyBatis中涉及的职责链模式和代理模式,其和Servlet Filter,Spring Interceptor拦截器类似,都是为了框架的扩展性,都可以归纳于职责链模式

不过,相比较于Servlet Filter和Spring Interceptor,MyBatis Plugin的实现借助于了代理模式的职责链

MyBatis Plugin和Servlet Filter及Spring Interceptor的功能类似,都是不修改原有流程代码的情况下,执行一些额外的代码逻辑,唯一区别在于拦截的位置不一样的,Servlet Filter是对于Servlet的请求,Spring Interceptor主要拦截Spring的Bean方法,比如Controller类的方法等,而MyBatis Plugin是对SQL执行过程的拦截

假如,我们需要统计应用中每个SQL的执行耗时,如果使用MyBatis Plugin来实现的话,需要定义一个SqlCostTimeInterceptor类,实现MyBaits的Interceptor接口,并且,在MyBaits的全局配置文件中,声明这个拦截器

@Intercepts({

@Signature(type = StatementHandler.class, method = “query”, args = {Statement.class, ResultHandler.class}),

@Signature(type = StatementHandler.class, method = “update”, args = {Statement.class}),

@Signature(type = StatementHandler.class, method = “batch”, args = {Statement.class})})

public class SqlCostTimeInterceptor implements Interceptor {

private static Logger logger = LoggerFactory.getLogger(SqlCostTimeInterceptor.class);

@Override

public Object intercept(Invocation invocation) throws Throwable {

Object target = invocation.getTarget();

long startTime = System.currentTimeMillis();

StatementHandler statementHandler = (StatementHandler) target;

try {

return invocation.proceed();

} finally {

long costTime = System.currentTimeMillis() – startTime;

BoundSql boundSql = statementHandler.getBoundSql();

String sql = boundSql.getSql();

logger.info(“执行 SQL:[ {} ]执行耗时[ {} ms]”, sql, costTime);

}

}

@Override

public Object plugin(Object target) {

return Plugin.wrap(target, this);

}

@Override

public void setProperties(Properties properties) {

System.out.println(“插件配置的信息:”+properties);

}

}

<!– MyBatis全局配置文件:mybatis-config.xml –>

<plugins>

<plugin interceptor=”com.xzg.cd.a88.SqlCostTimeInterceptor”>

<property name=”someProperty” value=”100″/>

</plugin>

</plugins>

我们首先从@Intercepts这一部分看起,不管是拦截器,过滤器还是插件,都需要明确的标明拦截的方法,@Intercepts注解就是这个作用,其中@Intercepts注解可以嵌套多个@Signature,一个@Signature注解标明一个要拦截的目标方法,如果要拦截多个,可以编写多个@Signature注解

@Signature注解包含三个元素,type,method,args,type指明要拦截的类,method指明方法名字,args指明方法的参数列表,通过这三个元素,标明一个要拦截的方法

但是具有被拦截的方法,只有如下的几个

图片

这几个方法就是MyBatis中的执行流程涉及的方法,Mybatis通过Executor来执行SQL的,Executor会创建StatementHandler,ParameterHandler,,ResultSetHandler三个对象,并且,首先使用ParameterHandler设置了SQL中占位符参数,然后使用StatementHandler来执行SQL语句,最后使用ResultHandler来封装结果,所以,我们只需拦截这几个类的方法,就可以控制SQL执行流程了

利用了MyBatis Plugin,我们还能做很多事情,比如分库分表,自动分页,数据加密解密等

MyBatis Plugin的设计和实现中,设计了动态代理模式实现了职责链的,一般来说,职责链汇总都会存在两个部分,处理器Handler,处理器链HandlerChain两个部分,Servlet Filter中就是Filter和Filter Chain两个部分,对应到MyBaits中就是Interceptor和InterceptorChain,当然,在因为涉及了动态代理,所以还包含了一个非常重要的类,Plugin,用于动态生成被拦截的类的动态代理

集成了MyBaits的应用启动的时候,会读取全局配置文件,mybatis-config.xml,解析出Interceptor,并且注入到Configuration类的InterceptorChain对象中

对应的源码如下

public class XMLConfigBuilder extends BaseBuilder {

//解析配置

private void parseConfiguration(XNode root) {

try {

//省略部分代码…

pluginElement(root.evalNode(“plugins”)); //解析插件

} catch (Exception e) {

throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause: ” + e, e);

}

}

//解析插件

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类对象

Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();

//调用Interceptor上的setProperties()方法设置properties

interceptorInstance.setProperties(properties);

//下面这行代码会调用InterceptorChain.addInterceptor()方法

configuration.addInterceptor(interceptorInstance);

}

}

}

}

// Configuration类的addInterceptor()方法的代码如下所示

public void addInterceptor(Interceptor interceptor) {

interceptorChain.addInterceptor(interceptor);

}

我们来看看Interceptor和InterceptorChain两个类的代码,Interceptor的setProperties的方法是个单纯的Setter方法,为了方便配置去配置一些属性值,在Interceptor中,有着intercept()和plugin()函数,在InterceptorChain类中的pluginAll()函数,是最核心的三个函数,我们过会解释

public class Invocation {

private final Object target;

private final Method method;

private final Object[] args;

// 省略构造函数和getter方法…

public Object proceed() throws InvocationTargetException, IllegalAccessException {

return method.invoke(target, args);

}

}

public interface Interceptor {

Object intercept(Invocation invocation) throws Throwable;

Object plugin(Object target);

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);

}

public List<Interceptor> getInterceptors() {

return Collections.unmodifiableList(interceptors);

}

}

这样,解析完成配置文件后,所有的Interceptor都加载到了InterceptorChain之中,这些拦截器如何被触发的呢?

这个可以借鉴到我们执行SQL过程中,MyBatis会创建Executor,StatementHandler,ParameterHandler,ResultSetHandler这几个类的对象

创建的过程在Configuration类中

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

executorType = executorType == null ? defaultExecutorType : executorType;

executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

Executor executor;

if (ExecutorType.BATCH == executorType) {

executor = new BatchExecutor(this, transaction);

} else if (ExecutorType.REUSE == executorType) {

executor = new ReuseExecutor(this, transaction);

} else {

executor = new SimpleExecutor(this, transaction);

}

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);

parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);

return parameterHandler;

}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,

ResultHandler resultHandler, BoundSql boundSql) {

ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);

resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

return resultSetHandler;

}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

return statementHandler;

}

从上面的代码中,可以发现,在类的创建过程中调用了InterceptorChain的pluginAll()方法,这个方法中,会嵌套调用InterceptorChain上每个Interceptor的plugin()方法,plugin()是一个接口方法,TImeCost拦截器的具体实现是去调用了Plugin的wrap()方法实现的,wrap方法代码如下

// 借助Java InvocationHandler实现的动态代理模式

public class Plugin implements InvocationHandler {

private final Object target;

private final Interceptor interceptor;

private final 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;

}

// wrap()静态方法,用来生成target的动态代理,

// 动态代理对象=target对象+interceptor对象。

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;

}

// 调用target上的f()方法,会触发执行下面这个方法。

// 这个方法包含:执行interceptor的intecept()方法 + 执行target上f()方法。

@Override

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);

// issue #251

if (interceptsAnnotation == null) {

throw new PluginException(“No @Intercepts annotation was found in interceptor ” + interceptor.getClass().getName());

}

Signature[] sigs = interceptsAnnotation.value();

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()]);

}

}

plugin是借助了Java InvocationHandler实现的动态代理类,代理给target对象添加上Interceptor功能,要代理的target对象就是Executor,StatementHandler,ParameterHandler,ResultSetHandler这四个类的对象,wrap()这个静态方法就是生成target对象的动态代理对象的

当然,并不是无脑的生成target对象,而是只有@Signature注解要连接的类包含target对象的时候,才能生成.这个在wrap()函数的开头就会检验是否有对应的interface

Mybatis的职责链模式的实现方式比较特殊,会对同一个目标对象嵌套多次代理,从而做到职责链的拦截功能

public Object pluginAll(Object target) {

// 嵌套代理

for (Interceptor interceptor : interceptors) {

target = interceptor.plugin(target);

// 上面这行代码等于下面这行代码,target(代理对象)=target(目标对象)+interceptor(拦截器功能)

// target = Plugin.wrap(target, interceptor);

}

return target;

}

// MyBatis像下面这样创建target(Executor、StatementHandler、ParameterHandler、ResultSetHandler),相当于多次嵌套代理

Object target = interceptorChain.pluginAll(target);

当执行Executor,StatementHandler,ParameterHandler四个类上面的某个方法的时候,mybatis都会嵌套执行每层代理对象Plugin对象上的invoke()方法,然后invoke()方法会先执行代理对象的interceptor的intercept()函数,最后一步步走到原始的4个类的对象上

本章重点:

我们看了利用了职责链模式和动态代理模式实现的MyBatis Plugin,至此,职责链模式可以适用的三种场景,过滤器,拦截器,插件

职责链模式的实现一般包含处理器和处理器链两部分,对应出来的就是Servlet Filter和FilterChain,除此外,在mybatis中,还有重要的实现类,Plugin类,用于生成被拦截的对象的动态代理

课后思考

Servlet Filter,Spring Interceptor都可以用来拦截用户自定义的类的方法,但是Mybatis Plugin可以拦截的只有Executor,StatementHandler,ParameterHandler,ResultSetHandler这四个类的方法,为什么Mybatis不拦截用户自定义的类的方法呢?

发表评论

邮箱地址不会被公开。 必填项已用*标注