Mybatis插件的使用及其原理

一、Mybatis插件的使用

1、我们先来看一下mybatis中怎么声明一个插件

下面代码的是一个对用户信息加解密的插件

@Intercepts({ 
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class }) })
public class CommonInterceptor implements Interceptor {
    private static final Logger LOG = LoggerFactory.getLogger(CommonInterceptor.class);
    
    public Object intercept(Invocation invocation) throws Throwable {
        String methodName = invocation.getMethod().getName();
        Object parameter = invocation.getArgs()[1];//Object.class
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];//MappedStatement.class
        try {
            if ("query".equals(methodName) && (parameter instanceof BaseEntity)) {
                Field[] fields = parameter.getClass().getDeclaredFields();
                for (Field f : fields) {
                    if (f.isAnnotationPresent(EncryptAnnotation.class)) {
                        EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
                        switch (en.value().getCode()) {
                        case 0:
                            String name = f.getName();
                            String set_method = "set" + name.substring(0, 1).toUpperCase()
                                    + name.substring(1, name.length());
                            String get_method = "get" + name.substring(0, 1).toUpperCase()
                                    + name.substring(1, name.length());
                            Method g_method = parameter.getClass().getMethod(get_method, null);
                            Object value = g_method.invoke(parameter, null);
                            if (value != null && !StringUtil.isEmpty(value.toString())) {
                                String encrypt = DesUtil.getUtil().encrypt(value.toString());
                                Method s_method = parameter.getClass().getMethod(set_method, String.class);
                                s_method.invoke(parameter, encrypt);
                            }
                            break;
                        case 1:
                            name = f.getName();
                            set_method = "set" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
                            get_method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
                            g_method = parameter.getClass().getMethod(get_method, null);
                            value = g_method.invoke(parameter, null);
                            if (value != null && !StringUtil.isEmpty(value.toString())) {
                                String encrypt = Des4MysqlUtil.aes_encrypt(value.toString());
                                Method s_method = parameter.getClass().getMethod(set_method, String.class);
                                s_method.invoke(parameter, encrypt);
                            }
                            break;
                        default:
                            break;
                        }
                    }
                }
            }
            if ("update".equals(methodName) && (parameter instanceof BaseEntity)) {
                Field[] fields = parameter.getClass().getDeclaredFields();
                for (Field f : fields) {
                    if (f.isAnnotationPresent(EncryptAnnotation.class)) {
                        EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
                        switch (en.value().getCode()) {
                        case 0:
                            String name = f.getName();
                            String set_method = "set" + name.substring(0, 1).toUpperCase()
                                    + name.substring(1, name.length());
                            String get_method = "get" + name.substring(0, 1).toUpperCase()
                                    + name.substring(1, name.length());
                            Method g_method = parameter.getClass().getMethod(get_method, null);
                            Object value = g_method.invoke(parameter, null);
                            if (value != null) {
                                String encrypt = DesUtil.getUtil().encrypt(value.toString());
                                Method s_method = parameter.getClass().getMethod(set_method, String.class);
                                s_method.invoke(parameter, encrypt);
                            }
                            break;
                        case 1:
                            name = f.getName();
                            set_method = "set" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
                            get_method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
                            g_method = parameter.getClass().getMethod(get_method, null);
                            value = g_method.invoke(parameter, null);
                            if (value != null) {
                                String encrypt = Des4MysqlUtil.aes_encrypt(value.toString());
                                Method s_method = parameter.getClass().getMethod(set_method, String.class);
                                s_method.invoke(parameter, encrypt);
                            }
                            break;
                        default:
                            break;
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOG.error("数据拦截解析失败!");
        }


        Object returnValue = invocation.proceed();
        try {
            if (returnValue instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) returnValue;
                for (Object val : list) {
                    if (val == null) {
                        continue;
                    }
                    Field[] fields = val.getClass().getDeclaredFields();
                    for (Field f : fields) {
                        if (f.isAnnotationPresent(EncryptAnnotation.class)) {
                            EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
                            switch (en.value().getCode()) {
                            case 0:
                                String name = f.getName();
                                String set_method = "set" + name.substring(0, 1).toUpperCase()
                                        + name.substring(1, name.length());
                                String get_method = "get" + name.substring(0, 1).toUpperCase()
                                        + name.substring(1, name.length());
                                Method g_method = val.getClass().getMethod(get_method, null);
                                Object value = g_method.invoke(val, null);
                                if (value != null) {
                                    String encrypt = DesUtil.getUtil().decrypt(value.toString());
                                    Method s_method = val.getClass().getMethod(set_method, String.class);
                                    s_method.invoke(val, encrypt);
                                }
                                break;
                            case 1:
                                name = f.getName();
                                set_method = "set" + name.substring(0, 1).toUpperCase()
                                        + name.substring(1, name.length());
                                get_method = "get" + name.substring(0, 1).toUpperCase()
                                        + name.substring(1, name.length());
                                g_method = val.getClass().getMethod(get_method, null);
                                value = g_method.invoke(val, null);
                                if (value != null) {
                                    String encrypt = Des4MysqlUtil.aes_decrypt(value.toString());
                                    Method s_method = val.getClass().getMethod(set_method, String.class);
                                    s_method.invoke(val, encrypt);
                                }
                                break;
                            default:
                                break;
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOG.error("数据拦截解析失败!");
        }
        return returnValue;
    }


    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties arg0) {

    }
}

看了一下mybatis插件的简单使用,在类头上我们可以看见有签名注解,里面又有mybatis的四大对象信息;mybatis其实拦截的就是四大对象;什么是mybatis的四大对象:传动门

2、知道了四大对象,那么来看一下四大对象都有哪些方法可以被拦截

在这里插入图片描述

其中,

  • Executor:是执行SQL的全过程,包括组装参数,组装结果返回和执行SQL过程;
  • StatementHandler:最常使用,分页插件的实现就是拦截它里面的prepare;

3、我们在编写插件时候,需要对四大对象中的属性进行读取、修改,但是四大对象提供的public方法很少
这里mybatis提供了一些工具类,供我们使用如:SystemMetaObject、MetaObject

public final class SystemMetaObject {
  。。。。。。。。。。。
    //这个方法主要用于包装对象;
    public static MetaObject forObject(Object object) {
        return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
    }
    。。。。。。。
}
public class MetaObject {
    /**
     * 该方法主要用户获取对象属性值
     * @param name
     * @return
     */
    public Object getValue(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());
            return metaValue == SystemMetaObject.NULL_META_OBJECT ? null : metaValue.getValue(prop.getChildren());
        } else {
            return this.objectWrapper.get(prop);
        }
    }
    //该方法主要用于改变属性值
    public void setValue(String name, Object value) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                if (value == null && prop.getChildren() != null) {
                    return;
                }
                metaValue = this.objectWrapper.instantiatePropertyValue(name, prop, this.objectFactory);
            }

            metaValue.setValue(prop.getChildren(), value);
        } else {
            this.objectWrapper.set(prop, value);
        }


    }
}

二、插件的实现原理:

1、在mybatis中要使用插件,必须实现Interceptor接口,它是插件的骨架,也可以说是模板,这里使用的就是模板模式;
看一下Interceptor为我们提供的方法:

public interface Interceptor {
    /**
     * 它会直接覆盖原有对象的方法,它是插件的核心方法;里面有一个invocation对象,通过它可以反射调用原对象中的方法;
     * @param var1
     * @return
     * @throws Throwable
     */
    Object intercept(Invocation var1) throws Throwable;


