从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 字(可选)

相关推荐
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
凌冰_4 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
Elaine2023916 小时前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
一二小选手7 小时前
【MyBatis】全局配置文件—mybatis.xml 创建xml模板
xml·java·mybatis
刘大浪7 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
蓝染-惣右介9 小时前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
武子康10 小时前
Java-07 深入浅出 MyBatis - 一对多模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据库·sql·mybatis·springboot
一二小选手10 小时前
【Mybatis】@Param注解 resultMap手动映射
java·mybatis
郑祎亦13 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis