MyBatis框架的代理模式
通过前面的了解,我们大致知道了MyBatis框架的基本使用方式。但是,我们可能会有一个疑问,为什么Mapper接口没有实现类却能被正常调用呢?其实这是因为MyBatis在Mapper接口上使用了动态代理的一种非常规的用法。在了解这种用法之前,我们先来看看原生方法的使用。
原生方法的调用方式
-
以查询操作为例,原生方法的调用会直接通过
sqlSession会话调用相应的方法来达到查询的目的。代码如下: -
接口文件:
public interface StudentMapper {
/**
* 模糊查询
*/
public List<Student> getStudentByName(String str);
}
- XML文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tulun.dao.StudentMapper">
<!--直接在参数上拼接通配符,进行模糊查询-->
<select id="getStudentByName" resultType="com.tulun.pojo.Student">
select * from Student where SName like #{SName}
</select>
mapper>
- 测试文件:
public class StudentMapper3Test {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() {
//mybatis配置文件
String resource = "mybatis-config.xml";
//通过mybatis提供的Resources类来得到配置文件流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//创建会话工厂,传输mybatis配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void getStudentByName(){
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//原生方法调用形式
//通过方法接口进行调用
List<Object> objects = sqlSession.selectList("com.tulun.dao.StudentMapper3.getStudentByName", "%L%");
System.out.println(objects);
}
}
-
注意测试方式的不同:
- 返回多个结果时,使用
selectList方法。 - 返回的结果不管是单个还是多个在resultType属性都是返回的Java对象全路径。
- 返回单个结果对象使用
selectOne。 - 对于增删改操作,同样可以使用此操作,其
sqlSession对象也同样提供了其对应的方法。
- 返回多个结果时,使用
-
执行结果:

而在MyBatis的使用过程中,我们经常使用的是通过getMapper方法获取代理对象,形如:StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);方式,从而进行对接口的动态代理,接下来,我们看看什么是动态代理。
动态代理
动态代理其实是代理模式的一种,而代理模式其实是Java当中的一种设计模式。其结构如图所示:

