(5)MyBatis使用javassist动态⽣成DAO接口实现类原理

javassist动态⽣成XxxDaoImpl类

Javassist是由东京⼯业⼤学的千叶滋所创建的⼀个开源的类库用来分析、编辑和创建Java字节码,已加⼊开放源代码JBoss应⽤服务器项⽬

  • 通过使⽤Javassist操作字节码为JBoss实现动态"AOP"框架

引⼊javassist的依赖

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.29.1-GA</version>
</dependency> 

javassist的常用方法

ClassPool类获取类池的静态方法和制造类和接口的实例方法

方法名功能
public static ClassPool getDefault()获取类池对象,这个类池就是用来给我生成class的
public CtClass makeClass(“全限定类名”)制造类 , 需要告诉javassist制造类的全限定类名是啥
public CtClass makeInterface(“全限定接口名”)制造接口 , 需要告诉javassist制造接口的全限定接口名是啥

CtMethod制造方法的静态方法和设置方法的实例方法

方法名功能
public static CtMethod make(“方法代码片段”,CtClass)制造方法对象 , 第一个参数是方法的代码片段 , 第二个参数指定要把方法添加到哪个制造类中
public CtMethod(返回值类型,⽅法名,形式参数列表,所属类)构造方法,创建方法对象
void setModifiers(枚举类)设置方法的修饰符,如Modifier.PUBLIC
void setBody(“方法体”)设置方法体

CtClass的实例方法

方法名
public CtClass addMethod(CtMethod)将方法添加到类中 , 参数是要添加的方法对象
public CtClass toClass();在内存中生成类,然后调用Class.forNmae方法手动加载到Jvm得到制造类的字节码对象
public Class toClass();在内存中生成类同时加载到JVM当中直接得到制造类的字节码对象
public CtClass addInterface(ctInterface)将接口添加到类中(类实现接口) , 参数是要添加的接口对象

在内存中制造类并创建方法,这里运⾏时由于JDK版本过高会出现有异常所以需要加两个参数(点击Modify options->Add VM options)

  • add-opens java.base/java.lang=ALL-UNNAMED
  • add-opens java.base/sun.net.util=ALL-UNNAMED

在这里插入图片描述

@Test
public void testGenerateFirstClass() throws Exception{
    // 获取类池,这个类池就是用来给我生成class的
    ClassPool pool = ClassPool.getDefault();
    
    // 制造类(需要告诉javassist类名是啥)
    CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
    
    // 制造方法
    String methodCode = "public void insert(){System.out.println(123);}";
    CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
    
    // 将方法添加到类中
    ctClass.addMethod(ctMethod);
    
    // 在内存中生成class
    ctClass.toClass();

    // 类加载到JVM当中,返回AccountDaoImpl类的字节码
    Class<?> clazz = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl");
    
    // 创建对象
    Object obj = clazz.newInstance();
    
    // 获取AccountDaoImpl中的insert方法
    Method insertMethod = clazz.getDeclaredMethod("insert");
    
    // 调用方法insert
    insertMethod.invoke(obj);
}

创建方法的另一种形式

// 执行构造方法创建⽅法对象
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", newCtClass[]{}, ctClass);
// 设置⽅法的修饰符列表
ctMethod.setModifiers(Modifier.PUBLIC);
// 设置⽅法体
ctMethod.setBody("{System.out.println(\"hello world\");}");

动态生成接口实现类

已知接口中方法的方法名和返回值类型,在内存中动态生成AccountDao接口的实现类

public interface AccountDao {
    void delete();
}
 @Test
public void testGenerateImpl() throws Exception{
    // 获取类池
    ClassPool pool = ClassPool.getDefault();
    // 制造类
    CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
    // 制造接口
    CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
    // 添加接口到类中,相当于让AccountDaoImpl implements AccountDao
    ctClass.addInterface(ctInterface);
    // 实现接口中的方法,假设我们已经知道接口中的方法名和返回值类型
    CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
    // 将方法添加到类中
    ctClass.addMethod(ctMethod);
    // 在内存中生成类的同时将生成的类加载到JVM当中
    Class<?> clazz = ctClass.toClass();
    // 面向接口编程,强制类型转换
    AccountDao accountDao = (AccountDao)clazz.newInstance();
    accountDao.delete();
}

不知道方法的方法名和返回值类型, 但是方法中的代码片段固定, 动态生成接口的实现类中并实现所有方法

