mybatis拦截器

2023-10-27



最近在用mybatis做项目,需要用到mybatis的拦截器功能,就顺便把mybatis的拦截器源码大致的看了一遍,为了温故而知新,在此就按照自己的理解由浅入深的理解一下它的设计。 
和大家分享一下,不足和谬误之处欢迎交流。直接入正题。 
首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做。拦截器的实现都是基于代理的设计模式设计的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并拦截执行拦截器代码。 
那么我们就用JDK的动态代理设计一个简单的拦截器: 

将被拦截的目标接口: 
Java代码   收藏代码
  1. public interface Target {  
  2.     public void execute();  
  3. }  

目标接口的一个实现类: 
Java代码   收藏代码
  1.     public class TargetImpl implements Target {  
  2.     public void execute() {  
  3.         System.out.println("Execute");  
  4.     }  
  5. }  

利用JDK的动态代理实现拦截器: 
Java代码   收藏代码
  1. public class TargetProxy implements InvocationHandler {  
  2.     private Object target;  
  3.     private TargetProxy(Object target) {  
  4.         this.target = target;  
  5.     }  
  6.       
  7.     //生成一个目标对象的代理对象  
  8.     public static Object bind(Object target) {  
  9.         return Proxy.newProxyInstance(target.getClass() .getClassLoader(),   
  10.                 target.getClass().getInterfaces(),  
  11.                        new TargetProxy(target));  
  12.     }  
  13.       
  14.     //在执行目标对象方法前加上自己的拦截逻辑  
  15.     public Object invoke(Object proxy, Method method,  
  16.                              Object[] args) throws Throwable {  
  17.         System.out.println("Begin");  
  18.         return method.invoke(target, args);  
  19.     }  
  20. }  

客户端调用: 
Java代码   收藏代码
  1. public class Client {  
  2. public static void main(String[] args) {  
  3.   
  4.     //没有被拦截之前  
  5.     Target target = new TargetImpl();  
  6.     target.execute(); //Execute  
  7.       
  8.     //拦截后  
  9.     target = (Target)TargetProxy.bind(target);  
  10.     target.execute();   
  11.     //Begin  
  12.     //Execute  
  13. }  

上面的设计有几个非常明显的不足,首先说第一个,拦截逻辑被写死在代理对象中: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,  
  2.                            Object[] args) throws Throwable {  
  3.         //拦截逻辑被写死在代理对象中,导致客户端无法灵活的设置自己的拦截逻辑  
  4.         System.out.println("Begin");  
  5.        return method.invoke(target, args);  
  6.     }  

我们可以将拦截逻辑封装到一个类中,客户端在调用TargetProxy的bind()方法的时候将拦截逻辑一起当成参数传入: 
定义一个拦截逻辑封装的接口Interceptor,这才是真正的拦截器接口。 
Java代码   收藏代码
  1.     public interface Interceptor {  
  2.     public void intercept();  
  3. }  

那么我们的代理类就可以改成: 
Java代码   收藏代码
  1. public class TargetProxy implements InvocationHandler {  
  2.   
  3. private Object target;  
  4. private Interceptor interceptor;  
  5.   
  6. private TargetProxy(Object target, Interceptor interceptor) {  
  7.     this.target = target;  
  8.     this.interceptor = interceptor;  
  9. }  
  10.   
  11. //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。  
  12. public static Object bind(Object target, Interceptor interceptor) {  
  13.     return Proxy.newProxyInstance(target.getClass().getClassLoader(),   
  14.                        target.getClass().getInterfaces(),  
  15.                        new TargetProxy(target, interceptor));  
  16. }  
  17.   
  18. public Object invoke(Object proxy, Method method,   
  19.                       Object[] args) throws Throwable {  
  20.     //执行客户端定义的拦截逻辑  
  21.     interceptor.intercept();  
  22.     return method.invoke(target, args);  
  23. }  

客户端调用代码: 
Java代码   收藏代码
  1. //客户端可以定义各种拦截逻辑  
  2. Interceptor interceptor = new Interceptor() {  
  3.     public void intercept() {  
  4.         System.out.println("Go Go Go!!!");  
  5.     }  
  6. };  
  7. target = (Target)TargetProxy.bind(target, interceptor);  
  8. target.execute();  

当然,很多时候我们的拦截器中需要判断当前方法需不需要拦截,或者获取当前被拦截的方法参数等。我们可以将被拦截的目标方法对象,参数信息传给拦截器。 
拦截器接口改成: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2.     public void intercept(Method method, Object[] args);  
  3. }  

在代理类执行的时候可以将当前方法和参数传给拦截,即TargetProxy的invoke方法改为: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  2.     interceptor.intercept(method, args);  
  3.     return method.invoke(target, args);  
  4. }  

