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


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

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

    我们不难发现,这个dao实现类中的⽅法代码很固定,基本上就是⼀⾏代码,通过SqlSession对象调⽤
    insert、delete、update、select等⽅法,这个类中的⽅法没有任何业务逻辑,既然是这样, 这个类我们
    能不能动态的⽣成 ,以后可以不写这个类吗?答案:可以。
    Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学
    系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤
    Javassist对字节码操作为JBoss实现动态"AOP"框架。
    新建moudle测试javassist
    引入依赖
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <modelVersion>4.0.0</modelVersion>
    6. <groupId>org.example</groupId>
    7. <artifactId>javassist</artifactId>
    8. <version>1.0-SNAPSHOT</version>
    9. <properties>
    10. <maven.compiler.source>8</maven.compiler.source>
    11. <maven.compiler.target>8</maven.compiler.target>
    12. </properties>
    13. <dependencies>
    14. <!--javassist依赖-->
    15. <dependency>
    16. <groupId>org.javassist</groupId>
    17. <artifactId>javassist</artifactId>
    18. <version>3.29.1-GA</version>
    19. </dependency>
    20. <!--junit依赖-->
    21. <dependency>
    22. <groupId>junit</groupId>
    23. <artifactId>junit</artifactId>
    24. <version>4.13.2</version>
    25. <scope>test</scope>
    26. </dependency>
    27. </dependencies>
    28. </project>

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

    1. import javassist.*;
    2. import java.io.File;
    3. import java.lang.reflect.Method;
    4. import java.net.URL;
    5. import java.net.URLClassLoader;
    6. /**
    7. * @author hrui
    8. * @date 2023/9/17 18:05
    9. */
    10. public class Test {
    11. @org.junit.Test
    12. public void test03() throws Exception {
    13. // 获取类池,这个类池就是用来生成class
    14. ClassPool pool = ClassPool.getDefault();
    15. // 制造类(需要告诉javassist,类名是啥)
    16. CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
    17. //制造方法
    18. String methodCode="public void insert(){System.out.println(\"Hello World\");}";
    19. CtMethod ctMethod=CtMethod.make(methodCode,ctClass);
    20. // 给类添加⽅法
    21. ctClass.addMethod(ctMethod);
    22. //写入磁盘
    23. ctClass.writeFile("src/main/java/");
    24. //还可以这样 下面是可以加载到的,加载不到原因可能是我当时没有放到测试目录
    25. // Class<?> aClass = Class.forName("com.example.javassist.Test");//这样加载不到类
    26. //用自定义类加载器
    27. ClassLoader customClassLoader = new URLClassLoader(new URL[]{new File("src/main/java/").toURI().toURL()});
    28. Class<?> aClass = customClassLoader.loadClass("com.example.javassist.Test");
    29. Object instance = aClass.newInstance();
    30. // 调用生成的方法
    31. Method method = aClass.getDeclaredMethod("insert");
    32. method.invoke(instance);
    33. }
    34. @org.junit.Test
    35. public void test02() throws Exception {
    36. // 获取类池,这个类池就是用来生成class
    37. ClassPool pool = ClassPool.getDefault();
    38. // 制造类(需要告诉javassist,类名是啥)
    39. CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
    40. //制造方法
    41. String methodCode="public void insert(){System.out.println(123);}";
    42. CtMethod ctMethod=CtMethod.make(methodCode,ctClass);
    43. // 给类添加⽅法
    44. ctClass.addMethod(ctMethod);
    45. // 用反射调⽤⽅法 这样是内存中直接调用
    46. Class<?> aClass = ctClass.toClass();
    47. Object o = aClass.newInstance();
    48. Method method = aClass.getDeclaredMethod("insert");
    49. method.invoke(o);
    50. }
    51. @org.junit.Test
    52. public void test01() throws Exception {
    53. // 获取类池,这个类池就是用来生成class
    54. ClassPool pool = ClassPool.getDefault();
    55. // 制造类(需要告诉javassist,类名是啥)
    56. CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
    57. // 制造⽅法
    58. // 1.返回值类型 2.⽅法名 3.形式参数列表 4.所属类
    59. CtMethod ctMethod = new CtMethod(CtClass.voidType, "insert", new
    60. CtClass[]{}, ctClass);
    61. // 设置⽅法的修饰符列表
    62. ctMethod.setModifiers(Modifier.PUBLIC);
    63. // 设置⽅法体
    64. ctMethod.setBody("{System.out.println(\"hello world\");}");
    65. // 给类添加⽅法
    66. ctClass.addMethod(ctMethod);
    67. // 用反射调⽤⽅法 这样是内存中直接调用
    68. Class<?> aClass = ctClass.toClass();
    69. Object o = aClass.newInstance();
    70. Method method = aClass.getDeclaredMethod("insert");
    71. method.invoke(o);
    72. }
    73. }

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

    解决办法

    两个参数
    --add-opens java.base/java.lang=ALL-UNNAMED
    --add-opens java.base/sun.net.util=ALL-UNNAMED
    如果第一次用可能不能设置参数,如下点击设置
    高版本JDK遇到如上问题,记得仔细配置,可能第一次配置完还是有错,记得仔细检查,重新配置一次

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

    1. @org.junit.Test
    2. public void testGenerateImpl() throws Exception {
    3. //获取类池
    4. ClassPool pool = ClassPool.getDefault();
    5. //制造类
    6. CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl");//在内存中
    7. //制造接口
    8. CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
    9. //类去实现接口 又可以说是类去添加接口
    10. ctClassImpl.addInterface(ctClassInterface);//TestImpl implements Test1
    11. //去实现接口中的方法 这个相对比较复杂,这里我们假设接口里就一个方法
    12. //先制造方法
    13. CtMethod ctMethod=CtMethod.make("public void delete(){System.out.println(\"删除成功!\");}",ctClassImpl);
    14. //将方法添加到类中
    15. ctClassImpl.addMethod(ctMethod);
    16. //在内存中生成类(这一步JVM已经将类加载到内存中)
    17. Class<?> aClass = ctClassImpl.toClass();
    18. Test1 test=(Test1)aClass.newInstance();
    19. test.delete();
    20. }

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

    上面方法演示过于简单

    比如说

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

    1. @org.junit.Test
    2. public void testGenerateImpl2(){
    3. //获取类池
    4. ClassPool pool = ClassPool.getDefault();
    5. //制造类
    6. CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
    7. //制造接口
    8. CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
    9. //类去实现接口 又可以说是类去添加接口
    10. ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
    11. //实现接口中所有方法
    12. //先获取接口中的所有方法
    13. Method[] declaredMethods = Test1.class.getDeclaredMethods();
    14. //System.out.println(declaredMethods.length);
    15. //System.out.println(Arrays.toString(declaredMethods));
    16. Arrays.stream(declaredMethods).forEach(method -> {
    17. //method是接口中的抽象方法,我们需要把抽象方法实现了
    18. try {
    19. //methodCode public void delete(){}
    20. //methodCode public int update(String actno,double balance)
    21. StringBuilder methodCode=new StringBuilder();
    22. methodCode.append("public ");
    23. methodCode.append(method.getReturnType().getName()+" ");//返回值类型
    24. methodCode.append(method.getName());//追加方法名
    25. methodCode.append("(");
    26. Class<?>[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
    27. for(int i=0;i<parameterTypes.length;i++){
    28. Class<?> parameterType = parameterTypes[i];//第一个参数的类型
    29. methodCode.append(parameterType.getName());//参数类型
    30. methodCode.append(" ");
    31. methodCode.append("arg"+i);//参数名
    32. if(i!=(parameterTypes.length-1)) {
    33. methodCode.append(",");//如果不是最后一个参数
    34. }
    35. }
    36. methodCode.append("){System.out.println(\"Hello World\");}");
    37. System.out.println(methodCode);
    38. //CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
    39. //将方法添加到类中
    40. //ctClassImpl.addMethod(ctMethod);
    41. } catch (Exception e) {
    42. e.printStackTrace();
    43. }
    44. });
    45. }

    程序写到这部  先执行一下

    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");}

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

    1. @org.junit.Test
    2. public void testGenerateImpl2() throws IllegalAccessException, InstantiationException, CannotCompileException {
    3. //获取类池
    4. ClassPool pool = ClassPool.getDefault();
    5. //制造类
    6. CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
    7. //制造接口
    8. CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
    9. //类去实现接口 又可以说是类去添加接口
    10. ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
    11. //实现接口中所有方法
    12. //先获取接口中的所有方法
    13. Method[] declaredMethods = Test1.class.getDeclaredMethods();
    14. //System.out.println(declaredMethods.length);
    15. //System.out.println(Arrays.toString(declaredMethods));
    16. Arrays.stream(declaredMethods).forEach(method -> {
    17. //method是接口中的抽象方法,我们需要把抽象方法实现了
    18. try {
    19. //methodCode public void delete(){}
    20. //methodCode public int update(String actno,double balance)
    21. StringBuilder methodCode=new StringBuilder();
    22. methodCode.append("public ");
    23. methodCode.append(method.getReturnType().getName()+" ");//返回值类型
    24. methodCode.append(method.getName());//追加方法名
    25. methodCode.append("(");
    26. Class<?>[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
    27. for(int i=0;i<parameterTypes.length;i++){
    28. Class<?> parameterType = parameterTypes[i];//第一个参数的类型
    29. methodCode.append(parameterType.getName());//参数类型
    30. methodCode.append(" ");
    31. methodCode.append("arg"+i);//参数名
    32. if(i!=(parameterTypes.length-1)) {
    33. methodCode.append(",");//如果不是最后一个参数
    34. }
    35. }
    36. methodCode.append("){System.out.println(\"Hello World\");");
    37. //动态添加renturn语句
    38. String simpleName = method.getReturnType().getSimpleName();//比如 int void String
    39. if("void".equals(simpleName)){
    40. //如果是void啥都不写
    41. }else if("int".equals(simpleName)){
    42. methodCode.append("return 1;");
    43. }else if("String".equals(simpleName)){
    44. methodCode.append("return \"1\";");
    45. }
    46. //System.out.println(simpleName);
    47. methodCode.append("}");
    48. System.out.println(methodCode);
    49. CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
    50. //将方法添加到类中
    51. ctClassImpl.addMethod(ctMethod);
    52. } catch (Exception e) {
    53. e.printStackTrace();
    54. }
    55. });
    56. //在内存中生成类(这一步JVM已经将类加载到内存中)
    57. Class<?> aClass = ctClassImpl.toClass();
    58. Test1 test=(Test1)aClass.newInstance();
    59. test.delete();
    60. int count = test.insert("sad");
    61. test.update("sad",1.1);
    62. }

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

    下面对

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

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

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

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

    1. package com.example.utils;
    2. import org.apache.ibatis.javassist.ClassPool;
    3. import org.apache.ibatis.javassist.CtClass;
    4. import org.apache.ibatis.javassist.CtMethod;
    5. import org.apache.ibatis.mapping.SqlCommandType;
    6. import org.apache.ibatis.session.SqlSession;
    7. import java.lang.reflect.Method;
    8. import java.util.Arrays;
    9. /**
    10. * 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
    11. * 注意注意注意注意注意!!!!!!:
    12. * 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
    13. */
    14. public class GenerateDaoProxy { // GenerateDaoProxy是mybatis框架的开发者写的。
    15. /**
    16. * 生成dao接口实现类,并且将实现类的对象创建出来并返回。
    17. * @param daoInterface dao接口
    18. * @return dao接口实现类的实例化对象。
    19. */
    20. public static Object generate(SqlSession sqlSession, Class daoInterface){
    21. // 类池
    22. ClassPool pool = ClassPool.getDefault();
    23. // 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
    24. CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类。
    25. // 制造接口
    26. CtClass ctInterface = pool.makeInterface(daoInterface.getName());
    27. // 实现接口
    28. ctClass.addInterface(ctInterface);
    29. // 实现接口中所有的方法
    30. Method[] methods = daoInterface.getDeclaredMethods();
    31. Arrays.stream(methods).forEach(method -> {
    32. // method是接口中的抽象方法
    33. //method这个抽象方法进行实现
    34. try {
    35. // Account selectByActno(String actno);
    36. // public Account selectByActno(String actno){ 代码; }
    37. StringBuilder methodCode = new StringBuilder();
    38. methodCode.append("public ");
    39. methodCode.append(method.getReturnType().getName());
    40. methodCode.append(" ");
    41. methodCode.append(method.getName());
    42. methodCode.append("(");
    43. // 需要方法的形式参数列表
    44. Class<?>[] parameterTypes = method.getParameterTypes();
    45. for (int i = 0; i < parameterTypes.length; i++) {
    46. Class<?> parameterType = parameterTypes[i];
    47. methodCode.append(parameterType.getName());
    48. methodCode.append(" ");
    49. methodCode.append("arg" + i);
    50. if(i != parameterTypes.length - 1){
    51. methodCode.append(",");
    52. }
    53. }
    54. methodCode.append(")");
    55. methodCode.append("{");
    56. // 需要方法体当中的代码
    57. methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.example.utils.SqlSessionUtil.openSession();");
    58. // 需要知道是什么类型的sql语句
    59. // sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。
    60. // 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的。
    61. // sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。
    62. String sqlId = daoInterface.getName() + "." + method.getName();
    63. SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
    64. if (sqlCommandType == SqlCommandType.INSERT) {
    65. }
    66. if (sqlCommandType == SqlCommandType.DELETE) {
    67. }
    68. if (sqlCommandType == SqlCommandType.UPDATE) {
    69. methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
    70. }
    71. if (sqlCommandType == SqlCommandType.SELECT) {
    72. String returnType = method.getReturnType().getName();
    73. methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
    74. }
    75. methodCode.append("}");
    76. CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
    77. ctClass.addMethod(ctMethod);
    78. } catch (Exception e) {
    79. e.printStackTrace();
    80. }
    81. });
    82. // 创建对象
    83. Object obj = null;
    84. try {
    85. Class<?> clazz = ctClass.toClass();
    86. obj = clazz.newInstance();
    87. } catch (Exception e) {
    88. e.printStackTrace();
    89. }
    90. return obj;
    91. }
    92. }

    那么业务层就可以这么写

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

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

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

    完整的SqlSessionUtil类

    1. package com.example.utils;
    2. import org.apache.ibatis.io.Resources;
    3. import org.apache.ibatis.session.SqlSession;
    4. import org.apache.ibatis.session.SqlSessionFactory;
    5. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    6. import java.io.IOException;
    7. /**
    8. * @author hrui
    9. * @date 2023/9/8 14:55
    10. */
    11. public class SqlSessionUtil {
    12. //工具类的构造方法一般都是私有化
    13. //方法都是静态的
    14. //为了防止new对象,构造方法私有化
    15. private SqlSessionUtil(){
    16. }
    17. private static SqlSessionFactory sqlSessionFactory;
    18. //类加载时候执行
    19. //SqlSessionUtil工具类在被加载的时候,解析mybatis-config1.xml.创建sqlSessionFactory对象
    20. static{
    21. try {
    22. //SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
    23. //下面这么写的原因是SqlSessionFactoryBuilder就是为了创建sqlSessionFactory而来的,使用完后,就不需要,都不需要创建个局部变量
    24. //一个sqlSessionFactory对应一个数据库
    25. sqlSessionFactory= new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config1.xml"));
    26. } catch (IOException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. //全局的 服务器级别的,一个服务器当中定义一个即可
    31. private static ThreadLocal<SqlSession> local=new ThreadLocal<>();
    32. //获取会话对象 返回会话对象
    33. public static SqlSession openSession(){
    34. SqlSession sqlSession=local.get();
    35. if(sqlSession==null){
    36. sqlSession = sqlSessionFactory.openSession();
    37. local.set(sqlSession);
    38. }
    39. return sqlSession;
    40. }
    41. //提供一个关闭的方法
    42. public static void close(SqlSession sqlSession){
    43. if(sqlSession!=null){
    44. //因为核心配置文件中配置POOLED 这里关闭是交还给连接池
    45. sqlSession.close();
    46. //注意移除SqlSession对象和当前线程的绑定关系
    47. //因为Tomcat服务器支持线程池 比如说t1线程用完了,close交还给连接池了,这个sqlSession属于不可用的状态,你没有remove出去 如果t2线程拿到了,那么这个sqlSession不可用
    48. local.remove();
    49. }
    50. }
    51. }

  • 相关阅读:
    [machineLearning]非监督学习unsupervised learning
    K8S架构常用组件核心资源
    valid ready握手无气泡
    盘点年度最佳10大顶级绘图软件,满足你99%的图表需求,赶紧收藏
    【3】Spring Boot 3 集成mybatis-plus+druid+mysql
    Idea提升工作效率的必备技巧
    商简智能:世界领先的高级计划与排程APS供应商
    BEV经典之作Lift, Splat, Shoot解析
    Nginx显示500错误原因和解决方法
    Windowsold文件夹作用以及删除方法
  • 原文地址:https://blog.csdn.net/tiantiantbtb/article/details/132948303