zoukankan      html  css  js  c++  java
  • mybatis框架之动态代理

    坦白讲,动态代理在日常工作中真没怎么用过,也少见别人用过,网上见过不少示例,但总觉与装饰模式差别不大,都是对功能的增强,什么前置后置,其实也就那么回事,至于面试中经常被问的mybatis框架mapper接口这一块,少不了的要扯到动态代理。说起来高深莫测,其实只是在忽略自己,或者也包括面试官吧。不过,在仔细阅读了mybatis这一块的源代码之后,对动态代理的理解稍有加强。

    请看下面这个jdk动态代理示例

    public interface UserService {
        Object sayHello(String name);
    }
    public class UserServiceImpl implements UserService {
        @Override
        public Object sayHello(String name) {
            System.out.println("sayHello方法被调用。。。");
            return "hello " + name;
        }
    }
    public class UserProxy implements InvocationHandler {
        private Object target;
    
        public UserProxy(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("前置增强。。。");
            Object result = method.invoke(target, args);
            System.out.println("后置增强。。。");
            return result;
        }
    }

    测试代码

    public class ProxyTest {
        public static void main(String[] args) {
            UserServiceImpl userServiceImpl = new UserServiceImpl();
            UserProxy userProxy = new UserProxy(userServiceImpl);
            UserService userService = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(),
                           userServiceImpl.getClass().getInterfaces(), userProxy); Object result
    = userService.sayHello("admin"); System.out.println("结果:" + result); } }

    运行结果打印如下:

    前置增强。。。
    sayHello方法被调用。。。
    后置增强。。。
    结果:hello admin

    类似于这样的示例,网上大把,单单就功能而言,此示例与装饰模式完全一样子,无非就是动态代理使用了invoke()方法,若是装饰模式,应该还会是sayHello()方法而已,

    这有个鬼的区别。但是,我们再来看看mybatis框架中,是如何运用动态代理的,看了或许就会有不一样的感受了。。。

    mybatis框架中Mapper接口动态代理相关的源码主要有下面两个类

    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.ibatis.session.SqlSession;
    
    /**
     * @author Lasse Voss
     */
    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }
    import java.io.Serializable;
    import java.lang.invoke.MethodHandles;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    import org.apache.ibatis.reflection.ExceptionUtil;
    import org.apache.ibatis.session.SqlSession;
    
    /**
     * @author Clinton Begin
     * @author Eduardo Macarron
     */
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
      private static final long serialVersionUID = -6424540398559729838L;
      private final SqlSession sqlSession;
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache;
    
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (method.isDefault()) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }
    
      private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    
      private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
          throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
            .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
            .newInstance(declaringClass,
                MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                    | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
            .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
      }
    }

    mybatis框架的尿性,创建对象基本都是用工厂,MapperProxyFactory类就是创建MapperProxy的工厂类,而这个MapperProxy就是代替Mapper接口干活的,或许就是因为

    这个原因,所以才叫代理模式吧!

    Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    
    
    Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

    接着说下我对上面这个方法的理解,:

    该方法就是创建一个代理对象,方法中的有三个参数。

    第1个参数: ClassLoader , 类加载器,对于理解代理模式而言,我觉得不重要。

    第2个参数:interfaces, 接口类,我的理解就是,它是一个大老板,吩咐狗腿子干活,自个基本不干活。

    第3个参数:InvocationHandler ,大老板的狗腿子,代替大老板的人,至于具体如何干活,就得看它的invoke()方法了。

    而在mybatis框架中,Mapper接口就是大老板,狗腿子就是MapperProxy, 具体怎么干活就是下面这段代码

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (method.isDefault()) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }

    看了这点代码,瞬间觉得与最前那个动态代理的例子大不相同,什么前置,后置,这里都没有使用到,或许动态代理的目的,根本就不是为了所谓的前置后置设计的吧(或许只是顺带的吧)。通过前面篇章的源码分析知道,操作数据库时,不会走if(){}else if(){}中的逻辑,直接走后面的逻辑,后面这段逻辑其实就是对Executor对象操作数据库的封装,这里就不会说了。

    为什么不进入if逻辑?

     if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          }

    通过最前面的示例知道 , invoke会调用method方法的真实现实,于Mapper接口而言,它根本没有实现,所以肯定不会走这段逻辑。 至于else if() ,完全没懂!

    总结:

    每次想到动态代理,我就想到装饰, 在工作中该如何取舍呢 ? 通过mybatis框架的启发,我觉得,如果接口有实现 ,使用装饰模式对原有功能进行增强便可,

    而如果单单只是一个接口,并没有提供实现,这时为了完成接口的功能(接口就是个废人,啥事都干不成),就可以选择动态代理了,毕竟在使用动态代理时,真正干事的

    是InvocationHandler#invoke, jdk提供的Proxy类就是将接口与真正干事InvocationHandler绑定关系。 

    上图县长相当于动态代理中的接口调用者; 大老板就是接口本身; 狗腿子就是InvocationHandler;   狗链子就是Proxy.

    最后再用一下示例加深一下理解:

    /**
     * 大老板
     */
    public interface IUserService {
        Object sayHello(String name);
    }
    /**
     * 狗腿子
     */
    public class UserProxy implements InvocationHandler {
        // 这里可以通过构造器传参,然后在invoke方法中执行业务逻辑
    
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是狗腿子。。。。。");
            return "hello" + args[0];
        }
    
    }/**
     * 县长
     */
    public class UserProxyTest {
        public static void main(String[] args) {
            IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(), 
                                    new Class[]{IUserService.class}, new UserProxy()); Object result = userService.sayHello("狗腿子"); System.out.println(result); } }

    打印结果如下:

    over! IUserService接口没有实现类,sayHello的具体实现是在UserProxy#invoke()方法中完成,当然,这只是个简单示例,在真正的业务代码中,完全可以通过UserProxy构造器传参数(包括对象), 然后调用这些参数,完成复杂的业务逻辑!

    通过此篇文章,应该是可以将装饰与代理的使用场景区分开了!

  • 相关阅读:
    "未能加载文件或程序集“XXX”或它的某一个依赖项。系统找不到指定的文件"的解决方案
    035——VUE中表单控件处理之使用vue控制select操作文字栏目列表
    034——VUE中表单控件处理之使用vue控制radio表单的实例操作
    033——VUE中安装使用vue-devtools调试工具用于监控数据变化
    015PHP基础知识——流程控制(三)
    014PHP基础知识——流程控制(二)
    032——VUE中表单控件处理之复选框的处理
    031——VUE中表单控件处理之使用vue控制input和textarea表单项
    030——VUE中鼠标语义修饰符
    029——VUE中键盘语义修饰符
  • 原文地址:https://www.cnblogs.com/z-qinfeng/p/11901989.html
Copyright © 2011-2022 走看看