在Java设计原则中有一个叫做迪米特法则,大概的意思就是一个类对其他类知道得越少越好。其实就是减少类与类之间的耦合强度。这是从类成员的角度去思考的。 
什么叫越少越好,什么是最少?最少就是不知道。 
所以我们是不是可以这么理解,一个类所要了解的类应该越少越好呢? 
当然,这只是从类的角度去诠释了迪米特法则。 
甚至可以反过来思考,一个类被其他类了解得越少越好。 
A类只让B类了解总要强于A类让B,C,D类都去了解。 

举个例子: 
我们的TargetProxy类中需要了解的类有哪些呢? 
1. Object target 不需要了解,因为在TargetProxy中,target都被作为参数传给了别的类使用,自己不需要了解它。 
2. Interceptor interceptor 需要了解,需要调用其intercept方法。 
3. 同样,Proxy需要了解。 
4. Method method 参数需要了解,需要调用其invoke方法。 
同样,如果interceptor接口中需要使用intercept方法传过去Method类,那么也需要了解它。那么既然Interceptor都需要使用Method,还不如将Method的执行也放到Interceptor中,不再让TargetProxy类对其了解。Method的执行需要target对象,所以也需要将target对象给Interceptor。将Method,target和args封装到一个对象Invocation中,将Invocation传给Interceptor。 
Invocation: 
Java代码   收藏代码
  1. public class Invocation {  
  2.     private Object target;  
  3.     private Method method;  
  4.     private Object[] args;  
  5.       
  6.     public Invocation(Object target, Method method, Object[] args) {  
  7.         this.target = target;  
  8.         this.method = method;  
  9.         this.args = args;  
  10.     }  
  11.       
  12.     //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们,  
  13.     //除非这样的操作需要Interceptor的其他支持。然而这儿不需要。  
  14.     public Object proceed() throws InvocationTargetException, IllegalAccessException {  
  15.         return method.invoke(target, args);  
  16.     }  
  17.         
  18.     public Object getTarget() {  
  19.         return target;  
  20.     }  
  21.     public void setTarget(Object target) {  
  22.         this.target = target;  
  23.     }  
  24.     public Method getMethod() {  
  25.         return method;  
  26.     }  
  27.     public void setMethod(Method method) {  
  28.         this.method = method;  
  29.     }  
  30.     public Object[] getArgs() {  
  31.         return args;  
  32.     }  
  33.     public void setArgs(Object[] args) {  
  34.         this.args = args;  
  35.     }  
  36. }  

Interceptor就变成: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2.     public Object intercept(Invocation invocation)throws Throwable ;  
  3. }  

TargetProxy的invoke方法就变成: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,   
  2.                           Object[] args) throws Throwable {  
  3.     return interceptor.intercept(new Invocation(target,   
  4.                                                    method, args));  
  5. }  

那么就每一个Interceptor拦截器实现都需要最后执行Invocation的proceed方法并返回。 
客户端调用: 
Java代码   收藏代码
  1. Interceptor interceptor = new Interceptor() {  
  2.     public Object intercept(Invocation invocation)  throws Throwable {  
  3.         System.out.println("Go Go Go!!!");  
  4.         return invocation.proceed();  
  5.     }  
  6. };  

好了,通过一系列调整,设计已经挺好了,不过上面的拦截器还是有一个很大的不足, 
那就是拦截器会拦截目标对象的所有方法,然而这往往是不需要的,我们经常需要拦截器 
拦截目标对象的指定方法。 
假设目标对象接口有多个方法: 
Java代码   收藏代码
  1. public interface Target {  
  2.     public void execute1();  
  3.     public void execute2();  
  4. }  

利用在Interceptor上加注解解决。 
首先简单的定义一个注解: 
Java代码   收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.TYPE)  
  3. public @interface MethodName {  
  4.     public String value();  
  5. }  

在拦截器的实现类加上该注解: 
Java代码   收藏代码
  1. @MethodName("execute1")  
  2. public class InterceptorImpl implements Interceptor {...}  

在TargetProxy中判断interceptor的注解,看是否实行拦截: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,  
  2.                          Object[] args) throws Throwable {  
  3.         MethodName methodName =   
  4.          this.interceptor.getClass().getAnnotation(MethodName.class);  
  5.         if (ObjectUtils.isNull(methodName))  
  6.             throw new NullPointerException("xxxx");  
  7.           
  8.         //如果注解上的方法名和该方法名一样,才拦截  
  9.         String name = methodName.value();  
  10.         if (name.equals(method.getName()))  
  11.             return interceptor.intercept(new Invocation(target,    method, args));  
  12.           
  13.         return method.invoke(this.target, args);  
  14. }  

