MyBatis中通过动态代理生成实现接口的对象

  • 在mybatis中对javassist进行了二次封装,即可以在mybatis中使用javassist

  • 经过我们的改造,我们的dao层的AccountDaoImpl类已经变成了以下模样

    • package com.deng.bank.dao.impl;
      
      import com.deng.bank.dao.AccountDao;
      import com.deng.bank.pojo.Account;
      import com.deng.bank.utils.SqlSessionUtil;
      import org.apache.ibatis.session.SqlSession;
      
      public class AccountDaoIm implements AccountDao {
          public Account select(String actno){
              SqlSession sqlSession = SqlSessionUtil.getSqlSession();
              Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);
              return act;
          }
      
          public int update(Account act){
              SqlSession sqlSession = SqlSessionUtil.getSqlSession();
              int update = sqlSession.update("account.updateByActno", act);
              return update;
          }
      }
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87

      * 可以发现,每个方法都有一行固定的代码,且其他代码也非常固定(CRUD)。那么我们能不能不编写该类,而是使用javassist帮我们动态生成呢,答案是可以的,并且在mybatis中已经实现了,为了加深理解,我们先手写底层逻辑

      * 用户需要将接口作为参数传入
      * 获得类池对象,并使用该对象动态生成要返回的类,以及接口,并让该类实现该接口
      * 获取接口内的所有方法,并实现。具体过程
      * 拼接方法:创建一个StringBuilder对象,先将方法的框架拼接出来
      * 再拼接方法内容。那行重复的代码可以直接拼接。后面的代码需要通过sqlid来确定,因此我们还需要用户将SqlSession作为参数传入。
      * 关键:由于SqlId是由用户编写的,可变性太大,不利于框架开发者开发,因此开发者规定:Mapper文件中的namespace必须为接口的全限定名,SqlId必须为方法名。否则无法使用该动态代理生成方法。
      * 将类加入内存并实例化
      * 最后返回该类

      * ```java
      package com.deng.bank.utils;

      import com.deng.bank.pojo.Account;
      import org.apache.ibatis.javassist.CannotCompileException;
      import org.apache.ibatis.javassist.ClassPool;
      import org.apache.ibatis.javassist.CtClass;
      import org.apache.ibatis.javassist.CtMethod;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.mapping.SqlCommandType;
      import org.apache.ibatis.session.SqlSession;

      import java.lang.reflect.Method;
      import java.util.Arrays;

      /**
      * author:dch
      * this class is used to generate the object that implement the DAO's Interface
      */
      public class GenerateDaoProxy {
      public Object Generate(SqlSession sqlSession, Class AccountDao){
      ClassPool pool = ClassPool.getDefault();
      CtClass ctClass = pool.makeClass(AccountDao.getName() + "Proxy");
      CtClass anInterface = pool.makeInterface(AccountDao.getName());
      ctClass.addInterface(anInterface);
      //implement the functions
      Method[] methods = AccountDao.getDeclaredMethods();
      Arrays.stream(methods).forEach(method -> {
      StringBuilder methodCode = new StringBuilder();
      //public void delete(int a,int b){...};
      methodCode.append("public ");
      methodCode.append(method.getReturnType().getName() + " ");
      methodCode.append(method.getName() + " ");
      methodCode.append("(");
      Class<?>[] types = method.getParameterTypes();
      for (int i = 0; i < types.length; i++) {
      methodCode.append(types[i] + "arg" + i);
      if(i != types.length - 1){
      methodCode.append(",");
      }
      }
      methodCode.append("){");
      //SqlSession sqlSession = SqlSessionUtil.getSqlSession();
      methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.deng.bank.utils.SqlSessionUtil.getSqlSessioni();");
      String SqlId = AccountDao.getName() + "." + method.getName();
      SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(SqlId).getSqlCommandType();
      if(sqlCommandType == SqlCommandType.DELETE){

      }else if(sqlCommandType == SqlCommandType.INSERT){

      }else if(sqlCommandType == SqlCommandType.SELECT){
      methodCode.append("return sqlSession.selectOne(\""+SqlId+"\",arg0);");
      }else if(sqlCommandType == SqlCommandType.UPDATE){
      methodCode.append("return sqlSession.update(\""+SqlId+"\",arg0);");
      }
      methodCode.append("}");
      try {
      CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
      ctClass.addMethod(ctMethod);
      } catch (CannotCompileException e) {
      e.printStackTrace();
      }
      });
      //put the class to internal storage and instantiation
      Object o = null;
      try {
      o = ctClass.toClass().newInstance();
      } catch (Exception e) {
      e.printStackTrace();
      }

      return o;
      }
      }