javassist在mybatis中的应用

参考老杜mybatis

javassist是什么?

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以通过完全手动的方式去生成一个新的类对象。

首先引入依赖

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

使用javassist动态地在内存中生成一个类

public static void javassistCreateClass() throws Exception{
    
    
//        首先获取类池
        ClassPool classPool = ClassPool.getDefault();
//        使用类池创建类
        CtClass ctClass = classPool.makeClass("org.dongmu.mapper.CarMapperImpl");
//        创建方法
//        需要 返回值类型 方法名 形式参数列表 所属的类
        CtMethod ctMethod = new CtMethod(CtClass.voidType,"execute",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("execute");
//        调用类中的方法
        method.invoke(o);
    }

由于我这里使用的是spring17的版本所以这里如果允许以上代码需要添加两个参数

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

在这里插入图片描述
然后运行即可输出Hello World

javassist生成mapper接口的实现类

这里会首先和上面一样获取接口,然后创建实现类,遍历接口里面的方法然后在类中进行实现,只是在mybatis的mapper文件中,我们不知道用户的namespace是什么,所以获取不到对应的sql语句,这里就是因为获取不到,所以mybatis规定namespace必须是接口的全限定类名,并且要求mapper接口的路径和mapper.xml文件的路径要一样,接口里面的方法的名字必须和sql语句的id保持一致,这样我们就可以通过接口的信息以及接口中的方法命名获取到xml配置及文件中的内容了,这就是mybatis底层的实现原理。获取到了mappe文件中的标签封装成的MappedStatement对象了之后,就可以调用sqlsession进行相应的操作了。

public static Object javassistCreateClassImpl(Class mapperInterface) throws Exception{
    
    
//        获取类池
        ClassPool pool = ClassPool.getDefault();
        // ⽣成代理类
        CtClass ctClass = pool.makeClass(mapperInterface.getPackageName() + ".impl." + mapperInterface.getSimpleName() + "Impl");
//        在内存中创建接口
        CtClass ctClassInterface = pool.makeClass(mapperInterface.getName());
//        把代理类作为接口的实现类
        ctClass.addInterface(ctClassInterface);
//        获取接口中的所有需要实现的方法
        Method[] declaredMethods = mapperInterface.getDeclaredMethods();
//        在类中实现所有的方法
        Arrays.stream(declaredMethods).forEach(method -> {
    
    
            StringBuilder methodStr = new StringBuilder();
//            获取方法的返回值类型
            String name = method.getReturnType().getName();
            methodStr.append(name);
            methodStr.append(" ");
//            获取方法名
            String methodName = method.getName();
            methodStr.append(methodName);
            methodStr.append("(");

//            获取方法需要传递的参数
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
    
    
//                拼接需要传递的形式参数的类型
                methodStr.append(parameterTypes[i].getName());
                methodStr.append(" arg");
                methodStr.append(i);
                if (i!=parameterTypes.length-1){
    
    
                    methodStr.append(",");
                }
            }
            methodStr.append("){");

//            下面我们就需要写方法体中的参数了,但是具体的方法体中的内容是什么
//            在mybatis读取mapper的配置文件的时候,里面都是需要执行的sql语句,所以我们需要获取这些sql语句然后执行,但是怎么获取?
//            这里就是因为获取不到,所以mybatis规定namespace必须是接口的全限定类名,并且要求mapper接口的路径和mapper.xml文件的路径要一样,
//            接口里面的方法的名字必须和sql语句的id保持一致,这样我们就可以通过接口的信息以及接口中的方法命名获取到xml配置及文件中的内容了,这就是mybatis底层的原理

//            获取sql语句的id
            String sqlId = mapperInterface.getName() + "." + methodName;
//            前面我们说过mybatis把读取到的mapper文件里面的内容都会封装成一个MappedStatement对象放在一个map集合当中。map集合的key就是这里的sqlId,
//            所以在实际的程序当中这里可以获取到MappedStatement对象然后执行对应的sql语句。
//            这里为了测试方便只是简单地实现接口里面的方法,但是其实在mybatis底层原理上就是这么实现的
            methodStr.append("System.out.println(\"调用sqlSession实现了mapper配置文件中的sql语句\");}");
            try {
    
    
//                创建这个类的方法
                CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
//                设置方法的修饰符
                ctMethod.setModifiers(Modifier.PUBLIC);
//                将方法添加到类中
                ctClass.addMethod(ctMethod);
            } catch (CannotCompileException e) {
    
    
                throw new RuntimeException(e);
            }
        });

//        创建代理对象
        Class<?> aClass = ctClass.toClass();
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
        Object o = declaredConstructor.newInstance();
        return o;
    }

编写程序调用上面的函数进行测试,结果输出和预期相符。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45401910/article/details/128370916