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

相关推荐
凯酱2 小时前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis
我是福福大王4 小时前
MyBatis源码学习总结
后端·mybatis
江沉晚呤时6 小时前
深入了解C# List集合及两种常见排序算法:插入排序与堆排序
windows·sql·算法·oracle·c#·排序算法·mybatis
爱的叹息7 小时前
mybatis-plus里的com.baomidou.mybatisplus.core.override.MybatisMapperProxy 类的详细解析
java·tomcat·mybatis
打死不学Java代码1 天前
PaginationInnerInterceptor使用(Mybatis-plus分页)
android·java·mybatis
Code哈哈笑1 天前
【Spring Boot】深入解析:#{} 和 ${}
java·spring boot·后端·spring·mybatis
quququ_21381 天前
Java面试:从Spring Boot到微服务的全面考核
spring boot·微服务·kubernetes·mybatis·hibernate·java面试
BillKu2 天前
Spring Boot + MyBatis 动态字段更新方法
java·spring boot·mybatis
我家领养了个白胖胖2 天前
#和$符号使用场景 注意事项
java·后端·mybatis
论迹2 天前
【JavaEE】-- MyBatis操作数据库(1)
java·开发语言·数据库·java-ee·mybatis