一步一步搞懂MyBatis中设计模式源码——代理模式


文章目录


一、引入今天的主题

二、正文开始——代理模式

三、代理模式分类

   3.1静态代理

      3.1.1实现静态代理两个要求

      3.1.2代码实现

   3.2jdk动态代理

      3.2.1定义

      3.2.2jdk动态代理的两个核心方法

      3.2.3拿上面的例子举例

四、动态代理在MyBatis中的应用

    4.1手写的MyBtatis框架的测试类

    4.2MapperProxyFactory类创建Dao层接口代理对象

    4.3使用代理对象执行findAll方法

       4.3.1首先看MapperProxy类

       4.3.2再看MapperMethod类

五、图总结


一、引入今天的主题



今天准备写代理模式的时候,苦思要找什么例子,就搜了下世界名牌口红的企业——YSL(圣罗兰),就问下了女朋友,知道这个嘛。上图的回答,简直让我怀疑找了个假女朋友(😂)。


搜YSL也是看见微商在朋友圈发的广告,不知道大家有没有发现,微商简直就是代理模式的完美例子,画个问号?,向下看👇


二、正文开始——代理模式


是什么


  • 代理模式是给某一个对象提供一个代理对象,并且代理对象持有原对象的引用

  • 在不更改原对象源码的情况下对原对象的方法进行修改和加强,符合开闭原则

  • 属于对象的结构型模式


看不太懂?,没有关系,下面讲例子


举上面的例子——微商


  • 原对象(真实对象):YSL官方商店,买YSL的产品(原对象方法)

  • 代理对象:微商,代理YSL官方商店买YSL的产品(原对象方法),为了提高竞争力,并送一些小礼物(方法修改和加强)


三、代理模式分类


静态代理:指在编译阶段,代理类由程序员写好,在程序运行时直接获取代理对象的源码进行编译


动态代理:编译阶段程序员不写代理类,而是在程序运行时,根据用户定义的增加规则来动态生成原对象的代理对象,(不用想,肯定用到了多态)

    

动态代理分为面向接口的jdk动态代理和Cglib动态代理(暂不做讨论,Mybatis中使用的是jdk动态代理)。


3.1静态代理


3.1.1实现静态代理两个要求


    1.原对象和代理对象实现同一个接口

    2.代理对象持有原对象的引用,并在方法中对原对象的方法进行增强


如:


  • 原对象:YSL的官方商店

  • 代理对象:微商,持有YSL的官方商店的引用

  • 实现同一个接口:卖产品


3.1.2代码实现


/**
   * @Author Think-Coder
   * @Data 2020/5/14 10:55
   * @Version 1.0
   */

  //定义一个卖化妆品的接口
  public interface MakeUpSeller {
      //销售的方法
      //name为化妆品名字,price是价格
      void sell(String name,double price);
  }
  
  //原对象—————YSL官方商店
  public class YSLSeller implements MakeUpSeller {
  
      @Override
      public void sell(String name, double price) {
          System.out.println("感谢购买"+name+",一共是"+price+"元");
      }
  }
  
  //代理对象————微商代理YSL官方商店
  public class WeiShangProxy implements MakeUpSeller {
    
    //持有YSL官方商店的引用
      private YSLSeller yslSeller;
    
      public WeiShangProxy(YSLSeller yslSeller) {
          this.yslSeller = yslSeller;
      }
    
    //实现接口的sell方法,并增强原对象YSL官方商店的方法
    //增强原对象的方法:两个输出方法
      @Override
      public void sell(String name, double price) {
          System.out.println("我要发朋友圈,介绍商品优势");
          //YSL官方商店对象调用卖产品的接口
          yslSeller.sell(name,price);
          System.out.println("并送您一瓶卸妆水,欢迎下次再来");
      }
  }


测试类ProxyTest


public class ProxyTest {
      public static void main(String[] args) {
  
          //将new的YSLSeller官方商店原对象传入微商代理对象
          //微商代理对象实现了客户对YSL官方商店的访问控制
          WeiShangProxy weiShangProxy = new WeiShangProxy(new YSLSeller());
          
          //微商代理对象调用卖产品方法
          weiShangProxy.sell("YSL口红",1000);
      }
  }


看下面的结果是不是很暖心


我要发朋友圈,介绍商品优势
感谢购买YSL口红,一共是1000.0
并送您一瓶卸妆水,欢迎下次再来
Process finished with exit code 0


