The use of javassist, and the simple implementation of the GenerateDaoProxy mechanism in MyBatis using javassist
-
- 1. What is javassist?
- 2. Simply use javassist: here we only consider simple implementation classes through interfaces
- 3. Provide tool classes for SqlSession
- 4. Dynamically generate a simple Dao implementation class:
1. What is javassist?
Javaassist is a class library that can process Java bytecode, which means you can use Javassist to dynamically generate implementation classes of interfaces. Classes can be generated dynamically in memory.
2. Simply use javassist: here we only consider simple implementation classes through interfaces
Provide an interface:
public interface AccountDao {<!-- --> void delete(); }
Create a class through javassist and implement this interface: The first step: Get the class pool. This class pool is used to generate classes. Step 2: Manufacturing Step 3: Generate interface Step 4: Add the interface to the class Step 5: How to make it Step 6: Add the method to the class Step 7: Instantiate the class and call the method
public void testGenerateImpl() throws Exception{<!-- --> // Get the class pool ClassPool pool = ClassPool.getDefault(); // Generate class CtClass ctClass = pool.makeClass("AccountDaoImpl"); // Generate interface CtClass ctInterface = pool.makeInterface("AccountDao"); //Add interface to class ctClass.addInterface(ctInterface); // Implement methods in the interface // Manufacturing method, the first parameter is the string of the entire method, and the second parameter is the loaded class object CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(1233);}", ctClass); //Add the method to the class ctClass.addMethod(ctMethod); // Generate classes and load the generated classes into the JVM Class<?> clazz = ctClass.toClass(); AccountDao account = (AccountDao) clazz.newInstance(); account.delete(); }
The configuration needs to be added to the running configuration:
–add-opens java.base/java.lang=ALL-UNNAMED
–add-opens java.base/sun.net.util=ALL-UNNAMED
3. Provide tool classes for 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(); // Bind the sqlSession object to the current thread local.set(sqlSession); } return sqlSession; } //Close the sqlSession object public static void close(SqlSession sqlSession){<!-- --> if (sqlSession != null) {<!-- --> sqlSession.close(); //Remove the binding relationship between the sqlSession object and the current thread from the current thread local.remove(); } } }
The purpose of using ThreadLocal: In order to facilitate transaction control in the business, it is necessary to ensure that the SqlSession obtained by the same thread is the same SqlSession.
Note: The file name and path of the mybatis configuration file here are hard-coded.
The file name is: mybatis-config.xml, and the path is the root path where class resources are loaded.
4. Dynamically generate a simple Dao implementation class:
java pojo class:
public class Account {<!-- --> private Long id; private String actno; private Double balance; // Omit construction methods, get, set methods, toString methods, etc. }
Dao interface:
public interface AccountDao {<!-- --> /** * Query account information based on account number * @param actno account * @return account information */ Account selectByActno(String actno); /** * Update account information based on account number * @param account account number * @return The number of updated records */ int updateByActno(Account account); }
javassist implements the interface:
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; /* Dynamically generate Dao implementation class */ public class GenerateDaoProxy {<!-- --> /** * Generate the implementation class of dao, create and return the object of this class * @param daoInterface If the dao interface is implemented * @return */ public static Object generate(SqlSession sqlSession, Class daoInterface){<!-- --> //class pool ClassPool pool = ClassPool.getDefault(); //manufacturing class CtClass ctClass = pool.makeClass(daoInterface + "Proxy"); //Manufacturing interface CtClass ctInterface = pool.makeInterface(daoInterface.getName()); //Methods to implement the interface ctClass.addInterface(ctInterface); //Add method to class Method[] methods = AccountDao.class.getDeclaredMethods(); Arrays.stream(methods).forEach(method -> {<!-- --> // Use StringBuilder to splice each method body // method is an abstract method in the interface // Implement the method abstract method CtMethod ctMethod = null; try {<!-- --> // This part is the modifier list + return value type + method name + formal parameter list StringBuilder methodStr = new StringBuilder(); methodStr.append("public "); // Get the return value type String returnType = method.getReturnType().getName(); methodStr.append(returnType + " "); // Get method name String name = method.getName(); methodStr.append(name); methodStr.append("("); // Get all parameters 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("){"); // This part is the code part // This part is dead, the first line is this methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = SqlSessionUtil.openSession();"); // Need to know what type of sql statement it is /** * sqlSession.getConfiguration() gets the configuration in xxxMapper * sqlSession.getConfiguration().getMappedStatement("id") You can get a sql statement in this mapper through this id * sqlSession.getConfiguration().getMappedStatement("id").getStatementType() can get which sql statement it is *SqlCommandType is an enumeration * UNKNOWN, *INSERT, *UPDATE, *DELETE, *SELECT, *FLUSH; * However, the ID of the SQL statement is provided by the user of the framework and is changeable. For developers of my framework. I have no idea * Since the developer of my framework does not know the sqlId, what should I do? The developers of the mybatis framework have issued a rule: Anyone who uses the GenerateDaoProxy mechanism. * sqlId cannot be written casually. namesapce must be the fully qualified name of the dao interface. id must be the method name of the interface. * Therefore sqlId is "interface name. method name" */ 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); //Method added to class ctMethod = CtMethod.make(String.valueOf(methodStr), ctClass); ctClass.addMethod(ctMethod); } catch (Exception e) {<!-- --> e.printStackTrace(); } }); //Load class Object obj = null; try {<!-- --> Class<?> clazz = ctClass.toClass(); //Create class 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; } }