前言:
了需要提前了解的知识点:
- JDK动态代理:https://mp.csdn.net/mdeditor/90598309#
- Mybatis简单Demo学习: https://www.mybatis.org/mybatis-3/zh/getting-started.html
- Sprng和Mybatis简单Demo构建: http://www.mybatis.org/spring/zh/getting-started.html
代码构建:
- Dao、Mapper、spring-mybatis.xml 文件的编写 省略:
- 创建测试类 MybatisSpringTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:/spring-mybatis.xml"})
@Slf4j
public class MybatisSpringTest {
@Autowired
private ClassInfoDao classInfoDao;
@Test
public void testSrpingMybatis() throws IOException {
ClassInfo classInfo = classInfoDao.selectClassByClassId(1);
System.err.println("classInfo : " + JSONObject.toJSONString(classInfo));
// -------- 将代理类反编译到文件中 ($Proxy17 需要根据Debug中显示定义,可能会不同)----------------
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy17", new Class[]{ClassInfoDao.class});
String path = "F:/mycode/Git/spring-mvc/learn/mybatis/mybatis-learn/src/test/java/com/lot/Proxy17.class";
try {
FileOutputStream fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
System.out.println("代理类写入成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码流程分析:
- 当我们Debug 运行时,可以 classInfoDao 的定义,如下图所示:
当我们在调用ClassInfoDao 实际上是 $ Proxy17(JDK 动态代理生成的对象),其中代理类 h 是 MapperProxy,通过 ProxyGenerator.generateProxyClass 将$Proxy17 生成class文件,class内容如下:
public final class $Proxy17 extends Proxy implements ClassInfoDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy17(InvocationHandler var1) throws {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.lot.dao.ClassInfoDao").getMethod("selectClassByClassId", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final ClassInfo selectClassByClassId(Integer var1) throws {
try {
return (ClassInfo)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 省略 hashCode toString equals方法
}
- 哦吼,很显然当我们代码执行 classInfoDao.selectClassByClassId 时 实际调用的是 JDK动态代理生成的对象 $Proxy17. selectClassByClassId 方法,而 super.h.invoke() 执行的是 org.apache.ibatis.binding.MapperProxy#invoke 方法,这一步已经由我们自己的代码跳转到 Spring中了。
- 最后当我们一步步Debug 时会发现最终调用的是Mybatis的 sqlSession.selectOne(),最终执行sql。 自此,Spring的 工作就已经完成了,后面就要交给Mybatis 来处理。
总结:
- Mybatis 为什么Dao不用实现?
简: 当我们启动项目时,Mybatis 会将我们的Mapper文件(或者注解)配置的 Sql 以及其类和方法组成唯一的 Key 保存起来, 并且Spring 通过JDK动态代理的方式和 MapperProxy 帮我们生成代理类,当我们供我们调用。极大的简化我们开发流程。