最后客户端调用: 
Java代码   收藏代码
  1. Target target = new TargetImpl();  
  2. Interceptor interceptor = new InterceptorImpl();  
  3. target = (Target)TargetProxy.bind(target, interceptor);  
  4. target.execute();  

从客户端调用代码可以看出,客户端首先需要创建一个目标对象和拦截器,然后将拦截器和目标对象绑定并获取代理对象,最后执行代理对象的execute()方法。 
根据迪米特法则来讲,其实客户端根本不需要了解TargetProxy类。将绑定逻辑放到拦截器内部,客户端只需要和拦截器打交道就可以了。 
即拦截器接口变为: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2. public Object intercept(Invocation invocation)  throws Throwable ;  
  3. public Object register(Object target);  

拦截器实现: 
Java代码   收藏代码
  1. @MethodName("execute1")  
  2. public class InterceptorImpl implements Interceptor {  
  3.       
  4.     public Object intercept(Invocation invocation)throws Throwable {  
  5.         System.out.println("Go Go Go!!!");  
  6.         return invocation.proceed();  
  7.     }  
  8.       
  9.     public Object register(Object target) {  
  10.         return TargetProxy.bind(target, this);  
  11.     }  
  12. }  

客户端调用: 
Java代码   收藏代码
  1. Target target = new TargetImpl();  
  2. Interceptor interceptor = new InterceptorImpl();  
  3.   
  4. target = (Target)interceptor.register(target);  
  5. target.execute1();  



OK,上面的一系列过程其实都是mybatis的拦截器代码结构,我只是学习了之后用最简单的方法理解一遍罢了。 
上面的TargetProxy其实就是mybatis的Plug类。Interceptor和Invocation几乎一样。只是mybatis的Interceptor支持的注解 
更加复杂。 
mybatis最终是通过将自定义的Interceptor配置到xml文件中: 
Xml代码   收藏代码
  1. <!-- 自定义处理Map返回结果的拦截器 -->  
  2.  <plugins>  
  3.      <plugin interceptor="com.gs.cvoud.dao.interceptor.MapInterceptor" />  
  4.  </plugins>  

通过读取配置文件中的Interceptor,通过反射构造其实例,将所有的Interceptor保存到InterceptorChain中。 
Java代码   收藏代码
  1. public class InterceptorChain {  
  2.   
  3.   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();  
  4.   
  5.   public Object pluginAll(Object target) {  
  6.     for (Interceptor interceptor : interceptors) {  
  7.       target = interceptor.plugin(target);  
  8.     }  
  9.     return target;  
  10.   }  
  11.   
  12.   public void addInterceptor(Interceptor interceptor) {  
  13.     interceptors.add(interceptor);  
  14.   }  
  15.     
  16.   public List<Interceptor> getInterceptors() {  
  17.     return Collections.unmodifiableList(interceptors);  
  18.   }  
  19.   
  20. }  

mybatis的拦截器只能代理指定的四个类:ParameterHandler、ResultSetHandler、StatementHandler以及Executor。 
这是在mybatis的Configuration中写死的,例如(其他三个类似): 
Java代码   收藏代码
  1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
  2.     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);  
  3.       
  4.     //将配置文件中读取的所有的Interceptor都注册到ParameterHandler中,最后通过每个Interceptor的注解判断是否需要拦截该ParameterHandler的某个方法。  
  5.     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
  6.     return parameterHandler;  
  7. }  

所以我们可以自定义mybatis的插件(拦截器)修改mybatis的很多默认行为, 
例如, 
通过拦截ResultSetHandler修改接口返回类型; 
通过拦截StatementHandler修改mybatis框架的分页机制; 
通过拦截Executor查看mybatis的sql执行过程等等。
最近在用mybatis做项目,需要用到mybatis的拦截器功能,就顺便把mybatis的拦截器源码大致的看了一遍,为了温故而知新,在此就按照自己的理解由浅入深的理解一下它的设计。 
和大家分享一下,不足和谬误之处欢迎交流。直接入正题。 
首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做。拦截器的实现都是基于代理的设计模式设计的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并拦截执行拦截器代码。 
那么我们就用JDK的动态代理设计一个简单的拦截器: 

将被拦截的目标接口: 
Java代码   收藏代码
  1. public interface Target {  
  2.     public void execute();  
  3. }  

目标接口的一个实现类: 
Java代码   收藏代码
  1.     public class TargetImpl implements Target {  
  2.     public void execute() {  
  3.         System.out.println("Execute");  
  4.     }  
  5. }  

