javassist的使用,以及使用javassist简单的实现MyBatis中的GenerateDaoProxy机制(getMapper方法)

本文详细阐述了如何使用javassist库配合MyBatis实现接口的动态生成,包括接口定义、生成实现类、SqlSession管理和DaoProxy的定制。
摘要由CSDN通过智能技术生成

1. 什么是javassist?

Javaassist 就是一个可以处理Java 字节码的类库,也就是说可以使用Javassist动态的生成接口的实现类。
可以在内存中动态的生成类。

2. 简单的使用javassist:这里只考虑通过接口简单的实现类

提供一个接口:

public interface AccountDao {
    void delete();
}
通过javassist来创建一个类,实现这个接口:
	第一步:获取类池,这个类池就是用来生成class的
	第二步:制造类
	第三步:生成接口
	第四步:添加接口到类中
	第五步:制造出来方法
	第六步:将方法加到类里
	第七步:实例化该类并调用方法
    public void testGenerateImpl() throws Exception{
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 生成类
        CtClass ctClass = pool.makeClass("AccountDaoImpl");
        // 生成接口
        CtClass ctInterface = pool.makeInterface("AccountDao");
        // 添加接口到类中
        ctClass.addInterface(ctInterface);
        // 实现接口中的方法
        // 制造方法,第一个参数为整个方法的字符串,第二位为装载的类对象
        CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(1233);}", ctClass);
        // 将方法加到类里
        ctClass.addMethod(ctMethod);
        // 生成类, 同时将生成的类加载到JVM中
        Class<?> clazz = ctClass.toClass();
        AccountDao account = (AccountDao) clazz.newInstance();
        account.delete();

    }

需要将配置添加到运行的配置中:
–add-opens java.base/java.lang=ALL-UNNAMED
–add-opens java.base/sun.net.util=ALL-UNNAMED
配置信息

3. 提供SqlSession的工具类

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

public class SqlSessionUtil {
    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    public static SqlSession openSession(){
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前的线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }
    // 关闭sqlSession对象
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            // 从当前线程中移除sqlSession对象和当前线程的绑定关系
            local.remove();
        }
    }
}

使用ThreadLocal的目的:为了方便在业务中进行事务控制,要保证同一个线程获取到的SqlSession是同一个SqlSession。
注意:这里的mybatis的配置文件的文件名和路径都是写死的。
文件名为:mybatis-config.xml, 路径为类资源加载的根路径。

4. 动态生成简单的Dao的实现类:

java pojo类:

public class Account {
    private Long id;
    private String actno;
    private Double balance;
    // 省略构造方法,get、set方法,toString方法等等
}

Dao接口:

public interface AccountDao {
    /**
     * 根据账号查询账户信息
     * @param actno 账号
     * @return 账户信息
     */
    Account selectByActno(String actno);

    /**
     * 根据账号更新账户信息
     * @param account 账号
     * @return 更新的记录条数
     */
    int updateByActno(Account account);
}

javassist实现接口:

import com.bjpowernode.bank.dao.AccountDao;
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.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.session.SqlSession;

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

/*
    动态生成Dao的实现类
 */
public class GenerateDaoProxy {

    /**
     * 生成dao的实现类,并将这个类的对象创建出来并返回
     * @param daoInterface 要是实现的dao接口
     * @return
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface){
        //类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClass = pool.makeClass(daoInterface+"Proxy");
        //制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        // 实现接口的方法
        ctClass.addInterface(ctInterface);
        // 将方法添加到类里
        Method[] methods = AccountDao.class.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
        	// 使用StringBuilder来拼接每一个方法体
            // method 是接口中的抽象方法
            // 把method抽像方法实现了
            CtMethod ctMethod = null;
            try {
            	// 这一部分是修饰符列表+返回值类型+方法名+形参列表
                StringBuilder methodStr = new StringBuilder();
                methodStr.append("public ");
                // 获取返回值类型
                String returnType = method.getReturnType().getName();
                methodStr.append(returnType+" ");
                // 获取方法名
                String name = method.getName();
                methodStr.append(name);
                methodStr.append("(");
                // 获取所有参数
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    String parameterTypeName = parameterTypes[i].getName();
                    methodStr.append(parameterTypeName);
                    methodStr.append(" ");
                    methodStr.append("arg" + i);
                    if(i != parameterTypes.length-1){
                        methodStr.append(",");
                    }
                }
                methodStr.append("){");
                // 这一部分是代码部分
                // 这一部门是死的,第一行都是这个
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = SqlSessionUtil.openSession();");
                // 需要知道是什么类型的sql语句
                /**
                 * sqlSession.getConfiguration() 获取xxxMapper内的配置
                 * sqlSession.getConfiguration().getMappedStatement("id")  通过这个id可以获取到这个mapper内的一个sql语句
                 * sqlSession.getConfiguration().getMappedStatement("id").getStatementType() 可以获取到是哪一种sql语句
                 * SqlCommandType是一个枚举
                 *      UNKNOWN,
                 *     INSERT,
                 *     UPDATE,
                 *     DELETE,
                 *     SELECT,
                 *     FLUSH;
                 * 但是sql语句的id是框架的使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道
                 * 既然我框架的开发者不知道sqlId,怎么办?mybatis框架的开发者就出台了一项规定:凡是使用GenerateDaoProxy机制的。
                 * sqlId不能随便写。namesapce必须是dao接口的全限定名称。id必须是接口种的方法名。
                 * 因此sqlId就是"接口名.方法名"
                 */
                String sqlId = daoInterface.getName() + "." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                System.out.println(sqlCommandType);
                if(sqlCommandType == SqlCommandType.INSERT){
                    methodStr.append("sqlSession.");
                }else if(sqlCommandType == SqlCommandType.DELETE){

                }else if(sqlCommandType == SqlCommandType.UPDATE){
                    methodStr.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                }else if(sqlCommandType == SqlCommandType.SELECT){
                    methodStr.append("return ("+method.getReturnType().getName()+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
                }


                methodStr.append("}");
                System.out.println(methodStr);
                // 方法添加到类中
                ctMethod = CtMethod.make(String.valueOf(methodStr), ctClass);
                ctClass.addMethod(ctMethod);

            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 加载类
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            // 创建类
            obj = clazz.newInstance();
        } catch (CannotCompileException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return obj;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值