在内存当中动态生成类的技术:
- JDK动态代理技术:只能代理接口。 spring6底层AOP是实现方式。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM)
- Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
MyBatis底层使用的就是Javassist,在内存中生成class文件,然后就不用写dao层的实现类了。
Javassist简单代码示例:
// 获取类池,这个类池就是用来给我生成class的
ClassPool pool = ClassPool.getDefault();
// 制造类(需要告诉javassist,类名是什么,在什么位置)
CtClass ctClass = pool.makeClass("com.zhenbang.mybatis.dao.impl.AccountImplDao");
// 制造方法
String methodCode = "public void insert(){System.out.println(123);}";
CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
// 加入方法
ctClass.addMethod(ctMethod);
// 在内存中生成class
ctClass.toClass();
// 加载到JVM当中,返回AccountDaoImpl类的字节码
Class<?> clazz = Class.forName("com.zhenbang.mybatis.dao.impl.AccountImplDao");
// 创建对象
Object obj = clazz.newInstance();
// 获取AccountDaoImpl中的insert方法
Method insertMethod = clazz.getDeclaredMethod("insert");
// 调用方法insert
insertMethod.invoke(obj);
由于java17过高版本报错如下:
解决办法:
配置
点修改选项
添加虚拟机项
--add-opens java.base/java.lang=ALL-UNNAMED
把上方代码放进去
--add-opens java.base/sun.net.util=ALL-UNNAMED
也把这个放到环境变量中,就不会报错了。
如何在MyBatis不写Dao接口的实现类,用Javassist生成代理类:
前提:
namespace:全限定Dao接口的全限定接口名
id:必须是对应的方法名
也不用自己写工具类,Mybatis内置了,此处代码为:
// 动态生成DaoImpl代理类的方式
private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
底层源码:
ClassPool pool = ClassPool.getDefault();
// 生成代理类
CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".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
String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
if ("SELECT".equals(sqlCommondTypeName)) {
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.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.powernode.bank.utils.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);
ctMethod.setModifiers(Modifier.PUBLIC);
// 将方法添加到类
ctClass.addMethod(ctMethod);
} catch (CannotCompileException 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);
}