MyBatis(三)动态代理

本文介绍了MyBatis中的动态代理机制,包括代理模式的概念、JDK动态代理和CGLib动态代理的工作原理。详细分析了JDK动态代理的源码,并探讨了MyBatis如何利用动态代理创建Mapper接口的代理对象,以及Mapper接口方法的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  在介绍MyBatis动态代理前。我们先介绍一下什么是代理模式。

代理模式

      代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

      代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性。

代理模式结构图

动态代理

      代理模式分为两种:静态代理和动态代理。静态代理是程序编译阶段确定代理类,而动态代理是在程序运行时确定代理类,代理类在程序运行时创建的代理方式被成为动态代理,动态代理是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

JDK自带的动态代理方式(必须有接口)

先实现一个接口和具体的实现类
#####接口
```
public interface IUser {
    void 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 object){
         super();
         this.object = object;
     }
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("doing UserProxy.invoke");
         method.invoke(object, args);
         System.out.println("doing UserProxy.invoke end");
         return null;
     }
     /**
      * 实现InvocationHandler接口要重写invoke方法,方法的三个参数分别:
      * proxy:就是动态代理生成的代理类对象
      * method:就是调用的方法
      * args:表示该方法的参数
      */
}
```
在main函数中创建代理对象,通过该对象,调用委托类的方法,每个方法的调用JVM都会给我们调用上面实现
InvocationHandler接口的类对象的invoke方法
```
public static void main(String[] args) {
     IUser user = (IUser) Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
     user.talk();
}
```
输出结果
```
doing UserProxy.invoke
doing User.talk
doing UserProxy.invoke end
```

JDK动态代理原理

      main中调用了代码user.talk()方法后,JVM会帮助自动实现invoke调用呢?在上面main添加一个监控

public static void main(String[] args) {
     //可以查看JDK生成的动态代理类
     System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
     IUser user = (IUser) Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
     user.talk();
}

     生成一个$Proxy().class文件,代理类的源码,public final class $Proxy0 extends Proxy implements IUser$Proxy()的定义,确实实现了IUser接口,和代理模式下的代理类完全一样,因此当user.talk()调用时,根据JAVA的多态原理,调用的应该是代理对象$Proxy()的talk()方法,看$Proxy()重写的talk方法。

public final void talk() throws  {
         try {
             super.h.invoke(this, m3, (Object[])null);
         } catch (RuntimeException | Error var2) {
             throw var2;
         } catch (Throwable var3) {
             throw new UndeclaredThrowableException(var3);
         }
}

     当调用IUser接口的引用变量user调用talk的时候,其实调用代理对象重写的talk方法,用传入的InvocationHandler对象,并调用了它的invoke方法。

CGLib动态代理(实际上要用动态代理技术)

      代理提供了一种可扩展的机制来控制被代理的对象的访问,就是在对象访问的时候加了一层封装,JDK从1.3版本开始提供了上述动态代理机制,使用简单,缺点显而易见,需要目标对象实现一个或者多个接口,当代理没有接口的类,此时Proxy和InvocationHandler机制不能使用了,此时可以使用CGLib库,CGLib采用非常底层的字节码技术,原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑,JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

public class CGLibSuper {
    public void doing() {
        System.out.println("CGLibSuper.doing");
    }
}

/**
* 该类实现了创建子类的方法与代理的方法,getProxy(CGLibSuper.class)方法通过入参即父类的字节码,
* 通过扩展父类的class来创建代理对象,intercept()方法拦截所有目标类方法的调用,Object表示目标类的实例,
* method为目标类方法的反射对象,objects为方法的动态入参,methodProxy为方法代理
* methodProxy.invokeSuper(o, objects)通过代理类来调用父类的方法
*/
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();
    }


    //实现MethodInterceptor
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLibProxy.intercept");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("CGLibProxy.intercept end");
        return o1;
    }
}

public static void main(String[] args) {
    CGLibProxy cgLibProxy = new CGLibProxy();
    CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
    proxy.doing();
}

动态代理源码分析

定义接口 & 实现类

public interface Subject {
    int add(int x, int y);
}

public class RealSubject implements Subject {
    @Override
    public int add(int x, int y) {
        System.out.println("计算两个数的和,结果为:" + (x + y));
        return x + y;
    }
}

定义代理类,实现动态代理接口

