Mybatis之拦截器原理(jdk动态代理优化版本)

在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化 其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方的非面向接口的cglib. 我们现在说的是jdk的动态代理,因为mybatis拦截器也是基于这个实现的。 简单介绍就是建立一个目标类的代理类。在执行目标类的方法前先执行代理类的方法,目标类的方法是在代理类中执行的,所以目标类方法执行前后,可以再代理类中进行其他操作。 简单版: 复制代码 1 public interface Target { 2 void work(); 3 } 复制代码 复制代码 1 public class TargetImpl implements Target { 2 @Override 3 public void work() { 4 System.out.println("我就只能做这么多了"); 5 } 6 } 复制代码 下面创建一个代理类 复制代码 1 public class TargetProxy implements InvocationHandler { 2 3 private Object target; 4 5 public TargetProxy(Object target){ 6 this.target = target; 7 } 8 9 /** 10 * 缺点 代理需要做的事情不是很灵活。直接在这里面写死了。 11 * @param proxy 12 * @param method 13 * @param args 14 * @return 15 * @throws Throwable 16 */ 17 @Override 18 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 19 System.out.println("代理在前面做事情了"); 20 try { 21 return method.invoke(target, args); 22 } catch (InvocationTargetException e) { 23 throw e.getCause(); 24 } 25 } 26 27 public static Object getProxyObject(Object target){ 28 return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TargetProxy(target)); 29 } 30 } 复制代码 复制代码 1 public class Client { 2 3 public static void main(String[] args) { 4 Target target = new TargetImpl(); 5 target.work(); 6 System.out.println("-----------------------------"); 7 Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl()); 8 target1.work(); 9 } 10 } 复制代码 结果: 我就只能做这么多了 ----------------------------- 代理在前面做事情了 我就只能做这么多了 ——————————————————————————————————————————————————————————————————————————————— 这样是最常见的代理了,但是有个缺点,代理类要做的事情在代理类写死了,要换就得多写一个代理类。那下面我们就把代理的事项单独拿出来。 增加拦截器接口和实现类 复制代码 1 public interface Interceptor { 2 void doOtherThings(); 3 } 复制代码 复制代码 1 public class InterceptorImpl implements Interceptor { 2 @Override 3 public void doOtherThings() { 4 System.out.println("还可以灵活地做其他事情"); 5 } 6 } 复制代码 代理类变一下: 复制代码 1 public class TargetProxy implements InvocationHandler { 2 3 private Object target; 4 5 private Interceptor interceptor; 6 7 public TargetProxy(Object target, Interceptor interceptor) { 8 this.interceptor = interceptor; 9 this.target = target; 10 } 11 12 @Override 13 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 14 interceptor.doOtherThings(); 15 return method.invoke(target, args); 16 } 17 18 /** 19 * 获取代理对象的时候顺便把拦截逻辑对象也传过来 20 * 21 * @param interceptor 22 * @return 23 * @paramarget 24 */ 25 public static Object getProxyObject(Object target, Interceptor interceptor) { 26 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor)); 27 } 28 29 30 } 复制代码 复制代码 1 public class Client { 2 3 public static void main(String[] args) { 4 Target target = new TargetImpl(); 5 target.work(); 6 System.out.println("-----------------------------"); 7 Interceptor interceptor = new InterceptorImpl(); 8 Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor); 9 target1.work(); 10 System.out.println("-----------------------------"); 11 Interceptor interceptor1 = new Interceptor() { 12 @Override 13 public void doOtherThings() { 14 System.out.println("换个拦截方式?"); 15 } 16 }; 17 Target target2 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor1); 18 target2.work(); 19 } 20 } 复制代码 结果: 我就只能做这么多了 ----------------------------- 还可以灵活地做其他事情 我就只能做这么多了 ----------------------------- 换个拦截方式? 我就只能做这么多了 ——————————————————————————————————————————————————————————————————————————————— 这样写是不是感觉挺好了。但是你是不是要拦截所有方法的呢?正常场景是不是只需要拦截method中某个某几个方法呢?那这样就把拦截器加个参数,method好了。 复制代码 1 public interface Interceptor { 2 void doOtherThings(Method method, Object[] args); 3 } 复制代码 复制代码 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 interceptor.doOtherThings(method, args); 3 return method.invoke(target, args); 4 } 复制代码 注意:java设计模式中有一个规则就迪米特法则,也叫最少知识法则,意思应该就是一个类知道的越少越好,对一个对象知道的越少越好。 那这样看反正拦截器都需要method还不如让代理类也不知道method好了,method执行需要代理类的target那就把target也传过去。 传了这么多参数,我们可以不可以把这些参数封装成一个对象传过去,暂时就叫Invocation 然后method.invoke执行就需要这三个参数,那么这三个操作就放在Invocation里面好了,要不然你还得让拦截器去获取这些属性 复制代码 1 public class Invocation { 2 3 private Object target; 4 5 private Method method; 6 7 private Object[] args; 8 9 public Invocation(Object target, Method method, Object[] args) { 10 this.target = target; 11 this.method = method; 12 this.args = args; 13 } 14 15 public Object proceed() throws InvocationTargetException, IllegalAccessException { 16 return method.invoke(target,args); 17 } 18 19 public Object getTarget() { 20 return target; 21 } 22 23 public void setTarget(Object target) { 24 this.target = target; 25 } 26 27 public Method getMethod() { 28 return method; 29 } 30 31 public void setMethod(Method method) { 32 this.method = method; 33 } 34 35 public Object[] getArgs() { 36 return args; 37 } 38 39 public void setArgs(Object[] args) { 40 this.args = args; 41 } 42 } 复制代码 下面看拦截器怎么实现 复制代码 1 public interface Interceptor { 2 3 public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException; 4 } 复制代码 复制代码 1 public class InterceptorImpl implements Interceptor { 2 @Override 3 public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException { 4 if(invocation.getMethod().getName().equals("work")){ 5 System.out.println("真的假的"); 6 return invocation.proceed(); 7 }else{ 8 return null; 9 } 10 11 } 复制代码 复制代码 1 public class TargetProxyTwo implements InvocationHandler { 2 3 private Object target; 4 5 private Interceptor interceptor; 6 7 public TargetProxyTwo(Object target, Interceptor interceptor) { 8 this.target = target; 9 this.interceptor = interceptor; 10 } 11 12 @Override 13 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 14 return interceptor.intercept(new Invocation(target,method,args)); 15 } 16 17 public static Object getProxyObj(Object target,Interceptor interceptor){ 18 return Proxy.newProxyInstance(target.getClass().getClassLoader(), 19 target.getClass().getInterfaces(), 20 new TargetProxyTwo(target, interceptor)); 21 } 22 } 复制代码 复制代码 1 public class Client { 2 3 public static void main(String[] args) { 4 Target target = (Target) TargetProxyTwo.getProxyObj(new TargetImpl(),new InterceptorImpl()); 5 target.work(); 6 } 7 } 复制代码 结果: 真的假的 我就只能做这么多了 ——————————————————————————————————————————————————————————————————————————————— 迪米特法则来看,客户端现在需要知道 拦截器,和代理类。 那么能不能把代理类的注册放到拦截器里面呢?可以的。来看下 复制代码 1 public interface Interceptor { 2 3 public Object register(Object target); 4 5 public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException; 6 } 复制代码 复制代码 1 public class InterceptorImpl implements Interceptor { 2 3 4 @Override 5 public Object register(Object target) { 6 return TargetProxyTwo.getProxyObj(target,this); 7 } 8 9 @Override 10 public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException { 11 if(invocation.getMethod().getName().equals("work")){ 12 System.out.println("真的假的"); 13 return invocation.proceed(); 14 }else{ 15 return invocation.procedd(); 16 } 17 18 } 19 } 复制代码 复制代码 1 public class Client { 2 3 public static void main(String[] args) { 4 5 Target target = (Target) new InterceptorImpl().register(new TargetImpl()); 6 target.work(); 7 } 8 } 复制代码 这样是不是很完美? 这样写是有问题的 复制代码 1 if(invocation.getMethod().getName().equals("work")){ 2 System.out.println("真的假的"); 3 return invocation.proceed(); 4 }else{ 5 return invocation.proceed(); 6 } 复制代码 把判断方法的逻辑方法拦截方法里面,那么假如十个方法,十个拦截逻辑呢?你是不是要写大一堆if else?这样是美观的。怎么解决呢?注解啊!!! 在拦截器上添加要拦截的方法注解不就好了嘛。 来看代码: 复制代码 1 @Retention(RetentionPolicy.RUNTIME) 2 @java.lang.annotation.Target(ElementType.TYPE) 3 public @interface MethodName { 4 public String value(); 5 } 复制代码 1 @MethodName("work") 2 public class InterceptorImpl implements Interceptor 复制代码 1 public class TargetProxy implements InvocationHandler { 2 3 private Object target; 4 5 private Interceptor interceptor; 6 7 public TargetProxy(Object target, Interceptor interceptor) { 8 this.interceptor = interceptor; 9 this.target = target; 10 } 11 12 @Override 13 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 14 MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class); 15 if (methodName == null) { 16 throw new NullPointerException("拦截器注解方法名字为空"); 17 } 18 String name = methodName.value(); 19 if (name.equals(method.getName())) { 20 return interceptor.intercept(new Invocation(target, method, args)); 21 } 22 return method.invoke(target, args); 23 } 24 25 /** 26 * 获取代理对象的时候顺便把拦截逻辑对象也传过来 27 * 28 * @param interceptor 29 * @return 30 * @paramarget 31 */ 32 public static Object getProxyObject(Object target, Interceptor interceptor) { 33 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor)); 34 } 35 } 复制代码 怎么样,这样是不是就很完美了。 先预告下: 上面的类对应Mybatis的类: Invocation,Interceptor完全一样。TargetProxy对应Plugin类。regist方法对应wrap方法 好的下面来看Mybatis的拦截器了 MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) Mybatis的拦截器的使用方式是通过xml配置的 复制代码 复制代码 通过这样的配置,就有了默认的拦截器: 那么这四种拦截器分别在什么时候添加的? XMLConfigBuilder 复制代码 1 private void pluginElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 String interceptor = child.getStringAttribute("interceptor"); 5 Properties properties = child.getChildrenAsProperties(); 6 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); 7 interceptorInstance.setProperties(properties); 8 configuration.addInterceptor(interceptorInstance); 9 } 10 } 11 } 复制代码 Mybatis中有一个拦截器链,典型的责任链模式 那么这四种拦截器分别在什么时候开始执行拦截呢? 先介绍下责任链获取恰当的拦截器的方法,Configuration类中 复制代码 1 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 2 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); 3 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 4 return parameterHandler; 5 } 6 7 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, 8 ResultHandler resultHandler, BoundSql boundSql) { 9 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); 10 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 11 return resultSetHandler; 12 } 13 14 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 15 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); 16 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 17 return statementHandler; 18 } 19 20 public Executor newExecutor(Transaction transaction) { 21 return newExecutor(transaction, defaultExecutorType); 22 } 23 24 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 25 executorType = executorType == null ? defaultExecutorType : executorType; 26 executorType = executorType == null ? ExecutorType.SIMPLE : execut
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信