动态代理在mybatis中的应用

mybatis最令人印象深刻的特性恐怕就是interface与mapper的映射了。开发者只需要声明接口,并编写对应在xml中的sql,一个可以提供服务的dao层功能就完成了,竟然不需要编写interface的实现类。这个感觉起来非常神奇也令人疑惑不解的特性正是利用jdk的动态代理技术实现的。事实上,mybatis内部使用了多种动态代理技术,包括jdk自带、javassist、cglib等,这篇文章主要围绕mybatis interface的实现来展开,即jdk动态代理在mybatis中的应用。

初探

为了探寻这一特性,决定写一个demo测试一下。因为料到mybatis会使用运行时动态生成类的技术,而且使用mybatis时都需要声明接口,极大可能是使用的jdk动态代理,因此在代码中加入如下代码:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

作用是使jvm自动将运行时动态生成的类字节码保存到磁盘的.class文件中,默认写在项目根目录/com/sun/proxy路径下,如果项目启动时不是以root启动,那么需要提前把目录建好,然后把目录权限调成777。

从官网的example找到最简单的demo:

Main类:

public class Main { 
    public static void main(String[] args) throws IOException { 
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 
        String resource = "mybatis.xml"; 
        InputStream inputStream = Resources.getResourceAsStream(resource); 
        SqlSessionFactory sqlSessionFactory = 
                new SqlSessionFactoryBuilder().build(inputStream); 
 
        SqlSession session = sqlSessionFactory.openSession(); 
        try { 
            UserMapper mapper = session.getMapper(UserMapper.class); 
            Map user = mapper.selectById(1L); 
            System.out.println(user); 
        } finally { 
            session.close(); 
        } 
    } 
}

UserMapper.xml

 
 
 
     

UserMapper.java

public interface UserMapper { 
    Map selectById(Long id); 
} 

mybatis.xml

 
 
 
     
         
             
             
                 
                 
                 
                 
             
         
     
     
         
     

项目结构:

运行项目,/com/sun/proxy路径下生成了两个class文件,名字格式为$ProxyX.class,如下:

可以使用jdgui或jad反编译一下便于阅读,类的声明如下:

public final class $Proxy0 extends Proxy implements UserMapper
public final class $Proxy1 extends Proxy implements Connection

从声明中便可以轻易的看出,生成了一个UserMapper接口的代理类,一个jdbc Connection的代理类。

下面贴出一个完整的代理类文件:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. 
// Jad home page: http://www.kpdus.com/jad.html 
// Decompiler options: packimports(3)  
 
package com.sun.proxy; 
 
import java.lang.reflect.*; 
import java.util.Map; 
import mybatis.dao.UserMapper; 
 
public final class $Proxy0 extends Proxy 
    implements UserMapper 
{ 
 
    public $Proxy0(InvocationHandler invocationhandler) 
    { 
        super(invocationhandler); 
    } 
 
    public final Map selectById(Long long1) 
    { 
        try 
        { 
            return (Map)super.h.invoke(this, m3, new Object[] { 
                long1 
            }); 
        } 
        catch(Error _ex) { } 
        catch(Throwable throwable) 
        { 
            throw new UndeclaredThrowableException(throwable); 
        } 
    } 
 
    public final boolean equals(Object obj) 
    { 
        try 
        { 
            return ((Boolean)super.h.invoke(this, m1, new Object[] { 
                obj 
            })).booleanValue(); 
        } 
        catch(Error _ex) { } 
        catch(Throwable throwable) 
        { 
            throw new UndeclaredThrowableException(throwable); 
        } 
    } 
 
    public final int hashCode() 
    { 
        try 
        { 
            return ((Integer)super.h.invoke(this, m0, null)).intValue(); 
        } 
        catch(Error _ex) { } 
        catch(Throwable throwable) 
        { 
            throw new UndeclaredThrowableException(throwable); 
        } 
    } 
 
    public final String toString() 
    { 
        try 
        { 
            return (String)super.h.invoke(this, m2, null); 
        } 
        catch(Error _ex) { } 
        catch(Throwable throwable) 
        { 
            throw new UndeclaredThrowableException(throwable); 
        } 
    } 
 
    private static Method m3; 
    private static Method m1; 
    private static Method m0; 
    private static Method m2; 
 
    static  
    { 
        try 
        { 
            m3 = Class.forName("mybatis.dao.UserMapper").getMethod("selectById", new Class[] { 
                Class.forName("java.lang.Long") 
            }); 
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { 
                Class.forName("java.lang.Object") 
            }); 
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 
        } 
        catch(NoSuchMethodException nosuchmethodexception) 
        { 
            throw new NoSuchMethodError(nosuchmethodexception.getMessage()); 
        } 
        catch(ClassNotFoundException classnotfoundexception) 
        { 
            throw new NoClassDefFoundError(classnotfoundexception.getMessage()); 
        } 
    } 
} 

获取代理类流程

获取Mapper代理类的时序图如下:

重点说下MapperProxy类,声明如下:

public class MapperProxy implements InvocationHandler, Serializable

jdk动态代理中暴露给开发者的,一个是Proxy类,一个是InvocationHandler接口。Proxy类的作用是组装并动态生成代理类,而实现InvocationHandler接口的类起到的作用就是增强被代理类。为了便于理解,称动态生成的类为代理类,但是严格来说,mybatis并没有代理任何 '被代理类',只是生成一个实现UserMapper接口的桩子类,真正处理逻辑全部落到了MapperProxy类中。(动态代理的通俗解释可以看下这里https://blog.csdn.net/joenqc/article/details/66474611)

代理类处理请求流程

从代理类的代码中可以看到,selectById方法的整个逻辑都交给了 h 对象处理,这个h对象正是实现了InvocationHandler接口的MapperProxy类,整个调用流程的时序图如下:

整个接口执行流程比较复杂,具体细节不再展开。注意Mapper接口代理类中没有任何处理逻辑,中介类MapperProxy也仅仅是一层壳子,sql的解析、组装、预处理都落到了后续类中,解耦非常严重。另外注意到PreparedStatementHandler类封装并调用了 mysql驱动中的PreparedStatement类,在这里真正调用数据库。

你可能感兴趣的:(java)