Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD

Mybatis学习笔记3 在Web中应用Mybatis_biubiubiu0706的博客-CSDN博客

上篇最后在DAO实现类中,代码固定,没有业务逻辑,这篇笔记中对该实现类进行封装,就是说,以后不用写DAO实现类了

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第1张图片

我们不难发现,这个dao实现类中的⽅法代码很固定,基本上就是⼀⾏代码,通过SqlSession对象调⽤
insert、delete、update、select等⽅法,这个类中的⽅法没有任何业务逻辑,既然是这样, 这个类我们
能不能动态的⽣成 ,以后可以不写这个类吗?答案:可以。
Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学
系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤
Javassist对字节码操作为JBoss实现动态"AOP"框架。
新建moudle测试javassist
Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第2张图片
Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第3张图片
Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第4张图片
引入依赖


    4.0.0

    org.example
    javassist
    1.0-SNAPSHOT

    
        8
        8
    

    
        
        
            org.javassist
            javassist
            3.29.1-GA
        
        
        
            junit
            junit
            4.13.2
            test
        
    

下面是几个javassist基本使用的demo

import javassist.*;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author hrui
 * @date 2023/9/17 18:05
 */
public class Test {


    @org.junit.Test
    public void test03() throws Exception {
        // 获取类池,这个类池就是用来生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(需要告诉javassist,类名是啥)
        CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中

        //制造方法
        String methodCode="public void insert(){System.out.println(\"Hello World\");}";
        CtMethod ctMethod=CtMethod.make(methodCode,ctClass);

        // 给类添加⽅法
        ctClass.addMethod(ctMethod);
        //写入磁盘
        ctClass.writeFile("src/main/java/");


        //还可以这样   下面是可以加载到的,加载不到原因可能是我当时没有放到测试目录
//      Class aClass = Class.forName("com.example.javassist.Test");//这样加载不到类
        //用自定义类加载器
        ClassLoader customClassLoader = new URLClassLoader(new URL[]{new File("src/main/java/").toURI().toURL()});
        Class aClass = customClassLoader.loadClass("com.example.javassist.Test");
        Object instance = aClass.newInstance();
        // 调用生成的方法
        Method method = aClass.getDeclaredMethod("insert");
        method.invoke(instance);

    }
    @org.junit.Test
    public void test02() throws Exception {
        // 获取类池,这个类池就是用来生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(需要告诉javassist,类名是啥)
        CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
        //制造方法
        String methodCode="public void insert(){System.out.println(123);}";
        CtMethod ctMethod=CtMethod.make(methodCode,ctClass);

        // 给类添加⽅法
        ctClass.addMethod(ctMethod);

        // 用反射调⽤⽅法  这样是内存中直接调用
        Class aClass = ctClass.toClass();
        Object o = aClass.newInstance();
        Method method = aClass.getDeclaredMethod("insert");
        method.invoke(o);
    }


    @org.junit.Test
    public void test01() throws Exception {
        // 获取类池,这个类池就是用来生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(需要告诉javassist,类名是啥)
        CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
        // 制造⽅法
        // 1.返回值类型 2.⽅法名 3.形式参数列表 4.所属类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "insert", new
                CtClass[]{}, ctClass);
        // 设置⽅法的修饰符列表
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置⽅法体
        ctMethod.setBody("{System.out.println(\"hello world\");}");

        // 给类添加⽅法
        ctClass.addMethod(ctMethod);

        // 用反射调⽤⽅法  这样是内存中直接调用
        Class aClass = ctClass.toClass();
        Object o = aClass.newInstance();
        Method method = aClass.getDeclaredMethod("insert");
        method.invoke(o);
    }
}

因本机安装的是JDK8,没有报错,将高版本报错情况记录下来,以便以后用高版本JDK遇到类似问题,方便解决

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第5张图片

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第6张图片

解决办法

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第7张图片

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第8张图片

两个参数
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
如果第一次用可能不能设置参数,如下点击设置
Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第9张图片
高版本JDK遇到如上问题,记得仔细配置,可能第一次配置完还是有错,记得仔细检查,重新配置一次

