发表于: 2020-10-18 23:26:34
1 1340
今天完成的事情:
遇到的问题:
JAVA动态代理
Classloader - 和被代理对象使用相同的类加载器
interfaces - 和被代理对象具有相同的行为。实现相同的接口。
InvocationHandler:如何代理。
类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader 类的一个实例。
Class中的字节码对象
字节码对象概述
每个类被加载之后,系统就会为该类生成一个对应的字节码对象,通过该字节码对象就可以访问到JVM中的对应的类。在Java中获得Class对象通常有三种方式。
获取字节码对象的三种方式
1.使用类的.class属性
Class<类类型> clz1 = 类名.class;
2.通过Class类中的静态方法forName(String className),传入类的全限定名(必须添加完整包名)比较常用
Class<?> clz2 = Class.forName("java.util.Date");
3.通过对象的getClass方法来实现,其中,getClass()是Object类中的方法,所有的对象都可以调用该方法
Date str = new Date();
Class<?> clz3 = str.getClass();
注意:同一个类在JVM中只存在一份字节码对象,也就说上述,claz1 == clz2 == clz3;且字节码对象是放在heap 堆里。
动态代理每一次实现接口中的方法,就会实现动态代理的方法,就不用像静态代理需要重复写相同的代码。
例子:
静态代理
动态代理,只写一次,但会被重复执行
Mapper代理
先将SqlMapConfig.xml中mappers的配置去除,因为Spring拥有自己的Mapper接口,用于扫描Mapper代理接口。此Mapper批量扫描器类,会从Mapper包中扫描Mapper接口,自动创建代理对象并且在SPring容器中注入。
1.将SqlMapConfig.xml中mappers的配置去除
2.在spring-config。xml文件中添加spring的Mapper扫描器,用于在指定包下扫描定义的Mapper代理接口:
3.创建一个StudentQueryMapper.XML文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<mapper namespace="com.jnshu.Mapper.StudentQueryMapper">
<select id="findStudentById" parameterType="int" resultType="com.jnshu.Pojo.Student">
SELECT * FROM Student WHERE id=#{id}
</select>
</mapper>
4.创建一个Mapper代理接口
package com.jnshu.Mapper;
import com.jnshu.Pojo.Student;
public interface StudentQueryMapper {
//根据Id查询用户信息
public Student findStudentById(int id) throws Exception;
}
注:
1:xml中的nameSpace必须是接口的全类路径名
2: 每个标签的id必须和接口中的id保持一致
3:两个文件的名字必须保持一致
5.编写测试类StudentMapperTest
import com.jnshu.Mapper.StudentQueryMapper;
import com.jnshu.Pojo.Student;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentMapperTest {
private ApplicationContext applicationContext;
//在执行测试方法之前首先获Spring配置文件对象
//注解Before是在执行本类所有测试方法之前先调用这个方法
@Before
public void setup() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:Spring-config.xml");
}
@Test
public void testFindStudentById() throws Exception{
//通过配置资源对象获取studentDao对象
StudentQueryMapper studentQueryMapper = (StudentQueryMapper)applicationContext.getBean("studentQueryMapper");
//调用studentDao的方法
Student student=studentQueryMapper.findStudentById(1);
//输出用户信息
System.out.println(student.getId()+":"+student.getName());
}
}
这里出现了一个错误
报错:Invalid bound statement (not found): com.xx.mapper.query
1.看了一下字母发现有个错的,改了,还是同样的结果
加上后发现target中还是没有sqlmap文件夹,但是运行成功了
看到查询结果和之前非mapper代理的结果一样。原理是:在Spring配置文件中配置的mapper批量扫描器类,会从mapper包中扫描出Mapper接口,自动创建代理对象并且在spring容器中注入。自动扫描出来的mapper的bean的id为mapper类名,所以这里获取的就是名为“studentQueryMapper”的mapper代理对象。
发现使用mapper代理后就不用创建实现类了,不知道为什么?
练习 查看日志,并转成Debug模式,练习调试,学会查看单步执行时的变量值。
1.查看日志
转成Debug模式
练习调试,学会查看单步执行时的变量值
package Testone;
import org.junit.Test;
public class IDEADebug {
@Test
public void testStringBuffer(){
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);//
System.out.println(sb.length());//4
System.out.println(sb);//"null"
StringBuffer sb1 = new StringBuffer(str);//抛出异常NullPointerException
System.out.println(sb1);//
}
}
了解了一下length() 方法的作用
我搜了一下
为什么mybatis的mapper没有实现类(原理探究)
通过debug跟踪了下getMapper这个方法,看到了关键代码:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
发现这个就是用的动态代理
通过Proxy.newProxyInstance方法源码也可以看到如下注释信息:
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
再通过查看mapperProxy的代码:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
那么我们执行sqlSession.getMapper(UserMapper.class)这句代码最终返回的应该就是一个由jdk动态代理生成的代理类
当执行userMapper.getAllUser()方法时,最终执行的也就是 mapperMethod.execute(this.sqlSession,args)的代码
所以回到原问题,为什么mybatis的mapper没有实现类呢?原因是因为 它采用了:Java动态代理实现接口
明天计划的事情:
部署数据库到远程DB,从本地直接连远程。
将部署自己服务到服务器上,包括Maven,Mysql客户端等。直接用Maven命令跑单元测试。
收获:了解了mapper代理也是采用Java动态代理实现接口,学会了 查看日志,并转成Debug模式,练习调试,学会查看单步执行时的变量值。
评论