public class MyProxy implements InvocationHandler{
    private Object targer;
    public MyProxy(Object targer) {
        this.targer = targer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before method do something");
        Object result = method.invoke(targer, args);
        System.out.println("after method do something");
        return result;
    }
}

获取代理类

public class Client {
    public static void main(String[] args) {
        Subject target = new RealSubject();
        MyProxy h = new MyProxy(target);
        Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class}, h);
        int result = proxy.add(1,2);
    }
}

      简单三步即可获取到代理对象,调用代理对象的任何方法都会执行 invoke 方法。从以上代码可以看出,JDK 动态代理只能代理接口,而且还需要定义实现类,那 mybatis 是如何做到不需要实现类就轻松获取到代理对象的呢?

Mybatis的动态代理

mybatis中的getMapper方法的实现方式理解为动态代理方法的实现方式理解为动态代理

mybatis中的getMapper的调用为

sqlSession.getMapper(OrdersMapper1.class);

mapper是如何添加进去的呢?

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

等价于

String resource = "mybatis-config.xml"; // xml内容就不贴了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    sqlSessionFactory采用以上方式,分析上面代码生成sqlSessionFactory过程有这么一行:

sqlSession类:

configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口

addMapper 方法的代码实现

Configuration类:

 public <T> void addMapper(Class<T> type) {
     mapperRegistry.addMapper(type);
   }

mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:

MapperRegistry类:

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {// 只添加接口
      if (hasMapper(type)) {// 不允许重复添加
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));// 注意这里
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

      所执行的configuration.addMapper(BlogMapper.class); 其实最终被放到了HashMap中,其名为knownMappers ,knowMappers是MapperRegistry 类的一个私有属性,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory 实例。

      总结一下: 诸如OrdersMapper1之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。

     从源码上我们可以看到getMapper方法会去调用Configuration类的getMapper方法。

DefaultSqlSession类:

public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

      Configuration类里面存放了所有关于XML文件的配置信息。从参数上我们可以看到他要我们传入一个Class<T>类型。这已经可以看到后面一定要用到反射机制和动态生成相应的类实例  

Configuration类:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

     代码所示,这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的。

MapperRegistry类:

 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession); //重点看这里
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

       我们调用的session.getMapper(OrdersMapper1.class);最终会到达上面这个方法,这个方法根据OrdersMapper1的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象

public class MapperProxyFactory<T> { //映射器代理工厂

  private final Class<T> mapperInterface;

  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  //删除不必要代码

  @SuppressWarnings("unchecked")

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用了JDK自带的动态代理生成映射器代理类的对象

    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

  }

  public T newInstance(SqlSession sqlSession) {

    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

    return newInstance(mapperProxy);
  }
}

      最终是通过Proxy.newProxyInstance产生了一个OrdersMapper1的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:

ClassLoader —— 指定当前接口的加载器即可

当前被代理的接口是什么 —— 这里就是OrdersMapper1

代理类是什么 —— 这里就是 MapperProxy

代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码

public class MapperProxy<T> implements InvocationHandler, Serializable {

//实现了InvocationHandler接口

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
      try {
        return method.invoke(this, args);// 注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
    //执行CURD
    return mapperMethod.execute(sqlSession, args);// 注意2
  }
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method)
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

     Blog blog =mapper.getOrdersById(3); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。只有调用getOrdersById之类的方法的时候,才执行增强的调用——即mapperMethod.execute(sqlSession, args);

而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了

public Object execute(SqlSession sqlSession, Object[] args) {
     Object result;
     switch (command.getType()) {
       case INSERT: {
         Object param = method.convertArgsToSqlCommandParam(args);
         result = rowCountResult(sqlSession.insert(command.getName(), param));
         break;
       }
       case UPDATE: {
         Object param = method.convertArgsToSqlCommandParam(args);
         result = rowCountResult(sqlSession.update(command.getName(), param));
         break;
       }
       case DELETE: {
         Object param = method.convertArgsToSqlCommandParam(args);
         result = rowCountResult(sqlSession.delete(command.getName(), param));
         break;
       }
       case SELECT:
         if (method.returnsVoid() && method.hasResultHandler()) {
           executeWithResultHandler(sqlSession, args);
           result = null;
         }
        // 删除部分代码      
       }
   // 删除部分代码
     return result;
   }

Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:

  1.  Mapper 接口在初始SqlSessionFactory 注册的。
  2.  Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。
  3.  Mapper 注册之后,可以从SqlSession中get
  4.  SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
  5.  动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值