设想一个问题  既然这样的话,能不能用javassist动态设计一个类,然后实现DAO接口呢

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第10张图片

@org.junit.Test
    public void testGenerateImpl() throws Exception {
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl");//在内存中
        //制造接口
        CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
        //类去实现接口  又可以说是类去添加接口
        ctClassImpl.addInterface(ctClassInterface);//TestImpl implements Test1
        //去实现接口中的方法 这个相对比较复杂,这里我们假设接口里就一个方法

        //先制造方法
        CtMethod ctMethod=CtMethod.make("public void delete(){System.out.println(\"删除成功!\");}",ctClassImpl);
        //将方法添加到类中
        ctClassImpl.addMethod(ctMethod);

        //在内存中生成类(这一步JVM已经将类加载到内存中)
        Class aClass = ctClassImpl.toClass();
        Test1 test=(Test1)aClass.newInstance();
        test.delete();

    }

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第11张图片

上面方法示例可见,我们是有办法动态生成接口实现类的

上面方法演示过于简单

比如说

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第12张图片

下面使用javassist动态生成实现类并实现接口中所有方法

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第13张图片

 @org.junit.Test
    public void testGenerateImpl2(){
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
        //制造接口
        CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
        //类去实现接口  又可以说是类去添加接口
        ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
        //实现接口中所有方法
        //先获取接口中的所有方法
        Method[] declaredMethods = Test1.class.getDeclaredMethods();
        //System.out.println(declaredMethods.length);
        //System.out.println(Arrays.toString(declaredMethods));
        Arrays.stream(declaredMethods).forEach(method -> {
            //method是接口中的抽象方法,我们需要把抽象方法实现了
            try {
                //methodCode public void delete(){}
                //methodCode public int update(String actno,double balance)
                StringBuilder methodCode=new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName()+" ");//返回值类型
                methodCode.append(method.getName());//追加方法名
                methodCode.append("(");

                Class[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
                for(int i=0;i 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(\"Hello World\");}");
                System.out.println(methodCode);

                //CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
                //将方法添加到类中
                //ctClassImpl.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

程序写到这部  先执行一下

public int update(java.lang.String arg0,double arg1){System.out.println("Hello World");}
public void delete(){System.out.println("Hello World");}
public int insert(java.lang.String arg0){System.out.println("Hello World");}
public java.lang.String selectByActno(java.lang.String arg0){System.out.println("Hello World");}

接下来就是关于返回值的问题

  @org.junit.Test
    public void testGenerateImpl2() throws IllegalAccessException, InstantiationException, CannotCompileException {
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
        //制造接口
        CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
        //类去实现接口  又可以说是类去添加接口
        ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
        //实现接口中所有方法
        //先获取接口中的所有方法
        Method[] declaredMethods = Test1.class.getDeclaredMethods();
        //System.out.println(declaredMethods.length);
        //System.out.println(Arrays.toString(declaredMethods));
        Arrays.stream(declaredMethods).forEach(method -> {
            //method是接口中的抽象方法,我们需要把抽象方法实现了
            try {
                //methodCode public void delete(){}
                //methodCode public int update(String actno,double balance)
                StringBuilder methodCode=new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName()+" ");//返回值类型
                methodCode.append(method.getName());//追加方法名
                methodCode.append("(");

                Class[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
                for(int i=0;i 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(\"Hello World\");");
                //动态添加renturn语句
                String simpleName = method.getReturnType().getSimpleName();//比如  int void  String
                if("void".equals(simpleName)){
                    //如果是void啥都不写
                }else if("int".equals(simpleName)){
                    methodCode.append("return 1;");
                }else if("String".equals(simpleName)){
                    methodCode.append("return \"1\";");
                }
                //System.out.println(simpleName);
                methodCode.append("}");
                System.out.println(methodCode);

                CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
                //将方法添加到类中
                ctClassImpl.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        //在内存中生成类(这一步JVM已经将类加载到内存中)
        Class aClass = ctClassImpl.toClass();
        Test1 test=(Test1)aClass.newInstance();
        test.delete();
        int count = test.insert("sad");
        test.update("sad",1.1);
        

    }

上面方式虽然实现的比较low,主要为说明Mybatis底层javassist的使用

下面对

Mybatis学习笔记3 在Web中应用Mybatis_biubiubiu0706的博客-CSDN博客

中的项目进行修改,就是说AccountDaoImpl不写了

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第14张图片

mybatis-03中只引入了mybatis依赖,但是可以直接使用ClassPool,而这个类是javassist的,原因是mybatis对javassist进行了封装

下面这段代码就是大概性的介绍Mybatis内部的一种封装    

package com.example.utils;

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.session.SqlSession;

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

/**
 * 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
 * 注意注意注意注意注意!!!!!!:
 *      凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
 */
public class GenerateDaoProxy { // GenerateDaoProxy是mybatis框架的开发者写的。

    /**
     * 生成dao接口实现类,并且将实现类的对象创建出来并返回。
     * @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 -> {
            // method是接口中的抽象方法
            // 将method这个抽象方法进行实现
            try {
                // Account selectByActno(String actno);
                // public Account selectByActno(String actno){ 代码; }
                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("{");
                // 需要方法体当中的代码
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.example.utils.SqlSessionUtil.openSession();");
                // 需要知道是什么类型的sql语句
                // sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。
                // 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的。
                // sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。
                String sqlId = daoInterface.getName() + "." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if (sqlCommandType == SqlCommandType.INSERT) {

                }
                if (sqlCommandType == SqlCommandType.DELETE) {

                }
                if (sqlCommandType == SqlCommandType.UPDATE) {
                    methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                }
                if (sqlCommandType == SqlCommandType.SELECT) {
                    String returnType = method.getReturnType().getName();
                    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;
    }

}

那么业务层就可以这么写

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第15张图片

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第16张图片

上面这步,出了点错,写完时候还没查出来

其实这个封装Mybatis已经做好了

Mybatis学习笔记4 用javassist动态实现DAO接口基于接口的CRUD_第17张图片

这样,面向接口的CRUD就产生了,以后无需再写持久层的实现类

完整的SqlSessionUtil类

package com.example.utils;

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;

/**
 * @author hrui
 * @date 2023/9/8 14:55
 */
public class SqlSessionUtil {

    //工具类的构造方法一般都是私有化
    //方法都是静态的
    //为了防止new对象,构造方法私有化
    private SqlSessionUtil(){

    }
    private static SqlSessionFactory sqlSessionFactory;

    //类加载时候执行
    //SqlSessionUtil工具类在被加载的时候,解析mybatis-config1.xml.创建sqlSessionFactory对象
    static{
        try {
            //SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
            //下面这么写的原因是SqlSessionFactoryBuilder就是为了创建sqlSessionFactory而来的,使用完后,就不需要,都不需要创建个局部变量
            //一个sqlSessionFactory对应一个数据库
            sqlSessionFactory= new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config1.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //全局的 服务器级别的,一个服务器当中定义一个即可
    private static ThreadLocal local=new ThreadLocal<>();

    //获取会话对象 返回会话对象
    public static SqlSession openSession(){
        SqlSession sqlSession=local.get();
        if(sqlSession==null){
            sqlSession = sqlSessionFactory.openSession();
            local.set(sqlSession);
        }

        return sqlSession;
    }

    //提供一个关闭的方法
    public static void close(SqlSession sqlSession){
        if(sqlSession!=null){
            //因为核心配置文件中配置POOLED  这里关闭是交还给连接池
            sqlSession.close();
            //注意移除SqlSession对象和当前线程的绑定关系
            //因为Tomcat服务器支持线程池 比如说t1线程用完了,close交还给连接池了,这个sqlSession属于不可用的状态,你没有remove出去 如果t2线程拿到了,那么这个sqlSession不可用
            local.remove();
        }
    }
}

你可能感兴趣的:(mybatis,学习,笔记)