代理模式:
- 静态代理
- 动态代理
1. 静态代理模式
1.1 故事背景(结婚)
有一天,小明要结婚了,他有两种方式来完成结婚这件事。
- 第一种就是小明亲力亲为,包揽婚礼所有事情。
- 第二种就是找婚庆公司来帮忙筹办婚礼,小明只需要人来就行。这里的婚庆公司就是代理,婚礼的筹办过程就是代理模式的应用。
在这个例子里,一共有3个角色:
- 小明:真实角色,因为是小明要结婚。
- 婚庆公司:代理角色,帮小明处理婚礼事宜。
- 结婚(接口):抽象角色,这个是小明和婚庆公司都要做的事情,只是两者具体做的内容不一样。
1.2 代码示例
用代码来实现上述的静态代理模式的思想,如下:
注意:代理角色和真实角色都要实现同一接口!
public class StacticProxy {
public static void main(String[] args) {
//4. 使用代理模式,演示整个婚礼流程
WeddingCompany weddingCompany = new WeddingCompany(new XiaoMing());
weddingCompany.HappyMarry();
}
}
//1. 代理角色和真实角色都要实现的接口
interface Marry{
void HappyMarry();
}
//2. 真实角色,小明要结婚
class XiaoMing implements Marry{
@Override
public void HappyMarry() {
System.out.println("小明出场,完成自己该做的事情");
}
}
//3. 代理角色,帮助小明结婚
class WeddingCompany implements Marry {
//真实角色
private Marry target;
//构造器
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚之后,收尾款");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}
执行结果:
结婚之前,布置现场
小明出场,完成自己该做的事情
结婚之后,收尾款
1.3 故事背景(租房子)
角色分析:
- 真实角色:房东,被代理的角色;
- 代理角色:中介,代理真实角色,一般会做一些附属操作;
- 抽象角色:出租房子,一般使用接口或者抽象类来实现;
- 客户:租客,使用代理角色来进行一些操作;
1.4 代码示例
1、抽象角色:Rent.java
// 租房
public interface Rent {
public void rent();
}
2、真实角色:Host.java
// 房东,房东出租房子
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
3、代理角色:Proxy.java
public class Proxy implements Rent{
// 组合:整体的生命周期结束也就意味着部分的生命周期结束
// 聚合:整体和部分可以具有各自的生命周期
// 组合优于继承,但这里是聚合
private Host host;
public Proxy(Host host) {
this.host = host;
}
public Proxy() {
}
@Override
public void rent() {
seeHouse();
host.rent();
hetong();
fare();
}
// 看房
public void seeHouse(){
System.out.println("中介带你看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签合同
public void hetong(){
System.out.println("签租赁合同");
}
}
4、测试:即客户角色,Client.java
public class Client {
public static void main(String[] args) {
// 房东要租房子
Host host = new Host();
// 代理,中介要帮房东租房子,但是呢?代理角色一般都会有一些附属操作!
Proxy proxy = new Proxy(host);
// 你不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
1.5 思想
所谓静态代理模式也就是说,在程序运行之前,代理类就已经由程序员创建好并编译成.class
文件( 在真正举办婚礼之前,婚庆公司就已经准备好婚礼方案、婚礼用品等),代理角色和真实角色的关系在运行前就确定了。
代理对象可以做很多真实对象做不了的事情,真实对象只需要专注做自己的事情就行。
1.6 优缺点
优点:
- 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情;
- 公共的业务由代理来完成,实现了业务的分工;
- 公共业务发生扩展时变得更加集中和方便;
- 代码结构简单,较容易实现。
缺点 :
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低;
- 无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则。
我们想要静态代理的优点,又不想要静态代理的缺点,所以就有了动态代理 !
1.7 知识扩展
new Thread( ()->System.out.println("这里是一个Runnable接口的实现类") ).start();
new WeddingCompany(new XiaoMing()).HappyMarry();
比较上面两行代码,可以发现他们非常相似。第一行代码也用到了代理模式,Thread类扮演代理角色,Runnable接口的实现类扮演真实角色,两者都实现了Runnable接口,在Runnable接口里,只有一个抽象方法run()
,这里虽然调用的是start()
方法,但是实际执行的还是run()
方法。
2. 动态代理模式
动态代理
- 在程序的执行过程中,使用 JDK 的反射机制,动态地创建代理类对象,并指定要代理的目标类,然后加载到 JVM 中供我们后续使用;
- 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的;
- 动态代理的角色和静态代理的一样;
- 真实角色 / 目标角色
- 代理角色
- 抽象角色
- 动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理;
- 基于接口的动态代理:JDK动态代理
- 基于类的动态代理:cglib、JAVAssist
Javassist
现在比较多是用 Javassist 来生成动态代理。Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。它已加入了开放源代码 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态 “AOP” 框架。
关于 Java 字节码的处理,有很多工具,如 bcel、asm,不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用 javassist,它是 jboss 的一个子项目。其主要的优点,在于简单,而且快速。直接使用 Java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
JDK动态代理 与 CGLIB 的区别
- JDK动态代理:需要真实角色类实现接口,生成的代理角色类也会实现相应的接口;
- CGLIB动态代理:不需要真实角色类实现某些接口,生成的代理类为真实角色类的子类;
2.1 JDK 动态代理
我们这里使用 JDK 的原生代码来实现,其余动态代理实现方式的道理都是一样的!
JDK的动态代理需要了解两个类:InvocationHandler
和 Proxy
。
InvocationHandler
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
InvocationHandler 是 java.lang.reflect 包下的一个接口类,它只有一个 invoke()
抽象方法,该方法需要传入3个参数:
- proxy:代理对象
- method:代理对象中的某个方法名
- args:method方法需要的参数
Proxy
package java.lang.reflect;
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
private Proxy() {}
protected Proxy(InvocationHandler h) {}
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException{}
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{}
public static boolean isProxyClass(Class<?> cl) { }
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { }
}
我们重点来看 newProxyInstance()
这个方法,它需要传入3个参数:
- loader:类加载器
- interfaces:接口(抽象角色)
- h:InvocationHandler 实例对象
作用:根据 类加载器、接口、InvocationHandler 生成代理类。生成的代理类实现了接口,且通过传入的类加载器加载到虚拟机,然后再反射生成代理类的实例,反射生成的过程中会将 InvocationHandler 传递给父类的构造方法,因此实例化的代理类对象会持有 InvocationHandler 的引用。
2.2 代码例子(租房子)
1、抽象角色:Rent.java
// 租房、抽象角色、接口
public interface Rent {
public void rent();
}
2、真实角色:Host.java
// 房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
3、ProxyInvocationHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 待会我们会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
// private Object target; // 改为这个即可通用,适用于多种业务场景
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
// this.getClass().getClassLoader() ===> sun.misc.Launcher$AppClassLoader@18b4aac2
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制实现!
seeHouse();
Object result = method.invoke(rent, args);
hetong();
fare();
return result;
}
// 看房
public void seeHouse(){
System.out.println("中介带你看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签合同
public void hetong(){
System.out.println("签租赁合同");
}
}
4、测试类:Client.java
// 租客
public class Client {
public static void main(String[] args) {
// 配置系统属性为true,代理类生成时将自动写入磁盘
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 真实角色
Host host = new Host();
// 代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 设置我们要调用的目标对象
pih.setRent(host);
//动态生成对应的代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
执行结果:
中介带你看房
房东要出租房子!
签租赁合同
收中介费
2.3 动态生成的代理类 Class 文件
JDK动态代理 Class 文件的生成有两个条件:
- 必须在main方法中执行,直接用 junit 的 test 方法调用无法生成;
- 在 main 方法最前面增加
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
配置,这样会输出代理 Class 文件。
我们上面已经在测试类中添加了该行配置代码,现在直接到目录下找到相应的文件即可。代理 class 的生成路径是在 idea 的工作空间下的 com\sun\proxy 目录中,如下图所示:
把文件拖到 idea 中,查看 $Proxy0.class 文件的内容:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.sun.proxy;
import demo03.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) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
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 void rent() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
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() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("demo03.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());
}
}
}
先看下代理类生成的流程图:
我们使用 Proxy.newProxyInstance() 方法生成 $Proxy0
类实例对象时 ,传入了3个参数,参数1是加载器,参数2是接口,参数3是 InvocationHandler 对象 h。现在说说这几个参数的作用: $Proxy0
类实现了接口(参数2),通过加载器(参数1)加载到虚拟机中,它的构造器会将对象 h (参数3)传给父类 Proxy 类的构造方法,也就是说生成的 $Proxy0
类实例对象会持有 InvocationHandler 实例对象 h 的引用。
当生成的代理类执行接口里的方法 rent() 时,本质是调用 InvocationHandler 实例对象 h 的invoke() 方法。
2.4 优缺点
- 优点:能够动态适配特定的代理场景,扩展性较好,符合软件工程的开闭原则;
- 缺点:动态代理需要利用到反射机制和动态生成字节码,导致其性能会比静态代理稍差一些,但是相比于优点,这些劣势几乎可以忽略不计。