mybatis拦截器的原理?

1 个回答

  • 夜太
    明知不可为而为之就是我最大的诚意
    MyBatis中的拦截器主要用于在SQL执行的生命周期中进行干预。下面我们从源码角度详细分析MyBatis拦截器的实现原理。 #### 1. 拦截器接口 ```java /** * @autor Clinton Begin */ public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } } ``` 拦截器接口定义了三个方法,其中最核心的是`intercept`方法。`plugin`方法用于创建代理对象,而`setProperties`方法通常用于设置拦截器的属性。 #### 2. `plugin`方法与`Plugin`类 `plugin`方法默认实现使用`Plugin.wrap(target, this)`来包装目标对象。我们先来看`wrap`方法之前的一些关键点。 #### 3. `Plugin`类和`InvocationHandler`接口 `Plugin`类实现了`InvocationHandler`接口,使用JDK动态代理机制。先看`Plugin`类的构造函数: ```java public final 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; } } ``` 构造函数接受目标对象、拦截器和一个签名映射`signatureMap`。 #### 4. 获取签名映射 `wrap`方法中首先调用`getSignatureMap`方法获取签名映射: ```java private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>()); 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; } ``` 该方法解析拦截器上的`@Intercepts`注解,获取所有需要拦截的方法,并通过反射将其存储在`signatureMap`中。 #### 5. `wrap`方法 `wrap`方法创建动态代理: ```java 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; } ``` `wrap`方法首先获取目标对象的所有接口,并判断是否有需要拦截的方法。如果有,则创建代理对象。 #### 6. `invoke`方法 `invoke`方法是动态代理的核心: ```java @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); } } ``` `invoke`方法在代理对象调用方法时执行。它首先检查`signatureMap`,判断当前方法是否需要拦截。如果需要,则调用拦截器的`intercept`方法;否则,直接调用原方法。 ### 总结 MyBatis拦截器通过`Interceptor`接口定义拦截行为,利用`Plugin`类实现动态代理。通过解析`@Intercepts`注解,MyBatis可以灵活地在SQL执行的各个阶段插入自定义逻辑,实现对SQL执行过程的精细控制。
    编辑于 2024-05-28 23:49 •IP属地 北京
    举报
    评论
    收藏