利用JDK的动态代理实现拦截器: 
Java代码   收藏代码
  1. public class TargetProxy implements InvocationHandler {  
  2.     private Object target;  
  3.     private TargetProxy(Object target) {  
  4.         this.target = target;  
  5.     }  
  6.       
  7.     //生成一个目标对象的代理对象  
  8.     public static Object bind(Object target) {  
  9.         return Proxy.newProxyInstance(target.getClass() .getClassLoader(),   
  10.                 target.getClass().getInterfaces(),  
  11.                        new TargetProxy(target));  
  12.     }  
  13.       
  14.     //在执行目标对象方法前加上自己的拦截逻辑  
  15.     public Object invoke(Object proxy, Method method,  
  16.                              Object[] args) throws Throwable {  
  17.         System.out.println("Begin");  
  18.         return method.invoke(target, args);  
  19.     }  
  20. }  

客户端调用: 
Java代码   收藏代码
  1. public class Client {  
  2. public static void main(String[] args) {  
  3.   
  4.     //没有被拦截之前  
  5.     Target target = new TargetImpl();  
  6.     target.execute(); //Execute  
  7.       
  8.     //拦截后  
  9.     target = (Target)TargetProxy.bind(target);  
  10.     target.execute();   
  11.     //Begin  
  12.     //Execute  
  13. }  

上面的设计有几个非常明显的不足,首先说第一个,拦截逻辑被写死在代理对象中: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,  
  2.                            Object[] args) throws Throwable {  
  3.         //拦截逻辑被写死在代理对象中,导致客户端无法灵活的设置自己的拦截逻辑  
  4.         System.out.println("Begin");  
  5.        return method.invoke(target, args);  
  6.     }  

我们可以将拦截逻辑封装到一个类中,客户端在调用TargetProxy的bind()方法的时候将拦截逻辑一起当成参数传入: 
定义一个拦截逻辑封装的接口Interceptor,这才是真正的拦截器接口。 
Java代码   收藏代码
  1.     public interface Interceptor {  
  2.     public void intercept();  
  3. }  

那么我们的代理类就可以改成: 
Java代码   收藏代码
  1. public class TargetProxy implements InvocationHandler {  
  2.   
  3. private Object target;  
  4. private Interceptor interceptor;  
  5.   
  6. private TargetProxy(Object target, Interceptor interceptor) {  
  7.     this.target = target;  
  8.     this.interceptor = interceptor;  
  9. }  
  10.   
  11. //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。  
  12. public static Object bind(Object target, Interceptor interceptor) {  
  13.     return Proxy.newProxyInstance(target.getClass().getClassLoader(),   
  14.                        target.getClass().getInterfaces(),  
  15.                        new TargetProxy(target, interceptor));  
  16. }  
  17.   
  18. public Object invoke(Object proxy, Method method,   
  19.                       Object[] args) throws Throwable {  
  20.     //执行客户端定义的拦截逻辑  
  21.     interceptor.intercept();  
  22.     return method.invoke(target, args);  
  23. }  

客户端调用代码: 
Java代码   收藏代码
  1. //客户端可以定义各种拦截逻辑  
  2. Interceptor interceptor = new Interceptor() {  
  3.     public void intercept() {  
  4.         System.out.println("Go Go Go!!!");  
  5.     }  
  6. };  
  7. target = (Target)TargetProxy.bind(target, interceptor);  
  8. target.execute();  

当然,很多时候我们的拦截器中需要判断当前方法需不需要拦截,或者获取当前被拦截的方法参数等。我们可以将被拦截的目标方法对象,参数信息传给拦截器。 
拦截器接口改成: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2.     public void intercept(Method method, Object[] args);  
  3. }  

在代理类执行的时候可以将当前方法和参数传给拦截,即TargetProxy的invoke方法改为: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  2.     interceptor.intercept(method, args);  
  3.     return method.invoke(target, args);  
  4. }  

在Java设计原则中有一个叫做迪米特法则,大概的意思就是一个类对其他类知道得越少越好。其实就是减少类与类之间的耦合强度。这是从类成员的角度去思考的。 
什么叫越少越好,什么是最少?最少就是不知道。 
所以我们是不是可以这么理解,一个类所要了解的类应该越少越好呢? 
当然,这只是从类的角度去诠释了迪米特法则。 
甚至可以反过来思考,一个类被其他类了解得越少越好。 
A类只让B类了解总要强于A类让B,C,D类都去了解。 