这种模式就相当于一个公司生产了一个产品,但并不是由其开发人员去销售,而是由销售人员去销售。
代理模式的特点:
- 代理模式中代理类和委托类是具有相同的接口。
- 代理类的主要职责就是为委托类预处理消息,过滤消息等功能的实现。
- 代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相关方法,来提供特定的一些服务。
- 代理类和委托类之间存在关联关系,一个代理类的对象和一个委托类的对象相关联。
- 访问实际对象,是通过代理对象来访问的。
代理模式的分类: 静态代理和动态代理。
- 静态代理是在程序编译阶段就确定了代理对象。
- 而动态代理是在程序运行阶段才确定代理对象。
- 而在这里,我们仅仅来分析一下动态代理:
其中动态代理是在运行时根据Java代码指示动态生成的,相比较静态代理,优势在于方便对代理类的函数进行统一的处理,而不用修改每个代理类的方法。
Java中提供的动态代理方式有两种:JDK自带的动态代理和CGLib实现的代理。下面来看看这两种方式的区别。
JDK自带的动态代理
JDK自带的代理方式是需要实现invocationHandler接口,并且实现invoke的方法来进行的。下面我们来自己实现一下这种代理方式:
- 先提供一个共有的接口和委托类的实现。
- 接口文件:
/**
* 接口类
* 定义委托类和代理类共工的方法
*/
public interface IUser {
void talk();
}
- 委托实现类:
/**
* 委托类
* 实现了接口Iuser中的talk方法
*/
public class User implements IUser {
@Override
public void talk() {
System.out.println("doing User.talk");
}
}
- 要实现动态代理,需要首先创建一个实现了InvocationHandler接口的辅助类。
/**
* 代理的辅助类
*/
public class UserProxy implements InvocationHandler {
private Object object;
public UserProxy(Object o) {
this.object = o;
}
/**
* 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
* @param proxy :就是动态代理生成的代理对象
* @param method:就是调用的方法
* @param args:表示该方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理类提供委托类提供的功能,也可以提供自己特有的功能
System.out.println("donging UserProxy.invoke");
//调用委托类提供的方法
method.invoke(object,args);
System.out.println("donging UserProxy.invoke end");
return null;
}
}
- 使用产生代理对象时,需要调用代理辅助类来调用委托方法。
public static void main(String[] args) {
//产生代理对象
IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
iUser.talk();
}
- 日志打印:

- 通过对日志的观察可以发现,当前的代理类在调用talk方法时,会调用到代理辅助类UserProxy中的invoke方法,并且还会调用到委托类中的talk实现。
- 那么JVM是如何自动实现invoke方法的调用呢?
JDK自带代理方式的原理探究
在探究时,需要进行参数的配置。在代理类的主方法调用中添加一个监控操作。
public static void main(String[] args) {
//看看JDK生成的自动代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//产生代理对象
IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
iUser.talk();
}
- 通过执行后查看,发现JVM生成了一个
$Proxy0.class文件,源码如下:

- 从
$Proxy0的定义中可知,它确实是实现了IUser接口,和代理模式下的代理是完全一致的。

- 通过分析可得:当代理类实例中调用方法talk时,根据Java中的多态,调用的是
$Proxy0的talk方法,而$Proxy0是重写接口中的talk方法。
CGLib实现的动态代理
这种代理方式提供了一种可扩展机制来控制被代理对象的访问操作,就是对对象的访问做了一层封装。当需要代理没有接口的类时,此时Proxy和InvocationHandler机制就不能使用了(原因是JDK自带的代理模式的使用必须需要有接口的存在),此时可以使用CGLib库,采用底层字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截父类所有方法的调用,这种方式则采用的是横切的逻辑。而Spring AOP(横向切面)的技术就是使用的这种动态代理模式。下面来看看这种方式的具体使用。
- CGLib的使用需要引入第三方库。
<!--CGLib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
- 父类:
public class CGLibSuper {
public void doing() {
System.out.println("CGLibSuper.doing");
}
}
- 辅助类的实现:
/**
* 通过已有的类产生代理类时
* 在当前辅助类需要实现MethodInterceptor接口
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public <T> T getProxy(Class<T> clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码动态的创建子类实例
return (T)enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLibProxy doing");
//拦截到父类响应的方法
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("CGLibProxy doing end");
return o1;
}
}
- 通过CGLib实现代理:
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
//动态产生的CGLibSuper的子类实例
CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
proxy.doing();
}
- 执行结果:

- 通过执行结果可知,当前产生的代理类调用相应的方法(doing),会被代理辅助类中的intercept方法拦截,在该方法中可以实现扩展器,从而也能调用到父类中的相应方法。
Mybatis的代理模式详解

MyBatis中产生的代理是哪种方式???
- 答案是:JDK自带的方式,原因是在使用时仅在其中实现了接口。
mapper是如何添加进去的???
- mapper是通过sqlSession拿到的,sqlSession是通过sqlSessionFactory拿到的,而sqlSessionFactory是通过读取XML配置源得到的。这种方式相当于直接在主程序中进行环境配置:
new Environment()方法。如图所示:

- 通过代码形式产生的会话工厂实例和读取配置形式是类似的。
- 通过代码形式中可以看到,Mapper接口是通过Configuration.addMapper()形式来添加,参数为接口的class。
- 类似于:
Configuration configuration = new Configuration();
configuration.addMapper(StudentMapper.class); //添加mapper
addMapper()方法的实现:
-
观察源码可知:
-
Configuration类中:
public <T> void addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
- mepper实际上被添加到了MapperRegistry中,继续观察MapperRegistry类可得:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { //只添加接口
if (this.hasMapper(type)) { //不允许接口重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type)); //将接口存放到HashMap中,重点
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
- 通过分析得:执行的Configuration.addMapper()操作,最终是被放入到HashMap中,其名为knownMappers,knownMappers是MapperRegistry中的属性,他是一个HashMap对象,key为当前Class对象,Value为MapperProxyFactory实例。
从getMapper入手分析:
- sqlSession中调用getMapper()方法操作实际调用到DefaultSqlSession中的实现。
- DefaultSqlSession类:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
- 其中DefaultSqlSession没有逻辑,直接调用configuration中的getMapper方法,而configuration主要是将配置信息存放在当中。
- configuration类中的操作:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
- 其中getMapper的实现是调用mapperRegistry中的方法,而我们知道,mapperRegistry中实际上是存放了一个HashMap。
- mapperRegistry类:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession); //重点看这里,通过这个操作返回了代理对象
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
- 调用sqlSession.getMapper()操作,最终会到达上面的这个方法,他会根据接口在HashMap中找到对应的Value值,是一个MapperProxyFactory的对象,然后通过调用该对象的newInstance()的方法,最终获取到代理对象。
- MapperProxyFactory类:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final 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) {
//JDK自带的代理方式生成映射器代理对象
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);
}
}
- MapperProxy类:
public class MapperProxy<T> implements InvocationHandler, Serializable {
//当前类实现了InvocationHandler接口
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;
}
//实现invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args); //其中实现是调用委托类方法
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method); //使用缓存???
//执行CRUD相关操作
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;
}
}
总结
Mapper是如何注册的,Mapper接口是如何产生动态代理对象的?
- Mapper接口在初始sqlSessionFactory的时候进行注册。
- Mapper接口注册到了mapperRegistry类HashMap中,key是Mapper的接口类class,value是当前的Mapper工厂。
- Mapper注册之后,可以从sqlSession中的getMapper方法获取对象。
- sqlSession.getMapper方法使用了JDK自带的动态代理,产生目标接口Mapper的代理对象。
- 动态代理的代理辅助类是MapperProxy。
本文详细介绍了MyBatis框架中的动态代理机制,包括原生方法调用方式、JDK动态代理和CGLib动态代理的原理及实现。重点讨论了MyBatis如何使用JDK动态代理创建Mapper接口的代理对象,以及Mapper接口的注册和代理对象的获取过程。
1276

被折叠的 条评论
为什么被折叠?