用类图做个总结:



在测试类中最重要的就是将new YSLSeller()对象放入WeiShangProxy构造函数中

也就是说客户直接访问了微商代理类,从而微商代理控制了客户对YSL官方商店的访问


静态代理缺点:


静态代理是面向实现编程(YSLSeller实现了MakeUpSeller接口)而不是面向接口编程,就把程序写死了,不利于程序的扩展,即如果原对象增加或删除方法,代理对象也会跟着改变,极大提高代码维护成本。于是就有了JDK动态代理。


3.2jdk动态代理


3.2.1定义


  • 在程序运行时,根据用户的定义规则,动态生成原对象的代理对象,

  • 用上边的例子解释就是,不写微商代理类,而是在程序运行时利用Proxy类及InvocationHandler接口等动态生成代理类及代理实例。


3.2.2jdk动态代理的两个核心方法


  • Proxy类的newProxyInstance方法:生成原对象的代理对象

  • InvocationHandler接口的invoke方法:包装原对象的方法,并增强


Proxy类的newProxyInstance方法


生成代理对象


/**
     * 参数1:ClassLoader loader,原对象的类加载器
     * 参数2:Class<?>[] interfaces,原对象继承(实现)的类和接口Class类数组
     * 参数3:InvocationHandler h,用户自定义增强原对象的方法接口
     **/

  public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
      //上面省略
    /*
         * Look up or generate the designated proxy class.
         * 查找或生成指定的代理类
         */

        Class<?> cl
= getProxyClass0(loader, intfs);


InvocationHandler接口的invoke方法


用户自定义的规则接口需要实现此接口,invoke方法用于增加原代理对象方法


public interface InvocationHandler {
  /**
     * 参数1:Object proxy,代理对象
     * 参数2:Method method,原对象方法对应的反射类,method.invoke反射调用原对象方法
     * 参数3:Object[] args,传入方法参数
     **/

  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable;
}


3.2.3拿上面的例子举例


  • 微商代理类已经不需要了,可以动态生成

  • MakeUpSeller接口及YSLSeller官方商店类不发生变化

  • 加入MakeUpSellerHandler类实现InvocationHandler接口,用于增强原对象方法


完整代码如下


/**
   * @Author Think-Coder
   * @Data 2020/5/14 10:55
   * @Version 1.0
   */

  
  //定义一个卖化妆品的接口
  public interface MakeUpSeller {
      //销售的方法
      //name为化妆品名字,price是价格
      void sell(String name,double price);
  }
  
  //原对象—————YSL官方商店
  public class YSLSeller implements MakeUpSeller {
  
      @Override
      public void sell(String name, double price) {
          System.out.println("感谢购买"+name+",一共是"+price+"元");
      }
  }

  //实现InvocationHandler接口
  public class MakeUpSellerHandler implements InvocationHandler {
    //持有原对象的父类的引用,父类引用指向子类对象,多态的体现
      private MakeUpSeller makeUpSeller;
  
      public MakeUpSellerHandler(MakeUpSeller makeUpSeller) {
          this.makeUpSeller = makeUpSeller;
      }
  
      @Override
      //增强原对象的方法
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("我要发朋友圈,介绍商品优势");
          //反射调用原对象的方法
          method.invoke(makeUpSeller,args);
          System.out.println("并送您一瓶卸妆水,欢迎下次再来");
          return null;
      }
  }


看下测试类


public class ProxyTest {
      public static void main(String[] args) {
        /**
         * 参数1:MakeUpSeller.class.getClassLoader(),MakeUpSeller的类加载器
         * 参数2:new Class[]{MakeUpSeller.class},MakeUpSeller继承(实现)的类和接口Class数组
         * 参数3:new MakeUpSellerHandler(new YSLSeller()),用户自定义增强原对象的方法接口
         **/

          MakeUpSeller yslProxy = (MakeUpSeller) Proxy.newProxyInstance(MakeUpSeller.class.getClassLoader(),
                 new Class[]{MakeUpSeller.class},
                     new MakeUpSellerHandler(new YSLSeller()));
          yslProxy.sell("YSL口红",1000);
      }
  }


看测试结果


我要发朋友圈,介绍商品优势
感谢购买YSL口红,一共是1000.0
并送您一瓶卸妆水,欢迎下次再来
Process finished with exit code 0


至此动态代理就实现了


不过,还有两个疑问没有解决