举个例子: 
我们的TargetProxy类中需要了解的类有哪些呢? 
1. Object target 不需要了解,因为在TargetProxy中,target都被作为参数传给了别的类使用,自己不需要了解它。 
2. Interceptor interceptor 需要了解,需要调用其intercept方法。 
3. 同样,Proxy需要了解。 
4. Method method 参数需要了解,需要调用其invoke方法。 
同样,如果interceptor接口中需要使用intercept方法传过去Method类,那么也需要了解它。那么既然Interceptor都需要使用Method,还不如将Method的执行也放到Interceptor中,不再让TargetProxy类对其了解。Method的执行需要target对象,所以也需要将target对象给Interceptor。将Method,target和args封装到一个对象Invocation中,将Invocation传给Interceptor。 
Invocation: 
Java代码   收藏代码
  1. public class Invocation {  
  2.     private Object target;  
  3.     private Method method;  
  4.     private Object[] args;  
  5.       
  6.     public Invocation(Object target, Method method, Object[] args) {  
  7.         this.target = target;  
  8.         this.method = method;  
  9.         this.args = args;  
  10.     }  
  11.       
  12.     //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们,  
  13.     //除非这样的操作需要Interceptor的其他支持。然而这儿不需要。  
  14.     public Object proceed() throws InvocationTargetException, IllegalAccessException {  
  15.         return method.invoke(target, args);  
  16.     }  
  17.         
  18.     public Object getTarget() {  
  19.         return target;  
  20.     }  
  21.     public void setTarget(Object target) {  
  22.         this.target = target;  
  23.     }  
  24.     public Method getMethod() {  
  25.         return method;  
  26.     }  
  27.     public void setMethod(Method method) {  
  28.         this.method = method;  
  29.     }  
  30.     public Object[] getArgs() {  
  31.         return args;  
  32.     }  
  33.     public void setArgs(Object[] args) {  
  34.         this.args = args;  
  35.     }  
  36. }  

Interceptor就变成: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2.     public Object intercept(Invocation invocation)throws Throwable ;  
  3. }  

TargetProxy的invoke方法就变成: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,   
  2.                           Object[] args) throws Throwable {  
  3.     return interceptor.intercept(new Invocation(target,   
  4.                                                    method, args));  
  5. }  

那么就每一个Interceptor拦截器实现都需要最后执行Invocation的proceed方法并返回。 
客户端调用: 
Java代码   收藏代码
  1. Interceptor interceptor = new Interceptor() {  
  2.     public Object intercept(Invocation invocation)  throws Throwable {  
  3.         System.out.println("Go Go Go!!!");  
  4.         return invocation.proceed();  
  5.     }  
  6. };  

好了,通过一系列调整,设计已经挺好了,不过上面的拦截器还是有一个很大的不足, 
那就是拦截器会拦截目标对象的所有方法,然而这往往是不需要的,我们经常需要拦截器 
拦截目标对象的指定方法。 
假设目标对象接口有多个方法: 
Java代码   收藏代码
  1. public interface Target {  
  2.     public void execute1();  
  3.     public void execute2();  
  4. }  

利用在Interceptor上加注解解决。 
首先简单的定义一个注解: 
Java代码   收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.TYPE)  
  3. public @interface MethodName {  
  4.     public String value();  
  5. }  

在拦截器的实现类加上该注解: 
Java代码   收藏代码
  1. @MethodName("execute1")  
  2. public class InterceptorImpl implements Interceptor {...}  

在TargetProxy中判断interceptor的注解,看是否实行拦截: 
Java代码   收藏代码
  1. public Object invoke(Object proxy, Method method,  
  2.                          Object[] args) throws Throwable {  
  3.         MethodName methodName =   
  4.          this.interceptor.getClass().getAnnotation(MethodName.class);  
  5.         if (ObjectUtils.isNull(methodName))  
  6.             throw new NullPointerException("xxxx");  
  7.           
  8.         //如果注解上的方法名和该方法名一样,才拦截  
  9.         String name = methodName.value();  
  10.         if (name.equals(method.getName()))  
  11.             return interceptor.intercept(new Invocation(target,    method, args));  
  12.           
  13.         return method.invoke(this.target, args);  
  14. }  

最后客户端调用: 
Java代码   收藏代码
  1. Target target = new TargetImpl();  
  2. Interceptor interceptor = new InterceptorImpl();  
  3. target = (Target)TargetProxy.bind(target, interceptor);  
  4. target.execute();  

