从mybatis到java 动态代理

我们都知道mybatis操作数据库的都是通过调用DAO接口中的方法,但是这是一个空方法,具体实现是怎么的,然后就想仔细看一下,发现使用了java动态代理来实现的。


先看一下mybatis中调用DAO中方式的大概过程,这里不详细介绍mybatis的内部细节,主要介绍和代理相关的部分。

首先从demo开始,这是一个简单的mybatis示例

ini 复制代码
public static void main(String[] args) throws IOException {
        // 获取配置文件
        String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //User user = sqlSession.selectOne("com.ais.vaaa.test.mybatis.UserDao.selectUser", 1);
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            User user = userDao.selectUser(1L);
            System.out.println(user);
        } finally {
            sqlSession.close();
        }
    }

首先获取DAO

ini 复制代码
UserDao userDao = sqlSession.getMapper(UserDao.class);

MapperRegistry.java#getMapper

ini 复制代码
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
... ... ...
return mapperProxyFactory.newInstance(sqlSession);

获取MapperProxyFactory,MapperProxyFactory是mybatis初始化的时候为每个DAO(mapper)生成的一个工厂对象,通过工厂类获取DAO

MapperProxyFactory.java

typescript 复制代码
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

这里会发现使用了jdk动态代理Proxy.newProxyInstance生成代理类,它的InvocationHandler实现类是MapperProxy.java

添加图片注释,不超过 140 字(可选)

这里看到userDao是一个对象并且名称中带有$Proxy

添加图片注释,不超过 140 字(可选)

这是生成的代理类,实现了UserDao接口,并实现了selectUser方法,具体selectUser方式是怎么处理,这里就不深入了,主要是在MapperProxy的invoke中来处理。


以上就是mybatis中使用到动态代理的一种场景,下面我们再说下jdk中的的动态代理是如何做的。

通过简单的示例来了解动态代理,示例中定义了一个接口(IHello.java),接口的实现(HelloImpl.java),一个代理类(HelloProxy.java)和入口类(Main.java)。通过代理类在sayHello方法调用前后加上日志

输出结果

erlang 复制代码
before call method...
Hello hello world
after call method...

IHello.java

arduino 复制代码
public interface IHello {
    void sayHello(String name);
}

HelloImpl.java

typescript 复制代码
public class HelloImpl implements IHello {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

HelloProxy.java

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class HelloProxy implements InvocationHandler {
    private Object delegate;

    public Object bind(Object delegate) {
        this.delegate = delegate;
        Object obj = Proxy.newProxyInstance(
                this.delegate.getClass().getClassLoader(), this.delegate
                        .getClass().getInterfaces(), this);

        // 也可以直接使用接口,不用实现类,mybatis就是这样
        // Object obj = Proxy.newProxyInstance(
        //        cls.getClassLoader(), new Class[] { cls }, this);

        return obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            System.out.println("before call method...");
            result = method.invoke(this.delegate, args);
            System.out.println("after call method...");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

Main.java

java 复制代码
public class Main {
    static public void main(String[] arg) {
        // jdk proxy 生成的代理类
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // cglib 生成的代理类
        //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\class");

        HelloProxy helloproxy = new HelloProxy();
        HelloImpl hello = new HelloImpl();
        IHello ihello = (IHello) helloproxy.bind(hello);
        ihello.sayHello("hello world");
    }
}

动态代理如何实现

通过Proxy.newProxyInstance重新生成一个新的代理类

csharp 复制代码
    public Object bind(Object delegate) {
        this.delegate = delegate;
        Object obj = Proxy.newProxyInstance(
                this.delegate.getClass().getClassLoader(), this.delegate
                        .getClass().getInterfaces(), this);
        return obj;
    }

我们进入Proxy.newProxyInstance方法查看

scss 复制代码
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
... ...

继续跟来到 java.lang.reflect.Proxy$ProxyClassFactory#apply方法,通过此方法生成了一个动态类

ini 复制代码
/*
 * Choose a name for the proxy class to generate.
 */
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
try {
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {

生成的动态类可以通过设置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");生成到本地,如下:

typescript 复制代码
public final class $Proxy0 extends Proxy implements IHello {
  private static Method m1;
  
  private static Method m3;
  
  private static Method m2;
  
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final void sayHello(String paramString) {
    try {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.ais.vaaa.test.invocationhandler.IHello").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

最终调用流程:

添加图片注释,不超过 140 字(可选)

相关推荐
小桥流水人家jjh2 小时前
Mybatis执行自定义SQL并使用PageHelper进行分页
java·数据库·spring boot·sql·mybatis
黑马师兄3 小时前
Mybatis
java·mybatis
Neoest1 天前
场景解决之mybatis当中resultType= map时,因某个字段为null导致返回的map的key不存在怎么处理
mybatis
ZWZhangYu1 天前
【MyBatis源码】深入分析TypeHandler原理和源码
数据库·oracle·mybatis
小鸡脚来咯2 天前
springboot 整合mybatis
java·spring boot·mybatis
种树人202408192 天前
MyBatis xml 文件中 SQL 语句的小于号未转义导致报错
mybatis
码农派大星。2 天前
MyBatis操作--进阶
mybatis
爱读源码的大都督2 天前
MyBatis中的LanguageDriver的作用是什么
java·spring boot·mybatis
鹿屿二向箔2 天前
基于SSM(Spring + Spring MVC + MyBatis)框架的快递管理系统
spring·mvc·mybatis
2的n次方_2 天前
MyBatis——增删查改(XML 方式)
xml·数据库·mybatis