代理模式(附mybatis动态代理)

模式

  1. 静态代理

    静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

  2. 动态代理

    动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象

组成

  1. 抽象角色:通过接口或抽象类声明真实角色实现的业务方法
  2. 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
  3. 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作

优点

  1. 职责清晰

    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

  2. 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

  3. 高扩展性

代码演示

租房子的例子,有房东、中介还有租房子的业务,对应于代理模式的组成就是

租房子的业务 --> 抽象角色

房东 --> 真实角色

中介 --> 代理角色

按照这个思路,静态代理如下:

/**
 * 抽象角色:租房子的业务
 *  按照代理模式,这里可以是接口或者抽象类,这里我用的是接口
 * @author:luohaijie
 * @date :2021/10/28
 */
public interface Rent {

    /**
     * 租房子的业务方法
     */
    void rent();
}
/**
 * 真实角色:房东
 * @author:luohaijie
 * @date :2021/10/28
 */
public class Landlord implements Rent{

    @Override
    public void rent() {
        System.out.println("房东租房子");
    }
}

/**
 * 代理角色:中介
 * @author:luohaijie
 * @date :2021/10/28
 */
public class ProxyRent implements Rent{

    private Rent rent;

    public ProxyRent(Rent rent) {
        this.rent = rent;
    }

    @Override
    public void rent() {
        System.out.println("收取中介费");
        rent.rent();
        System.out.println("帮转租房子");
    }
}
/**
 * 租客
 * @author:luohaijie
 * @date :2021/10/28
 */
public class Client {
    public static void main(String[] args) {
        Rent rent = new Landlord();
        ProxyRent proxyRent = new ProxyRent(rent);
        proxyRent.rent();
    }
}
/*
收取中介费
房东租房子
帮转租房子
*/

动态代理(以下基于JDK动态代理,是动态代理的一种)如下:

/**
 * 抽象角色:租房子的业务
 *  按照代理模式,这里可以是接口或者抽象类,这里我用的是接口
 * @author:luohaijie
 * @date :2021/10/28
 */
public interface Rent {

    /**
     * 租房子的业务方法
     */
    void rent();
}
/**
 * 真实角色:房东
 * @author:luohaijie
 * @date :2021/10/28
 */
public class Landlord implements Rent {

    @Override
    public void rent() {
        System.out.println("房东租房子");
    }
}

/**
 * 假代理角色
 * @author:luohaijie
 * @date :2021/10/28
 */
public class ProxyUtils<T> implements InvocationHandler{
    private T target;

    public ProxyUtils(T target) {
        this.target = target;
    }

    public T getProxyInstance(ClassLoader loader,
                              Class<?>[] interfaces,
                              InvocationHandler h) {
        return (T) Proxy.newProxyInstance(loader, interfaces, h);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("收取中介费");
        method.invoke(target, args);
        System.out.println("帮转租房子");
        return "代理成功";
    }
}
/**
 * 租客
 * @author:luohaijie
 * @date :2021/10/28
 */
public class Client {
    public static void main(String[] args) {
        Rent rent = new Landlord();
        ProxyUtils<Rent> rentProxy = new ProxyUtils<>(rent);
        Rent proxyInstance = rentProxy.getProxyInstance(rent.getClass().getClassLoader(), rent.getClass().getInterfaces(), rentProxy);
        proxyInstance.rent();
    }
}
/*
收取中介费
房东租房子
帮转租房子
*/

上面的ProxyUtils类,我称之为假的代理角色,因为它并没有实现抽象角色,实际上,真正的代理角色在上面动态代理的代码中并没有体现出来,我们可以通过jvm中自带的HSDB查看,在JAVA_HOME下的lib文件夹下的sa-jdi.jar。

cmd进入lib目录,执行java -cp sa-jdi.jar sun.jvm.hotspot.HSDB即可进入

java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

在这里插入图片描述
生成的代理类是执行了Proxy.newProxyInstance(loader, interfaces, h)之后生成的,所以我们在这里打个断点,然后debug。
在这里插入图片描述
在这里插入图片描述

打开HSDB,点击File下Attach to HotSpot Process,

在这里插入图片描述

输入进程ID(cmd下输入jps获得),我的是15748,点击OK

在这里插入图片描述

然后点击Tools下的Class Browser,搜索$Proxy0(看debug的对象名),找到之后,点击Create .class File,在JAVA_HOME下的lib文件夹中,就会生成对应的class文件

我的是D:\Java\jdk1.8.0_211\lib\com\sun\proxy$Proxy0.class,直接打开字节码文件会乱码,这里我是用idea打开(idea有反编译的工具)

在这里插入图片描述
在这里插入图片描述

这里为什么要输入前缀$Proxy,这是因为上面debug时,proxyInstance的对象前缀是$Proxy,实际上,JDK动态代理生成的类,前缀都带有$Proxy,可以在类Proxy中的内部类ProxyClassFactory中查看

在这里插入图片描述