从客户端调用代码可以看出,客户端首先需要创建一个目标对象和拦截器,然后将拦截器和目标对象绑定并获取代理对象,最后执行代理对象的execute()方法。 
根据迪米特法则来讲,其实客户端根本不需要了解TargetProxy类。将绑定逻辑放到拦截器内部,客户端只需要和拦截器打交道就可以了。 
即拦截器接口变为: 
Java代码   收藏代码
  1. public interface Interceptor {  
  2. public Object intercept(Invocation invocation)  throws Throwable ;  
  3. public Object register(Object target);  

拦截器实现: 
Java代码   收藏代码
  1. @MethodName("execute1")  
  2. public class InterceptorImpl implements Interceptor {  
  3.       
  4.     public Object intercept(Invocation invocation)throws Throwable {  
  5.         System.out.println("Go Go Go!!!");  
  6.         return invocation.proceed();  
  7.     }  
  8.       
  9.     public Object register(Object target) {  
  10.         return TargetProxy.bind(target, this);  
  11.     }  
  12. }  

客户端调用: 
Java代码   收藏代码
  1. Target target = new TargetImpl();  
  2. Interceptor interceptor = new InterceptorImpl();  
  3.   
  4. target = (Target)interceptor.register(target);  
  5. target.execute1();  



OK,上面的一系列过程其实都是mybatis的拦截器代码结构,我只是学习了之后用最简单的方法理解一遍罢了。 
上面的TargetProxy其实就是mybatis的Plug类。Interceptor和Invocation几乎一样。只是mybatis的Interceptor支持的注解 
更加复杂。 
mybatis最终是通过将自定义的Interceptor配置到xml文件中: 
Xml代码   收藏代码
  1. <!-- 自定义处理Map返回结果的拦截器 -->  
  2.  <plugins>  
  3.      <plugin interceptor="com.gs.cvoud.dao.interceptor.MapInterceptor" />  
  4.  </plugins>  

通过读取配置文件中的Interceptor,通过反射构造其实例,将所有的Interceptor保存到InterceptorChain中。 
Java代码   收藏代码
  1. public class InterceptorChain {  
  2.   
  3.   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();  
  4.   
  5.   public Object pluginAll(Object target) {  
  6.     for (Interceptor interceptor : interceptors) {  
  7.       target = interceptor.plugin(target);  
  8.     }  
  9.     return target;  
  10.   }  
  11.   
  12.   public void addInterceptor(Interceptor interceptor) {  
  13.     interceptors.add(interceptor);  
  14.   }  
  15.     
  16.   public List<Interceptor> getInterceptors() {  
  17.     return Collections.unmodifiableList(interceptors);  
  18.   }  
  19.   
  20. }  

mybatis的拦截器只能代理指定的四个类:ParameterHandler、ResultSetHandler、StatementHandler以及Executor。 
这是在mybatis的Configuration中写死的,例如(其他三个类似): 
Java代码   收藏代码
  1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
  2.     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);  
  3.       
  4.     //将配置文件中读取的所有的Interceptor都注册到ParameterHandler中,最后通过每个Interceptor的注解判断是否需要拦截该ParameterHandler的某个方法。  
  5.     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
  6.     return parameterHandler;  
  7. }  

