2.7.解析 plugins 元素 完成 Mybatis 插件的配置

优质
小牛编辑
135浏览
2023-12-01

点击查看typeAliases元素的用法

Mybtis的插件机制是一个很强大的功能,它允许我们在Mybatis运行期间切入到Mybatis内部执行我们想要做的一些事情。

mybatis比较火的分页插件page-helper其实就是基于Mybatis的插件功能实现的。

Mybatis的pluginsDTD定义如下:

<!ELEMENT plugins (plugin+)>

<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>

plugins标签下必须定义一个或多个plugin子节点。

plugin子节点有一个必填的属性interceptor,该属性指向一个实现了Interceptor的类,同时在plugin标签下面允许出现零个或多个property标签,用来配置该插件运行时依赖的属性。

比如:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

Interceptor是mybatis提供的插件接口定义:

/**
 * Mybatis 拦截器(插件)接口定义
 *
 * @author Clinton Begin
 */
public interface Interceptor {
    /**
     * 提供被拦截方法的代理实现,完成额外的业务处理
     *
     * @param invocation 代理
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 该方法用于处理被拦截对象,返回该对象本身或者为其生成一个代理对象。
     * <p>
     * 如果返回的是代理对象,我们可以为指定方法实现代理实现:{@link #intercept(Invocation)}
     *
     * @param target 被拦截的对象
     */
    Object plugin(Object target);

    /**
     * 配置该插件运行时依赖的属性
     */
    void setProperties(Properties properties);

}

它定义了三个方法:

  • intercept方法用于提供被拦截方法的代理实现,完成额外的业务处理。
  • plugin方法负责处理被拦截的对象,返回该对象本身或者为其生成一个代理对象。
  • setProperties方法用于配置该插件运行时依赖的属性。

其中intercept方法的入参是一个Invocation类型的对象,Invocation对象是mybatis提供的一个方法反射操作封装对象,他维护了特定的方法对象及其运行时依赖的参数。

Invocation定义了三个属性:

/**
 * 被代理的对象
 */
private final Object target;
/**
 * 被代理的方法
 */
private final Method method;
/**
 * 被代理方法的入参
 */
private final Object[] args;

这三个属性的赋值操作是在Invocation的构造方法中完成的:

public Invocation(Object target, Method method, Object[] args) {
     this.target = target;
     this.method = method;
     this.args = args;
 }

同时Invocation不仅对外提供了这三个属性的getter方法,还提供了一个用于完成方法反射操作的proceed方法:

/**
 * 通过反射完成方法调用
 *
 * @return 方法返回值
 */
public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
}

补充完基础知识之后,我们回到plugins元素的解析操作上来:

// 插件配置
pluginElement(root.evalNode("plugins"));

pluginElement方法中,XmlConfigBuilder会依次处理所有的plugin子节点,获取其interceptor的属性值,交给BaseBuilderresolveClass(String)方法获取具体的插件实现类,

并读取plugin子节点下的所有property属性配置生成Properties对象。

/**
 * 解析plugins节点
 *
 * @param parent plugins节点
 */
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 处理每一个interceptor
            String interceptor = child.getStringAttribute("interceptor");
            // 获取运行时属性配置
            Properties properties = child.getChildrenAsProperties();
            // 获取插件的实现
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 初始化插件配置
            interceptorInstance.setProperties(properties);
            // 注册插件,在插件职责链中注册一个新的插件实例
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

因为这里用的是resolveClass方法来获取插件的实现类,所以对于插件的配置,也是支持别名机制的。

之后通过反射得到具体的插件对象实例,并调用其setProperties方法配置其运行时需要使用的属性。

完成上诉操作之后,调用configurationaddInterceptor(Interceptor)方法完成保存插件的工作。

/**
 * 添加一个拦截器
 * @param interceptor 拦截器
 */
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

configurationaddInterceptor(Interceptor)方法中将添加插件的工作交给了interceptorChainaddInterceptor方法来完成。

interceptorChain属性在Configuration中被硬编码为InterceptorChain类型的实例:

/**
 * 插件拦截链(Mybatis插件)
 */
protected final InterceptorChain interceptorChain = new InterceptorChain();

InterceptorChain定义了一个List<Interceptor>类型的属性interceptors用于存放mybatis中所有的插件实现类。

/**
 * 所有插件
 */
private final List<Interceptor> interceptors = new ArrayList<>();

对外暴露的addInterceptor方法就是用来往interceptors集合中添加Interceptor实现的:

/**
 * 添加一个新的{@link Interceptor#}实例
 */
public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
}

除了addInterceptor方法之外,InterceptorChain还对外暴露了getInterceptorspluginAll两个方法。

其中getInterceptors用于获取当前所有Interceptor实例:

/**
 * 获取所有的Interceptor
 */
public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
}

pluginAll方法则负责依次调用所有的Interceptor实例的plugin方法对目标对象进行处理。

/**
 * 调用所有插件的{@link Interceptor#plugin(Object)}方法,包装指定对象
 */
public Object pluginAll(Object target) {
    // 多层拦截器对目标对象进行代理,会形成一条职责链
    for (Interceptor interceptor : interceptors) {
        // 处理被拦截对象,返回该对象本身或者为其生成一个代理对象。
        target = interceptor.plugin(target);
    }
    return target;
}

如果多个Interceptor对目标对象进行了代理,那么多层代理之间就会形成一条职责链。

职责链模式(责任链模式)也是一种常见的行为型设计模式:

  • 责任链模式是由多个对象构成的一条链式结构,该结构中的对象之间通过前者持有后者引用的方式贯穿链路.
  • 用户在发起对责任链的请求时,并不知道请求会被责任链中的哪一个对象处理,这样做用户只需要直接对责任链请求即可,而不需要针对每一个具体的对象发起请求.

责任链模式按照处理请求方式的不同可以分为纯责任链和不纯的责任链:

  • 纯责任链:

在处理请求过程中,每个节点对于请求的处理方式有且仅有处理和不处理两种,同时请求一定会被责任链中的某一个节点处理,当这个节点处理了请求之后,他一定不会将请求继续往下顺延,即,终止请求在链路中继续运行.

  • 不纯的责任链

在不纯的责任链模式内,一个请求可能会被多个节点处理,即每个节点都可能处理请求的一部分,其后续节点同样还有可能处理 请求的部分,甚至,还可能存在没有任何节点处理请求的情形.

理论上讲,由interceptors形成的代理对象责任链应该是不纯的责任链。