字节码内容如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.rob.demo02.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Rent {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.rob.demo02.Rent").getMethod("rent");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

        public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

发现该类继承了Proxy,实现了Rent接口,除了实现方法之外,还重写了Object类下的toString()方法等,其实该类就是真正的真实角色,看一下实现方法rent(),核心代码如下

super.h.invoke(this, m3, (Object[])null);

追踪看一下,哦,原来h就是InvocationHandler接口,

在这里插入图片描述

再看回字节码文件,构造方法中有个参数InvocationHandler,如下

public $Proxy0(InvocationHandler var1) {
    super(var1);
}

再结合Proxy.newProxyInstance方法

/**
* 这个是上面ProxyUtils的方法
*/
public T getProxyInstance(ClassLoader loader,
                          Class<?>[] interfaces,
                          InvocationHandler h) {
    return (T) Proxy.newProxyInstance(loader, interfaces, h);
}

在这里插入图片描述

哦,原来我们生成真正的代理角色实例的时候,传的就是这个InvocationHandler对象,那这样调用super.h.invoke()方法的时候,实际上就是调用ProxyUtils类中实现的invoke方法。

到这里,JDK动态代理的思路我们已经理了一遍了,想看看是如何动态生成这个代理类的,可以看 Proxy.newProxyInstance的实现逻辑。

回答一下jdk动态代理为什么不能代理类,因为生成的代理类已经继承了Proxy类,java不能多继承,所以不能代理类。

mybatis动态代理

下面是mabatis中mapper的动态代理,先回想一下上面的JDK动态代理的三个角色

有接口(抽象角色),有我们自己写的接口的实现类(真实角色),有动态生成的继承Proxy类和实现接口的代理类(代理角色)

问题:我们用mybatis的时候,没有写接口的实现类,只有写接口,在接口上写注解(注解中写sql语句),或者在xml文件中写sql语句,并没有写实现类,就算JDK动态代理生成了一个代理类,那这个代理类代理了个寂寞?

换个思路想,假如没有了实现类,还能不能代理,看如下代码

public interface Rent {

    /**
     * 租房子的业务方法
     */
    void rent();
}

public class ProxyUtils<T> implements InvocationHandler{

    public T getProxyInstance(ClassLoader loader,
                              Class<?>[] interfaces,
                              InvocationHandler h) {
        return (T) Proxy.newProxyInstance(loader, interfaces, h);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("收取中介费");
        System.out.println("帮转租房子");
        return "代理成功";
    }
}

public class Client {
    public static void main(String[] args) {
        ProxyUtils<Rent> rentProxy = new ProxyUtils<>();
        Rent proxyInstance = rentProxy.getProxyInstance(Rent.class.getClassLoader(), new Class[]{Rent.class}, rentProxy);//注意这里跟有实现类的不一样
        proxyInstance.rent();
    }
}
/*
收取中介费
帮转租房子
*/

居然也可以执行,但是少了实现类的业务代码,既然这样的话,看如下代码

public class ProxyUtils<T> implements InvocationHandler{

    public T getProxyInstance(ClassLoader loader,
                              Class<?>[] interfaces,
                              InvocationHandler h) {
        return (T) Proxy.newProxyInstance(loader, interfaces, h);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("收取中介费");
        System.out.println("房东租房子");//实现类的业务代码
        System.out.println("帮转租房子");
        return "代理成功";
    }
}
/*
收取中介费
房东租房子
帮转租房子
*/

既然这样的话,我们就可以直接在InvocationHandler的实现类下的invoke方法,写业务代码,在某些情况下,效果是一样的。

正片开始,建项目的就略去,就是一个简单的spring项目(或者一个springboot项目),导入mybatis依赖,连接数据库。debug如下:
在这里插入图片描述
在这里插入图片描述

发现在getMapper方法里面,有mapperProxyFactory.newInstance(sqlSession);其中mapperProxyFactory是从knownMappers中获得的,knownMappers是一个hashMap,存着每个Dao接口的MapperProxyFactory,在加载配置文件的时候,会调用addMapper方法存进去

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

在这里插入图片描述

继续debug,就到了newInstance方法

在这里插入图片描述

在newInstance方法里面,就有JDK动态代理,传的是接口的类加载器,不是实现类的类加载器。

在这里插入图片描述

至此,就获得了代理类,我们也可以通过HSDB查看一下,步骤跟上面一样,就不详细说了,生成的类的字节码(idea反编译之后)内容如下。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.rob.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;

public final class $Proxy22 extends Proxy implements UserDao {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy22(InvocationHandler var1) {
        super(var1);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.rob.dao.UserDao").getMethod("findAll");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

其中findAll()方法就是从UserDao接口中实现过来的

继续往下,按照之前的思路,我估计mapper.findAll();应该就是执行一个实现了InvocationHandle接口的实例的invoke方法。(先把HSDB关掉,才能继续debug)

在这里插入图片描述

在这里插入图片描述

果然,执行的是MapperProxy类的invoke方法,该类也实现了InvocationHandler接口。首先是判断是不是Object类的方法,不是就往下,再判断是不说Default方法,jdk1.8接口有default方法,不是就往下,执行cachedMapperMethod方法。

在这里插入图片描述

一开始没有执行过findAll这个方法,所以就进入了if语句里面。new了一个MapperMethod对象,MapperMethod很重要,里面有两个属性SqlCommand和MethodSignature,还有execute方法等,

private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;

继续debug,就来到了execute方法,findAll是一个查询方法,且返回的是一个List,就来到了executeForMany方法。

在这里插入图片描述

然后判断是否有分页,最终就执行sqlSession.selectList方法

在这里插入图片描述

然后执行query方法

在这里插入图片描述

在这里插入图片描述

最终将查询到的结果返回。

在这里插入图片描述

最后,建议你们把mybatis的执行流程也debug一遍,上面的还不够详细,有些细节还是得自己debug才能发现,第一次发表文章,有什么问题还请在讨论区指出,谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值