所以我们可以自定义mybatis的插件(拦截器)修改mybatis的很多默认行为, 
例如, 
通过拦截ResultSetHandler修改接口返回类型; 
通过拦截StatementHandler修改mybatis框架的分页机制; 
通过拦截Executor查看mybatis的sql执行过程等等。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mybatis拦截器 的相关文章

  • JSP和JavaBean

    8 JSP 8 1 什么是JSP Java Servlet Pages java服务器端页面 也和Servlet一样 用于实现动态Web技术 最大特点 写JSP就像是写HTML 区别 HTML只给用户提供静态的数据 JSP页面中可以嵌入Ja
  • Filter(过滤器)常见应用

    javaweb学习总结 四十六 Filter 过滤器 常见应用 一 统一全站字符编码 通过配置参数charset指明使用何种字符编码 以处理Html Form请求参数的中文问题 1 package me gacl web filter 2
  • javaWeb数据库连接池,过滤器和监听器

    数据库连接池 JDBC 1 什么是数据库连接池 是一个数据库的工具 能够分配 管理和释放数据库连接 它允许应用程序重复使用一个现有的数据库连接 而不是再重新建立一个 常见数据库连接池 C3P0 是一个开放源代码的JDBC连接池 它在lib目
  • 详解Java四大作用域

    文章目录 四大作用域 1 pageContext 1 1定义 2 request 详解 2 1定义 2 2 request域对象的使用 2 3生命周期 2 4 获取请求头数据 3 session 详解 3 1定义 3 2 获取session
  • Idea的 Cannot resolve method ‘getAttribute(java.lang.String)‘问题解决

    问题 写javaweb jsp时使用application getAttribute出现报错 Cannot resolve method getAttribute java lang String 解决方法 第一步 File gt Proj
  • servlet传json数据给前端

    重点 1 json数据的发送 后 2 json数据的提取 前 例如 servlet返回从session提取的当前用户信息给前端使用 1 前端不带数据发送axios给后端请求数据 用get axios method get url http
  • IDEA(最新版)导入Myeclipse/eclipse的web项目并运行(全) Windows或者Mac系统

    一 前言 最近在做毕业设计 没想到现在的大学中还是使用的Myeclipse比较多 这个工具逐渐被IDEA工具所代替 因为IDEA的性能和使用更加优秀 或者我们在工作中遇到Myeclipse项目导入IDEA中的情形 怎么将Myeclipse开
  • Springboot 实现发送邮件功能,使用QQ邮箱

    引入依赖
  • javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint

    java 验证出现如下错误 javax validation UnexpectedTypeException HV000030 No validator could be found for constraint 错误原因 Java实体类中
  • 理解Servlet和Servlet容器、Web服务器等概念

    转载自http blog csdn net iAm333 之前在开源中国看到一篇文章 初学 Java Web 开发 请远离各种框架 从 Servlet 开发 觉得很不错 想到自己之前一直对各种框架执迷不悟 顿感惭愧 于是 看了孙鑫的 Ser
  • Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】

    Goby社区第 21 篇技术分享文章 全文共 6700 字 预计阅读时间 17 分钟 自我介绍 大家好 我是 su18 无论是个人博客还是社区 已经很久没写技术文章了 原因有很多 时间 精力 心态等等 但在开源社区也算比较活跃 由于工作需要
  • spring的jdbcTemplate批量添加数据,单条数据中文正常,多数据第二条数据开始中文乱码

    今天用spring的jdbcTemplate org springframework jdbc core JdbcTemplate 批量添加数据 方法是jdbcTemplate bathUpdate 然后遇到一个很神奇的问题 批量添加的数据
  • shell脚本中$#、$*、$@、$?、$0-n等含义一次性搞明白!!!

    一 Shell脚本变量的含义 1 表示执行脚本传入参数的个数 2 表示执行脚本传入参数的列表 不包括 0 3 表示进程的id Shell本身的PID ProcessID 即脚本运行的当前 进程ID号 4 Shell最后运行的后台Proces
  • IntelliJ Idea 常用快捷键 列表(实战终极总结!!!!)

    自动代码 常用的有fori sout psvm Tab即可生成循环 System out main方法等boilerplate样板代码 例如要输入for User user users 只需输入user for Tab 再比如 要输入Dat
  • Tomcat——IDEA & Eclipse部署Web项目

    目录 IDEA部署Web项目 IDEA配置Tomcat 部署web项目 IDEA与tomcat的相关配置 Eclipse部署Web项目 Eclipse配置Tomcat 部署web项目 IDEA部署Web项目 跳转到目录 一 IDEA配置To
  • Servlet3.0基础

    一 要求 1 MyEclipse10 0或以上版本 2 发布到Tomcat7 0或以上版本 二 步骤 1 创建javaEE6 0应用 三 概述 注解代替web xml配置文件 异步处理 对上传的支持 四 注解代替配置文件 1 删除web x
  • MVC三层架构

    1 什么是MVC Model View Controller 模型 视图 控制器 模型就是Java对应数据库的那些字段 实体类 视图 就是JSP页面 控制器 就是Servlet负责跳转页面 Controller作用 Controller其实
  • mysql不是内部或外部命令,也不是可运行的程序 或批处理文件。

    安装Mysql后 当我们在window r中输入cmd中敲入mysql时会出现 Mysql 不是内部或外部命令 也不是可运行的程序或其处理文件 打开我的电脑在我的电脑右键中选择属性 然后单击选择高级系统设置 在系统属性的 高级 中选择环境变
  • 域名解析ip地址的过程

    浏览器会把输入的域名解析成对应的IP 其过程如下 1 查找浏览器缓存 因为浏览器一般会缓存DNS记录一段时间 不同浏览器的时间可能不一样 一般2 30分钟不等 浏览器去查找这些缓存 如果有缓存 直接返回IP 否则下一步 2 查找系统缓存 浏
  • JDBC基本概念

    什么是JDBC JDBC概念 JDBC Java DataBase Connectivity 是一套统一的基于Java语言的关系数据库编程接口规范 该规范允许将SQL语句作为参数通过JDBC接口发送给远端数据库 远端数据库接收到SQL语句后

