模式
-
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
-
动态代理
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象
组成
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
优点
-
职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
-
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
-
高扩展性
代码演示
租房子的例子,有房东、中介还有租房子的业务,对应于代理模式的组成就是
租房子的业务 --> 抽象角色
房东 --> 真实角色
中介 --> 代理角色
按照这个思路,静态代理如下:
/**
* 抽象角色:租房子的业务
* 按照代理模式,这里可以是接口或者抽象类,这里我用的是接口
* @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才能发现,第一次发表文章,有什么问题还请在讨论区指出,谢谢大家。