javassist implements interface 模拟mybatis 生成代理类

 

动态创建代理对象的工具类 

package com.wsd.util;

import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.session.SqlSession;

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

/**
 * 使用javassist库动态生成dao接口的实现类
 * @author: Mr.Wang
 * @create: 2023-07-08 18:44
 **/
public class ProxyUtil {

    public static Object getMyMapper(SqlSession sqlSession, Class daoInterface){
        //mybatis 依赖中内置了 javassist,不需要再引入javassist dependency
        //获取一个默认的 ClassPool 实例
        //Javassist 是一个 Java 字节码编辑库,它可以在运行时修改已加载的类或者生成新的类。
        // ClassPool 是 Javassist 的核心组件,它是一个类容器,负责存储和管理字节码(.class 文件)
        ClassPool pool = ClassPool.getDefault();
        // 生成代理类
        CtClass ctClass = pool.makeClass(daoInterface.getPackage().getName() + ".impl." + daoInterface.getSimpleName() + "Impl");
        // 接口
        CtClass ctInterface = pool.makeClass(daoInterface.getName());
        // 代理类实现接口
        ctClass.addInterface(ctInterface);
        // 获取所有的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            // 拼接方法的签名
            StringBuilder methodStr = new StringBuilder();
            String returnTypeName = method.getReturnType().getName();
            //返回值类型
            methodStr.append(returnTypeName);
            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("){");
            // 方法体当中的代码怎么写?
            // 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)
            String sqlId = daoInterface.getName() + "." + methodName;
            // 获取SqlCommondType 获取配置文件中该sql tag 的类型
            String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
            if ("SELECT".equals(sqlCommondTypeName)) {
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.wsd.util.SqlSessionUtil.openSession();");
                methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");
                methodStr.append("return (" + returnTypeName + ")obj;");
            } else if ("UPDATE".equals(sqlCommondTypeName)) {
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.wsd.util.SqlSessionUtil.openSession();");
                methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");
                methodStr.append("return count;");
            }
            methodStr.append("}");
            System.out.println(methodStr);
            try {
                // 创建CtMethod对象
                CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
                //添加访问修饰符public
                ctMethod.setModifiers(Modifier.PUBLIC);
                // 将方法添加到类
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        try {
            // 创建代理对象
            Class aClass = ctClass.toClass();
            Constructor defaultCon = aClass.getDeclaredConstructor();
            Object o = defaultCon.newInstance();
            return o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

 

pom.xml 




  4.0.0

  com.wsd
  web-mybatis01
  1.0-SNAPSHOT
  war

  web-mybatis01 Maven Webapp
  
  http://www.example.com

  
    UTF-8
    1.7
    1.7
  

  
    
    
      org.mybatis
      mybatis
      3.5.10
    
    
    
      mysql
      mysql-connector-java
      8.0.30
    
    
    
      ch.qos.logback
      logback-classic
      1.2.11
    
    
    
      javax.servlet
      javax.servlet-api
      4.0.1
      provided
    
  

  
    web-mybatis01
    
      
        
          maven-clean-plugin
          3.1.0
        
        
        
          maven-resources-plugin
          3.0.2
        
        
          maven-compiler-plugin
          3.8.0
        
        
          maven-surefire-plugin
          2.22.1
        
        
          maven-war-plugin
          3.2.2
        
        
          maven-install-plugin
          2.5.2
        
        
          maven-deploy-plugin
          2.8.2
        
      
    
  

dao interface

package com.wsd.dao;

import com.wsd.pojo.Account;

/**
 * @author: Mr.Wang
 * @create: 2023-07-02 01:30
 **/
public interface AccountDao {

    /**
     * 根据账号获取账户信息
     * @param actno 账号
     * @return 账户信息
     */
    Account selectByActno(String actno);

    /**
     * 更新账户信息
     * @param act 账户信息
     * @return 1表示更新成功,其他值表示失败
     */
    int update(Account act);
}

mapper.xml




    
    
        update t_act set balance = #{balance} where actno = #{actno}
    

mybatis 配置文件 


        



    
        
        
            
            
            
            
        
    


    

jdbc.properties 

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

获取sqlsession的工具类 

package com.wsd.util;

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


/**
 * @description: Utility class for mybatis
 * @author: Mr.Wang
 * @create: 2023-06-17 17:38
 **/

public class SqlSessionUtil {

    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;

    //保存sqlSession , 一个线程一个
    private static ThreadLocal sqlSessionThreadLocal = new ThreadLocal<>();


    /**
     * 类加载时初始化sqlSessionFactory对象
     */
    static {
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 每调用一次openSession()可获取一个新的会话。
     *
     * @return 新的会话对象
     */

    public static SqlSession openSession() {
        // Get a sqlSession instance from sqlSessionThreadLocal
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        //如果 sqlSessionThreadLocal 未获取到 sqlSession,让工厂生产一个新的sqlSession
        if(sqlSession == null){
            sqlSession = sqlSessionFactory.openSession();
            sqlSessionThreadLocal.set(sqlSession);
        }
        return sqlSession;
    }


    /**
     * @description close sqlSession and remove it from sqlSessionThreadLocal
     * @param sqlSession
     * @return
     */
    public static void close(SqlSession sqlSession) {
        if(sqlSession != null){
            sqlSession.close();
            //remove from sqlSessionThreadLocal
            sqlSessionThreadLocal.remove();
        }
    }
}

封装数据的 pojo  class

package com.wsd.pojo;

/**
 * @description: pojo for Account
 * @author: Mr.Wang
 * @create: 2023-07-01 23:41
 **/
public class Account {
    private Long id;
    private String actno;
    private Double balance;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    public Account() {
    }

    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}

service interface

package com.wsd.service;

import com.wsd.exception.AppException;
import com.wsd.exception.MoneyNotEnoughException;

public interface AccountService {


    void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException;
}

实现类

package com.wsd.service.impl;

import com.wsd.dao.AccountDao;
import com.wsd.exception.AppException;
import com.wsd.exception.MoneyNotEnoughException;
import com.wsd.pojo.Account;
import com.wsd.service.AccountService;
import com.wsd.util.ProxyUtil;
import com.wsd.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @author: Mr.Wang
 * @create: 2023-07-02 01:19
 **/
public class AccountServiceImpl implements AccountService {

    //使用工具类生成代理对象
    private AccountDao accountDao = (AccountDao) ProxyUtil.getMyMapper(SqlSessionUtil.openSession(), AccountDao.class);

    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{
        //创建sql session
        SqlSession sqlSession =  SqlSessionUtil.openSession();

        // 查询转出账户的余额
        Account fromAct = accountDao.selectByActno(fromActno);
        //余额不足,抛出异常
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("对不起,您的余额不足。");
        }

        // 程序如果执行到这里说明余额充足
        // 修改账户余额
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        // 更新数据库
        int count = accountDao.update(fromAct);

        /*//模拟异常
        String s = null;
        s.toString();*/

        count += accountDao.update(toAct);
        if (count != 2) {
            throw new AppException("转账失败,未知原因!");
        }

        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }
}

Exception

package com.wsd.exception;

/**
 * @author: Mr.Wang
 * @create: 2023-07-02 11:57
 **/
public class AppException extends Exception {

    public AppException() {
    }

    public AppException(String message) {
        super(message);
    }
}
package com.wsd.exception;

/**
 * @author: Mr.Wang
 * @create: 2023-07-02 09:06
 **/
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException() {
    }

    public MoneyNotEnoughException(String message) {
        super(message);
    }
}

servlet

package com.wsd.web;

import com.wsd.exception.AppException;
import com.wsd.exception.MoneyNotEnoughException;
import com.wsd.service.AccountService;
import com.wsd.service.impl.AccountServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @description:
 * @author: Mr.Wang
 * @create: 2023-07-02 00:52
 **/
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {

    private AccountService accountService = new AccountServiceImpl();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取页面表单数据
        String fromActno = req.getParameter("fromActno");
        String toActno = req.getParameter("toActno");
        Double money = Double.parseDouble( req.getParameter("money") ) ;
        //调用service
        try {
            accountService.transfer(fromActno,toActno,money);
            //Show the result after transfer success
            resp.sendRedirect(req.getContextPath()  + "/success.html");
        } catch (MoneyNotEnoughException e) {
            resp.sendRedirect(req.getContextPath()  + "/error1.html");
        } catch (Exception e) {
            resp.sendRedirect(req.getContextPath() + "/error2.html");
        }

    }
}

页面




    
    银行账户转账



转出账户:
转入账户:
转账金额:



    
    transfer result


The transfer failed because of insufficient balance




    
    transfer result


The transfer failed, error occurred




    
    transfer result


The transfer success

test:

table before test: 

javassist implements interface 模拟mybatis 生成代理类_第1张图片

javassist implements interface 模拟mybatis 生成代理类_第2张图片 

 javassist implements interface 模拟mybatis 生成代理类_第3张图片

 javassist implements interface 模拟mybatis 生成代理类_第4张图片

 

mybatis 框架中的sqlSession.getMapper(接口类) 可以生成代理对象

//sqlsession.getMapper生成代理对象
private AccountDao accountDao = (AccountDao) SqlSessionUtil.openSession().getMapper(AccountDao.class);
package com.wsd.service.impl;

import com.wsd.dao.AccountDao;
import com.wsd.exception.AppException;
import com.wsd.exception.MoneyNotEnoughException;
import com.wsd.pojo.Account;
import com.wsd.service.AccountService;
import com.wsd.util.ProxyUtil;
import com.wsd.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @author: Mr.Wang
 * @create: 2023-07-02 01:19
 **/
public class AccountServiceImpl implements AccountService {

    //sqlsession.getMapper生成代理对象
    private AccountDao accountDao = (AccountDao) SqlSessionUtil.openSession().getMapper(AccountDao.class);

    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{
        //创建sql session
        SqlSession sqlSession =  SqlSessionUtil.openSession();

        // 查询转出账户的余额
        Account fromAct = accountDao.selectByActno(fromActno);
        //余额不足,抛出异常
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("对不起,您的余额不足。");
        }

        // 程序如果执行到这里说明余额充足
        // 修改账户余额
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        // 更新数据库
        int count = accountDao.update(fromAct);

        /*//模拟异常
        String s = null;
        s.toString();*/

        count += accountDao.update(toAct);
        if (count != 2) {
            throw new AppException("转账失败,未知原因!");
        }

        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }
}

test:

javassist implements interface 模拟mybatis 生成代理类_第5张图片 

javassist implements interface 模拟mybatis 生成代理类_第6张图片 

 

 

你可能感兴趣的:(mybatis)