public interface AccountDao {
    void delete();
    int insert(String actno);
    int update(String actno, Double balance);
    String selectByActno(String actno)
}
@Test
public void testGenerateAccountDaoImpl() throws Exception{
    // 获取类池
    ClassPool pool = ClassPool.getDefault();
    // 制造类
    CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
    // 制造接口
    CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
    // 实现接口
    ctClass.addInterface(ctInterface);
    // 要实现接口中所有的方法,先获取接口中所有的抽象方法
    Method[] methods = AccountDao.class.getDeclaredMethods();
    Arrays.stream(methods).forEach(method -> {
        try {
            // public void delete(){}
            // public int update(String actno, Double balance){}
            StringBuilder methodCode = new StringBuilder();
            // 追加修饰符列表
            methodCode.append("public "); 
            // 追加返回值类型(getReturnType()以Class形式返回方法的返回值类型对应的Class对象)
            methodCode.append(method.getReturnType().getName()); 
            // 追加空格
            methodCode.append(" ");
            // 追加方法名
            methodCode.append(method.getName()); 
            // 获取方法的所有形参类型并拼接方法参数
            me(thodCode.append("(");
            // getParameterTypes()以Class[]形式返回方法的每个参数的数据类型对应的Class对象
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                methodCode.append(parameterType.getName());
                methodCode.append(" ");
                methodCode.append("arg" + i);
                // 如果不是最后一个形参就要在它的后面加上一个逗号
                if(i != parameterTypes.length - 1){
                    methodCode.append(",");
                }
            }
            // 往方法体中添加代码
            methodCode.append("){System.out.println(11111); ");
            // 根据方法的返回值类型的简类名,动态的添加return语句
            String returnTypeSimpleName = method.getReturnType().getSimpleName();
            // 判断返回值类型
            if ("void".equals(returnTypeSimpleName)) {
            }else if("int".equals(returnTypeSimpleName)){
                methodCode.append("return 1;");
            }else if("String".equals(returnTypeSimpleName)){
                methodCode.append("return \"hello\";");
            }
            methodCode.append("}");
            System.out.println(methodCode);
            // 制造方法并添加到类中
            CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
            ctClass.addMethod(ctMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }

    });
    // 在内存中生成class并加载到JVM当中
    Class<?> clazz = ctClass.toClass();
    // 创建实现类的对象
    AccountDao accountDao = (AccountDao) clazz.newInstance();
    // 调用动态生成的AccountDao实现类的方法
    accountDao.insert("aaaaa");
    accountDao.delete();
    accountDao.update("aaaa", 1000.0);
    accountDao.selectByActno("aaaa");
}

MyBatis生成DAO接口实现类原理

MyBatis号称轻量级只需要一个jar包就行 , 所以它对javassist进行了二次包装,底层动态生成类的时候不需要再引入javassist的依赖

SqlSession类获取核心配置文件信息的方法

方法名功能
getConfiguration()获取MyBaits核心配置文件的configuration标签内的信息
getMappedStatement(sqlId)通过sql语句id获取mapper配置文件中的sql语句
getSqlCommandType()获取sql语句的枚举类型(增,删,改,查)

手写GenerateDaoProxy工具类

只要按照MaBatis的规定创建规范的XxxMapper.xml文件, MaBatis解析完xml文件就会为你动态生成dao接口的实现类

public interface AccountDao {
    // 根据账号查询账户信息
    Account selectByActno(String actno);
    // 更新账户信息.1表示更新成功,其他值表示失败
    int updateByActno(Account act);
    //其他增删的方法...
}

// dao实现类中的⽅法一般都是通过SqlSession对象调⽤增删改查等⽅法,没有任何业务逻辑
public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String arg0) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", arg0);
        return account;
    }

    @Override
    public int updateByActno(Account arg0) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateByActno",arg0);
        return count;
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.bank.dao.AccountDao">
    <select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
        select * from t_act where actno = #{actno}
    </select>

    <update id="updateByActno">
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
</mapper>

GenerateDaoProxy工具类(MyBatis框架的已写好),只要按照规范它就可以动态生成dao接口实现类并将实现类的对象创建出来返回

  • 方法体中所有代码片段的数据类型必须使用全类名: 如对于javassist来说必须要知道SqlSession是哪个包下的SqlSession类
  • 确定sqlid(namespace.id): sqlid由框架使用者提供具有多变性,所以MyBatis框架规定namespace必须是dao接口的全限定名称,Id必须是接口中的方法名
  • 确定sql语句类型: 使用sqlSession对象获取核心配置文件中configuration标签内的信息,然后通过sqlid获取sql语句的类型,确定执行sqlSession对象增删改查的方法
public class GenerateDaoProxy { 
    /**
      * @param daoInterface dao接口
      * @return dao接口实现类的实例化对象
      */
    public static Object generate(SqlSession sqlSession, Class daoInterface){
        // 类池
        ClassPool pool = ClassPool.getDefault();
        // 在内存中动态生成一个代理类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); 
        // 制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中所有的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            try {
                // Account selectByActno(String actno)--->public Account selectByActno(String arg0){代码; }
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName());
                methodCode.append(" ");
                methodCode.append(method.getName());
                methodCode.append("(");
                // 获取方法的所有形式参数列表的字节码对象
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getName());
                    methodCode.append(" ");
                    methodCode.append("arg" + i);
                    if(i != parameterTypes.length - 1){
                        methodCode.append(",");
                    }
                }
                methodCode.append(")");
                methodCode.append("{");

 // 生成SqlSession sqlSession = SqlSessionUtil.openSession();对于javassist来说必须知道SqlSession是哪个包下的SqlSession类
 methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
                
                // 生成return 接口的方法返回值类型 sqlSession.selectOne("sqlId", arg0);拼接方法时的形式参数用的是arg0
                String sqlId = daoInterface.getName() + "." + method.getName();
                // 通过sqlId判断sql语句的类型
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if (sqlCommandType == SqlCommandType.INSERT) {
                }
                if (sqlCommandType == SqlCommandType.DELETE) {
                }
                if (sqlCommandType == SqlCommandType.UPDATE) 
                    // 实现return sqlSession.update("account.updateByActno", arg0);
                    methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                }
                if (sqlCommandType == SqlCommandType.SELECT) {
                    // 实现return (Account) sqlSession.selectOne("account.selectByActno", agr0);
                    String returnType = method.getReturnType().getName();
                    // selectOne方法的返回值类型默认是object类型,需要我们强转为接口方法的返回值类型
                    methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
                }

                methodCode.append("}");
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }

        });

        // 创建对象
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

通过自己封装的GenerateDaoProxy工具类动态生成AccountDao的实现类AccountServiceImpl

public class AccountServiceImpl implements AccountService {
    // 手动编写AccountDao的实现类并创建对象返回
    private AccountDao accountDao = new AccountDaoImpl();

    // 通过自己封装的GenerateDaoProxy工具类动态生成AccountDao的实现类并创建好对象返回
    private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(), AccountDao.class);
    
    // 业务方法...

}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值