1.为什么Proxy.newProxyInstance方法生成的代理对象可以强转成MakeUpSeller接口类型?

    

2.为什么代理对象调用sell方法,会调用MakeUpSellerHandler的invoke方法?


带着这两个疑问,咱们反编译下生成动态代理类


编译是.java文件编译为.class文件,反编译为.class文件变为.java文件的过程


反编译生成动态代理类


改下测试类代码


public static void main(String[] args) throws IOException {
        MakeUpSeller yslProxy = (MakeUpSeller) Proxy.newProxyInstance(MakeUpSeller.class.getClassLoader(),new Class[]{MakeUpSeller.class},
                new MakeUpSellerHandler(new YSLSeller()));
        yslProxy.sell("YSL口红",1000);

        createProxyClass();
    }

    public static void createProxyClass() throws IOException {
        byte[] bytes = ProxyGenerator.generateProxyClass("MakeUpSeller$proxy", new Class[]{MakeUpSeller.class});
        Files.write(new File("D:\\ITProject\\javaproj\\selfproj\\ProxyTest\\out\\production\\ProxyTest\\MakeUpSeller$proxy.class").toPath(),bytes);
    }


生成的文件如下



代码如下,做了部分省略,


//继承Proxy代理类,实现了MakeUpSeller接口
  //这个就可以回答第一个问题,可以转成MakeUpSeller类型
  public final class MakeUpSeller$proxy extends Proxy implements MakeUpSeller {
      private static Method m1;
      private static Method m2;
      private static Method m3;
      private static Method m0;
  
      public MakeUpSeller$proxy(InvocationHandler var1) throws  {
          super(var1);
      }
  
    //实现MakeUpSeller接口sell类
      public final void sell(String var1, double var2) throws  {
          try {
            //这行代码很重要,回答了第二个问题
            //该类继承proxy类,h便为InvocationHandler接口,因此可以调用invoke方法
            //而MakeUpSellerHandler实现了InvocationHandler接口,因此直接调用了
            //MakeUpSellerHandler类中invoke方法
              super.h.invoke(this, m3, new Object[]{var1, var2});
          } catch (RuntimeException | Error var5) {
              throw var5;
          } catch (Throwable var6) {
              throw new UndeclaredThrowableException(var6);
          }
      }
  }


如此就可以解释上面的两个问题了


最后也用类图总结一下



main方法用代理对象调用sell方法时,其实是动态生成的MakeUpSeller$proxy类实例调用的sell方法


根据上面反编译类中sell方法中,调用的是MakeUpSellerHandler接口中invoke方法,invoke方法中包装了原对象YSLSeller的sell方法,最后实现了动态代理。


接下来看jdk动态代理在Mybatis中的应用,终于到了


四、动态代理在MyBatis中的应用


4.1手写的MyBtatis框架的测试类


public static void main(String[] args) throws IOException {
        //1.读取配置文件,连接数据库
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);

        //3.使用工厂生产SqlSession对象,用于操作数据库
        SqlSession session = factory.openSession();

        //4.使用SqlSession创建Dao接口的代理对象,因为IUserDao接口没有实现类
        IUserDao userDao = session.getMapper(IUserDao.class);

        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for (User user:users){
            System.out.println(user);
        }

        //6.释放资源
        session.close();
        in.close();
    }


在短短的测试类中就使用了三个设计模式,确实对初学者不太友好,所以一点一点拆开来看未免不是一个好的学习习惯,所以今天主要看两行代码


//4.使用SqlSession创建Dao接口的代理对象,因为IUserDao接口没有实现类
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();

看完上面的动态代理,再看这两行代码就能解开初学Mybatis时候的疑惑,为什么只有Dao层接口,没有Dao层的接口实现类就可以操作数据库?


就是用到了jdk的动态代理生成了Dao层接口的代理对象userDao


下面从源码分析一下,Mybatis底层是怎么创建Dao层接口的代理对象的


4.2MapperProxyFactory类创建Dao层接口代理对象


也就是研究下面的代码


IUserDao userDao = session.getMapper(IUserDao.class);


当调用几个类的getMapper方法后,会调用下面类第1个newInstance方法