    /**
     * var这里是被拦截对象,本方法的作用是给被拦截对象生产一个代理对象,并返回它;
     * mybatis提供了一个Plugin类,里面有个静态wrap方法可以生成代理对象;
     * @param var1
     * @return
     */
    Object plugin(Object var1);


    /**
     * 本方法会在插件初始化时候被调用,然后存入到配置中;
     * @param var1
     */
    void setProperties(Properties var1);
}

2、插件初始化是在mybatis初始化的时候进行的,由于插件可能会有很多个,这里各个插件被组织成一个责任链;
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator var2 = parent.getChildren().iterator();
        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            this.configuration.addInterceptor(interceptorInstance);
        }
    }
}
public void addInterceptor(Interceptor interceptor) {
    this.interceptorChain.addInterceptor(interceptor);
}
到这里我们可以看到插件被放到了interceptorChain,它主要负责组织责任链;
下面的方法就是责任链的具体实现;
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
    Interceptor interceptor;
    for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
        interceptor = (Interceptor)var2.next();
    }
    return target;
}

3、下面我们来看一下Plugin到底是何方神圣

public class Plugin implements InvocationHandler {
    。。。。。

    /**
     * 该方法主要用来封装代理对象;
     * @param target
     * @param interceptor
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }


    /**
     * 如果代理对象被调用,就会进入到这里的invoke
     * 如果存在签名的拦截方法,插件中的Intercept方法就会在这里被调用,然后可以使用Invocation进行插件逻辑处理;最后通过Invocation中的proceed方法,调用真实对象;
     * 否则,直接反射调用执行的方法;
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    。。。。。。。。。。
}

4、再来简单的看一下Invocation

public class Invocation {
    private Object target;
    private Method method;
    private Object[] args;


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


    public Object getTarget() {
        return this.target;
    }


    public Method getMethod() {
        return this.method;
    }


    public Object[] getArgs() {
        return this.args;
    }

	//在责任链最底层的时候,这里调用的才是真实对象的目标方法;
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return this.method.invoke(this.target, this.args);
    }
}

最后再通过一张时序图来看一下调用链路
在这里插入图片描述
总结:
1、在mybatis初始化的时候,使用Plugin包装生成代理对象,
2、当发起SQL执行的时候,会先执行Plugin中的invoke,
3、如果存在签名的拦截方法,会执行Interceptor中的interceptor,这里主要进行插件逻辑处理,如果符合预期逻辑,这里可以通过invocation中的proceed调用真实对象中的目标方法(当然这里如果有多个插件,会先调用责任链上层的代理方法);

图片来源:https://www.cnblogs.com/wuzhenzhao/p/11120848.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值