随机推荐

  • pytorch和tensorflow有什么区别?

    PyTorch 是一种用于构建深度学习模型的功能完备框架 同时tensorflow也是常用的框架之一 大家在学习的时候 尝尝会用来做比较 那么pytorch和tensorflow有什么区别 大家所关心的问题 解答来了 pytorch和ten
  • vue拖拽实现

    拖拽介绍 目标是将左侧list中的item拖入右侧card中 如下所示 将list1和list3拖入右侧拖拽区 一 拖拽样式实现 使用vue vuetifyjs实现 页面布局可根据不同的UI库自行修改 html内容
  • C++消消乐

    键盘版 include
  • 5、JSON.parse()

    JSON parse JSON 通常用于与服务端交换数据 在接收服务器数据时一般是字符串 我们可以使用 JSON parse 方法将数据转换为 JavaScript 对象 语法 JSON parse text reviver 参数说明 te
  • 虚幻引擎程序化资源生成框架PCG 之Extents Modifier 和 Bounds Modifier

    Extents Modifier 和 Bounds Modifier这两个节点看起来很像 都是修改Point的Bouding Box 查看一下源代码 简单看一下它们的区别 文章目录 两个节点的代码对比 Bounds Modifier 源代码
  • MySQL使用ReplicationConnection导致的连接失效分析与解决

    MySQL数据库读写分离 是提高服务质量的常用手段之一 而对于技术方案 有很多成熟开源框架或方案 例如 sharding jdbc spring中的AbstractRoutingDatasource MySQL Router等 而mysql
  • 基于vue-cli、elementUI的Vue超简单入门小例子

    这个例子还是比较简单的 独立完成后 能大概知道vue是干嘛的 可以写个todoList的小例子 开始写例子之前 先对环境的部署做点简单的介绍 其实和Vue官方的差不多 如若没有安装过vue cli 先全局安装一下vue cli cnpm i
  • minikube 快速使用入门 - 安装 - 1

    minikube的官网地址 Welcome minikube k8s io minikube是什么 Minikube是一个单机版的kubernetes集群 可以在windows mac linux 快速的创建一个kubernetes集群 它
  • 原来react的createContext 这么简单

    今天看了下react中createContext相关的源码 特意在这里拿出来分享下 同时也会体现出关于本人看源码的技巧 本文采用源码分析以及源码断点调试的方式进行列举 用法 import React from react const Cou
  • (转)解决windows10下无法安装.net framework 3.5,错误代码0x800F081F

    1 下载 NET Framework 3 5的安装包netfx3 cab 将下载的文件复制到复制到 C 盘的 Windows 文件夹 后请在 命令提示符 管理员 中执行下面的命令 dism online Enable Feature Fea
  • centos下离线安装PostgreSQL

    安装简述 1 配置系统环境 2 安装postgreSQL 3 创建用户和分配权限 4 设置远程连接 配置系统环境 前提条件 文件postgresql 12 2 tar gz 放在 opt路径 步骤1 解压文件 cd opt tar zxvf
  • 编译错误记录

    一 MDK编译错误 1 error 235 variable epos msg was declared with a never completed type 这个错误的意思是epos msg这个变量被一个 没有被完成的的类型 定义 原因
  • Android串口通讯SerialPort(使用篇)

    1 什么是串口 在不会使用串口通讯之前 暂且可以把它理解为 一个可通讯的口 使用篇不深入探讨理论及原理 能理解串口如何使用之后 可以查看Android串口通讯SerialPort 浅谈原理 2 添加依赖 1 在 module 中的 buil
  • 数据库学习笔记之数据查询(一)

    数据库学习笔记之数据查询 一 查询之前先添加几条数据叭 还是基于这个里面建的那三个表 Student Course SC 进行插入查询操作 数据都是书上的 为Student表添加数据 INSERT INTO Student VALUES 1
  • pip install requests 报错 Could not fetch URL https://pypi.python.org/simple/requests/: There was ..r

    如题 pip install requests 报错 Could not fetch URL https pypi python org simple requests There was a problem confirming the
  • pandas读写mysql、h2和oracle数据库

    pandas读写mysql h2和oracle数据库 一 mysql数据库 二 h2数据库 三 oracle数据库 前言 在机器学习过程中 除开自己导入数据 用pandas的read xx之外 很多时候同样需要从数据库导入数据 特别是在做工
  • 夜莺(Flashcat)V6监控(五):夜莺监控k8s组件(下)---使用kube-state-metrics监控K8s对象

    目录 一 前言 二 categraf作为Daemonset的方式去运行监控k8s组件 1 1 24版本以下的k8s集群部署方法 创建autu yaml绑定权限 Daemonset部署categraf采集监控kubelet kube prox
  • 在jsp页面获取url请求参数

    JSP页面 When using the JSTL s expression language the request parameters are made available in the implicit object param T
  • css flex布局 —— 项目属性 flex-grow

    flex grow 属性定义项目的放大比例 解决的问题是 在空间有多余时把多余空间分配给各个子元素 flex grow 的值默认为 0 也就是说 如果存在剩余空间 也不放大 flex grow 取值为非负数 如果取值为负数那么和0的取值效果
  • mybatis拦截器

    最近在用mybatis做项目 需要用到mybatis的拦截器功能 就顺便把mybatis的拦截器源码大致的看了一遍 为了温故而知新 在此就按照自己的理解由浅入深的理解一下它的设计 和大家分享一下 不足和谬误之处欢迎交流 直接入正题 首先 先