public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
  
  //通过构造函数传入IUerDao接口Class对象
  //学过反射的童鞋应该知道,拿到Class对象,相当于拿到IUserDao类
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }
    
    //先调用此方法
   public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    
    //调用下面newInstance方法
        return this.newInstance(mapperProxy);
    }
    
    protected T newInstance(MapperProxy<T> mapperProxy) {
       /**
        * 有没有很熟悉!
        * mapperInterface就是Dao层接口 IUserDao
       * 参数1:this.mapperInterface.getClassLoader(),IUserDao的类加载器
       * 参数2:new Class[]{this.mapperInterface},IUserDao继承(实现)的类和接口Class数组
       * 参数3:mapperProxy,上边的newInstace方法返回的,实现了InvocationHandler接口,用于方法增强
       **/

        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
}


上面的代码,写注释的地方是重点


MapperProxyFactory类就是创建代理对象的工厂类,自定义Dao层接口传入构造函数,通过newInstance方法返回自定义Dao层接口的代理对象


4.3使用代理对象执行findAll方法


Listusers = userDao.findAll();


看到代码不得不提出两个问题


    1.代理对象userDao是如何执行findAll()方法的

    2.findAll方法是如何找到对应的sql语句进行增删改查的


4.3.1首先看MapperProxy类


该类实现InvocationHandler接口,重写的invoke方法包装了原对象IUserDao接口中findAll方法,也就是说,当执行userDao.findAll();时,会调用该类的invoke方法


invoke方法作用:生成findAll方法对应的MapperMethod类实例,MapperMethod类是最重要的,在下面


public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
   

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //上面省略
    
    //下面两行代码很重要
    //method为Dao层自定义接口方法
    //调用下面的cachedMapperMethod找到与要执行的Dao层接口方法对应的MapperMethod
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        
        //调用execute方法来执行findAll方法
        //先把sqlSession传入到MapperMethod内部
        //在MapperMethod内部将要执行的方法名和参数再传入sqlSession对应方法中去执行
        return mapperMethod.execute(this.sqlSession, args);
    }
    
    //根据的传入IUserDao接口自定义方法findAll,生成对应的MapperMethod类实例
    private MapperMethod cachedMapperMethod(Method method) {
        return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
            return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        });
    }
}


4.3.2再看MapperMethod类


该类的两个作用


    1.解析接口自定义的findAll方法

    2.并找到执行对应的sql语句的方法


先看是如何解析的


public class MapperMethod {
  //SqlCommand内部类解析自定义接口方法的方法名称和SQL语句类型,
    private final MapperMethod.SqlCommand command;
    //MethodSignature内部类解析接口方法的签名,即接口方法和参数名称和参数值映射关系,如String a="0"
    private final MapperMethod.MethodSignature method;
    
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }
}


那么问题来了,该类是如何找到findAll方法对应的sql语句呢?


答案就是Configuration对象,通过MapperMethod构造函数传进来的



如图所示Configuration中的mapperedStatements字段中的MapperedStatement对象是一个Map类型


key为findAll方法,value中包含sql语句,可以通过方法名findAll找到对应的sql语句(这个就是上面第二个问题的答案)


再看execute方法为findAll方法找到的sql语句类型匹配方法


execute方法源码


public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;

    //根据SqlCommand解析出来的sql语句类型,为增删改查类型匹配方法
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }


根据sql语句类型匹配对应的方法后,其实是调用SqlSession接口的实现类执行sql语句

如根据查找到executeForMany方法


private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if (this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
      
      //最后执行sqlSession接口中的selectList方法
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
            return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        } else {
            return result;
        }
    }


SqlSession接口


public interface SqlSession extends Closeable {
   /**
     * var1:Dao层自定义接口的方法名称,即findAll()
     * var2:方法的参数
     * var3:用于分页查询
      **/

    <T> T selectOne(String var1);
    <T> T selectOne(String var1, Object var2);
    <E> List<E> selectList(String var1, Object var2, RowBounds var3);
    ....
}


最后交给SqlSession实现类DefaultSqlSession去执行findAll方法对应sql语句,并返回结果


这个和我们直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,转了一圈回来,就完成了动态代理


五、图总结


当代理对象userDao调用findAll()执行的代码流程


原文链接:https://blog.csdn.net/shang_0122/java/article/details/106105717


精彩推荐

MyBatis设计要点与核心架构分析

mybatis逆向生成工具,真的很好用!

Spring加载MyBatis过程解析

SpringBoot整合Mybatis的实现原理



扫下方二维码关注“程序员考拉”,每日推荐优秀好文!




如果感觉推送内容不错,不妨右下角点个在看,感谢支持!

评论