Mybatis拦截器设计原理1

拦截器的实现都是基于代理的设计模式实现的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并在方法之前执行拦截器代码。 首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做。下面我们就利用JDK的动态代理自己设计一个简单的拦截器。

将被拦截的目标接口:
 

public interface Target {  
    public void execute();  
} 

目标接口的一个实现类:

public class TargetImpl implements Target {  
    public void execute() {  
        System.out.println("Execute");  
    }  
}  

利用JDK的动态代理实现拦截器:

public class TargetProxy implements InvocationHandler {  
    private Object target;  
    private TargetProxy(Object target) {  
        this.target = target;  
    }  

    //生成一个目标对象的代理对象  
    public static Object bind(Object target) {  
        return Proxy.newProxyInstance(target.getClass() .getClassLoader(),   
                target.getClass().getInterfaces(),  
                       new TargetProxy(target));  
    }  

    //在执行目标对象方法前加上自己的拦截逻辑  
    @Override
    public Object invoke(Object proxy, Method method,  
                             Object[] args) throws Throwable {  
        System.out.println("Begin");  
        return method.invoke(target, args);  
    }  
}  

客户端调用:

public class Client {  
public static void main(String[] args) {  

    //没有被拦截之前  
    Target target = new TargetImpl();  
    target.execute(); //Execute  

    //拦截后  
    //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
    target = (Target)TargetProxy.bind(target);  
    target.execute();   
    //Begin  
    //Execute  
}  

上面的设计有几个非常明显的不足。首先,拦截逻辑被写死在代理对象中:

public Object invoke(Object proxy, Method method,  
                   Object[] args) throws Throwable {  
   //拦截逻辑被写死在代理对象中,导致客户端无法灵活的设置自己的拦截逻辑  
   System.out.println("Begin");  
   return method.invoke(target, args);  
}  

我们可以将拦截逻辑封装到一个类中,客户端在调用TargetProxy的bind()方法的时候将拦截逻辑一起当成参数传入: 
定义一个拦截逻辑封装的接口Interceptor,这才是真正的拦截器接口。

public interface Interceptor {  
    public void intercept();  
}  

那么我们的代理类就可以改成:

public class TargetProxy implements InvocationHandler {  

private Object target;  
private Interceptor interceptor;  

private TargetProxy(Object target, Interceptor interceptor) {  
    this.target = target;  
    this.interceptor = interceptor;  
}  

//将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。  
public static Object bind(Object target, Interceptor interceptor) {  
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),   
                       target.getClass().getInterfaces(),  
                       new TargetProxy(target, interceptor));  
}  

public Object invoke(Object proxy, Method method,   
                      Object[] args) throws Throwable {  
    //执行客户端定义的拦截逻辑  
    interceptor.intercept();  
    return method.invoke(target, args);  
}  

客户端调用代码:

/客户端可以定义各种拦截逻辑  
Interceptor interceptor = new Interceptor() {  
    public void intercept() {  
        System.out.println("Go Go Go!!!");  
    }  
};  
target = (Target)TargetProxy.bind(target, interceptor);  
target.execute();  

当然,很多时候我们的拦截器中需要判断当前方法需不需要拦截,或者获取当前被拦截的方法参数等。我们可以将被拦截的目标方法对象,参数信息传给拦截器。 
拦截器接口改成:

public interface Interceptor {  
    public void intercept(Method method, Object[] args);  
}  

在代理类执行的时候可以将当前方法和参数传给拦截,即TargetProxy的invoke方法改为:

扫描二维码关注公众号,回复: 3601045 查看本文章
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    interceptor.intercept(method, args);
    return method.invoke(target, args); 
}  

在Java设计原则中有一个叫做迪米特法则,大概的意思就是一个类对其他类知道得越少越好。其实就是减少类与类之间的耦合强度。这是从类成员的角度去思考的。 什么叫越少越好,什么是最少?最少就是不知道。 所以我们是不是可以这么理解,一个类所要了解的类应该越少越好呢?

对于上面的例子,Interceptor接口中需要使用intercept方法传过去Method类,那么也需要了解它。那么既然Interceptor都需要使用Method,还不如将Method的执行也放到Interceptor中,不再让TargetProxy类对其了解。Method的执行需要target对象,所以也需要将target对象给Interceptor。将Method,target和args封装到一个对象Invocation中,将Invocation传给Interceptor。

public interface Interceptor {  
    Object intercept(Invocation invocation)throws Throwable;
}  
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;
    }

    //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们,
    //除非这样的操作需要Interceptor的其他支持。然而这里不需要。
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

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

    public void setArgs(Object[] args) {
        this.args = args;
    }
}
public interface Target {  
    public void execute();  
} 
public class TargetProxy implements InvocationHandler {

    private Object target;
    private Interceptor interceptor;

    private TargetProxy(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。
    public static Object bind(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TargetProxy(target, interceptor));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return interceptor.intercept(new Invocation(target, method, args));
    }
}

public class Client {

    public static void main(String[] args) {
        /**
         * 相当于:
            Interceptor interceptor2 = new Interceptor() {
                @Override
                public Object intercept(Invocation invocation) throws Throwable {
                System.out.println("Go Go Go!!!");
                return invocation.proceed();
                }
        }   ; 
         */
        Interceptor interceptor = invocation -> {
            System.out.println("Go Go Go!!!");
            return invocation.proceed();
        };
        

        Target target = (Target) TargetProxy.bind(new TargetImpl(), interceptor);
        target.execute();

    }

}

根据迪米特法则来讲,其实客户端根本不需要了解TargetProxy类。将绑定逻辑放到拦截器内部,客户端只需要和拦截器打交道就可以了。

拦截器接口变为:

public interface Inteceptor {
    Object intercept(Invocation invocation)throws Throwable;
    Object register(Object target);
}

拦截器实现类:

public class InteceptorImpl implements Inteceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("Go Go Go!!!");
        return invocation.proceed();
    }

    @Override
    public Object register(Object target) {
        return TargetProxy.bind(target, this);
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        Inteceptor interceptor = new InteceptorImpl();
        Target target = (Target) interceptor.register(new TargetImpl());
        target.execute();
    }
}

好了,通过一系列调整,设计已经挺好了,不过上面的拦截器还是有一个很大的不足, 那就是拦截器会拦截目标对象的所有方法,然而这往往是不需要的,我们经常需要拦截器拦截目标对象的指定方法。 我们利用在Interceptor上加注解解决。

假设目标对象接口有多个方法:

public interface Target {
    public void execute1();
    public void execute2();
}
public class TargetImpl implements Target {

    @Override
    public void execute1() {
        System.out.println("Execute");
    }

    @Override
    public void execute2() {
        System.out.println("Execute");
    }
}

首先简单的定义一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MethodName {
    public String value();
}

在拦截器的实现类加上该注解:


public interface Interceptor {

    Object intercept(Invocation invocation)throws Throwable;

    Object register(Object target);
}  
@MethodName("execute1")
public class InteceptorImpl implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("Go Go Go!!!");
        return invocation.proceed();
    }

    @Override
    public Object register(Object target) {
        return TargetProxy.bind(target, this);
    }
}

在TargetProxy中判断interceptor的注解,看是否实行拦截: 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TargetProxy implements InvocationHandler {

    private Object target;
    private Interceptor interceptor;

    private TargetProxy(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。
    public static Object bind(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TargetProxy(target, interceptor));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);
        if (methodName == null) {
            throw new NullPointerException("xxxx");
        }

        //如果注解上的方法名和该方法名一样,才拦截
        String name = methodName.value();
        if (name.equals(method.getName())){
            //拦截方法后 加上自己的逻辑
            return interceptor.intercept(new Invocation(target, method, args));
        }

        return method.invoke(this.target, args);
    }
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

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

    //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们,
    //除非这样的操作需要Interceptor的其他支持。然而这里不需要。
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

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

    public void setArgs(Object[] args) {
        this.args = args;
    }
}

测试类:


public class Client {

    public static void main(String[] args) {

        Interceptor interceptor = new InteceptorImpl();
        Target target = (Target) interceptor.register(new TargetImpl());
        target.execute1();
        target.execute2();

    }

}

打印结果,只拦截了execute1()方法:

Go Go Go!!!
Execute1
Execute2

Process finished with exit code 0

OK,上面的一系列过程其实都是mybatis的拦截器代码结构,上面的TargetProxy其实就是mybatis的Plugin类。Interceptor和Invocation几乎一样。只是mybatis的Interceptor支持的注解更加复杂。

MyBatis的Plugin类,看到里面的两个方法,现在就不那么陌生了。


 

public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> 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;
  }

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

  ...
}

mybatis最终是通过将自定义的Interceptor配置到xml文件中:

<!-- 自定义处理Map返回结果的拦截器 -->  
 <plugins>  
     <plugin interceptor="com.chm.inteceptor.PageInterceptor">
 </plugins>  

猜你喜欢

转载自blog.csdn.net/luzhensmart/article/details/83105837