目录
1、概述
2、结构
2.1、角色分类
2.2、类图
3、静态代理
3.1、案例类图
3.2、案例代码
4、JDK 动态代理
4.1、案例代码
4.2、底层原理
4.3、执行流程说明
5、CGLib 动态代理
5.1、案例代码
6、三种代理的对比
6.1、JDK代理和CGLib代理
6.2、动态代理和静态代理
7、优缺点
7.1、优点
7.2、缺点
8、使用场景
8.1、远程(Remote)代理
8.2、防火墙(Firewall)代理
8.3、保护(Project or Access)代理
1、概述
由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的媒介。
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。
Java 中的代理按照生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则在 Java 运行时动态生成,程序结束时内存会自动释放。动态代理又分为 JDK 动态代理和 CGLib 动态代理。
2、结构
2.1、角色分类
代理(Proxy)模式分为三种角色,分别如下:
(1)抽象主题(Subject)角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
(2)具体主题(Real Subject)角色:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,也叫做被委托角色、被代理角色。
(3)代理(Proxy)角色:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
2.2、类图
3、静态代理
静态代理是由程序创建或者特定工具自动生成源代码,在程序运行前,代理类的.class文件已经存在。
案例:
房东出租房屋,将出租和收回房子的事情都交给中介来完成,中介出租房屋后可以赚取到中介费。
3.1、案例类图
3.2、案例代码
/**
* @ClassName IHouseOwner
* @Description 出租房屋的接口-抽象主题角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public interface IHouseOwner {
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
void rentHouse();
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
void takeBackHouse();
}
/**
* @ClassName HouseOwner
* @Description 房东-具体主题角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class HouseOwner implements IHouseOwner {
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
@Override
public void rentHouse() {
System.out.println("房屋闲置,房东要出租房屋");
}
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
@Override
public void takeBackHouse() {
System.out.println("房屋到期,房东要收回房屋");
}
}
/**
* @ClassName HouseOwnerProxy
* @Description 代理角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class HouseOwnerProxy implements IHouseOwner {
private IHouseOwner houseOwner;
public HouseOwnerProxy(IHouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
@Override
public void rentHouse() {
this.houseOwner.rentHouse();
System.out.println("帮房东出租了房子,挣取佣金2000元");
}
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
@Override
public void takeBackHouse() {
this.houseOwner.takeBackHouse();
System.out.println("帮房东收回了房子");
}
}
/**
* @ClassName Client
* @Description 业务场景
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class Client {
public static void main(String[] args) {
//创建抽象主题角色
IHouseOwner houseOwner = new HouseOwner();
//创建代理类,通过代理类来完成具体主题角色需要做的事情
HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);
proxy.rentHouse();
proxy.takeBackHouse();
}
}
执行结果:
房屋闲置,房东要出租房屋
帮房东出租了房子,挣取佣金2000元
房屋到期,房东要收回房屋
帮房东收回了房子
从上面代码中可以看出业务场景直接访问的是 IHouseOwner 类对象,也就是说 IHouseOwner 作为访问对象和目标对象的中介,同时也对 rentHouse() 和 takeBackHouse() 方法做出了增强。
4、JDK 动态代理
JDK 动态代理要求目标对象实现一个接口。
Java 中提供了一个动态代理类 Proxy,Proxy 不是静态代理中所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance())来获取代理对象。
4.1、案例代码
在JDK动态代理中,不再需要去手动创建一个代理类来完成目标对象的执行方法。
/**
* @ClassName IHouseOwner
* @Description 抽象主题角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public interface IHouseOwner {
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
void rentHouse();
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
void takeBackHouse();
}
/**
* @ClassName HouseOwner
* @Description 房东-具体主题角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class HouseOwner implements IHouseOwner {
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
@Override
public void rentHouse() {
System.out.println("房屋闲置,房东要出租房屋");
}
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
@Override
public void takeBackHouse() {
System.out.println("房屋到期,房东要收回房屋");
}
}
/**
* @ClassName ProxyFactory
* @Description 获取代理对象的工厂类
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class ProxyFactory {
//1、声明目标对象
private HouseOwner houseOwner = new HouseOwner();
/**
* @Description: 获取代理对象的方法
* @Author: chengjunyu
*/
public IHouseOwner getHouseOwner() {
//2、返回代理对象
/*
* ClassLoader loader: 类加载器,用于加载代理类(程序运行中动态的在内存中生成的类),通过目标对象获取
* Class<?>[] interfaces:代理类实现的接口的字节码对象
* InvocationHandler h:代理对象的调用处理程序
*/
IHouseOwner proxyObject = (IHouseOwner) Proxy.newProxyInstance(
houseOwner.getClass().getClassLoader(),
houseOwner.getClass().getInterfaces(),
//匿名内部类,重写 invoke 方法
new InvocationHandler() {
/*
* Object proxy:代理对象,和 proxyObject 是同一个对象,在 invoke 方法中基本上不使用
* Method method:对接口中的方法进行封装的 method 对象
* Objects[] args:调用方法的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = method.invoke(houseOwner, args);
if ("rentHouse".equals(method.getName())) {
System.out.println("帮房东出租了房子,挣取佣金2000元");
}
if ("takeBackHouse".equals(method.getName())) {
System.out.println("帮房东收回了房子");
}
return object;
}
}
);
return proxyObject;
}
}
/**
* @ClassName Client
* @Description 业务场景
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class Client {
public static void main(String[] args) {
//获取代理对象
//1、创建代理对象工厂
ProxyFactory proxyFactory = new ProxyFactory();
//2、使用factory对象的方法获取代理对象
IHouseOwner houseOwner = proxyFactory.getHouseOwner();
//调用租房方法
houseOwner.rentHouse();
//调用收回房屋方法
houseOwner.takeBackHouse();
}
}
执行结果:
房屋闲置,房东要出租房屋
帮房东出租了房子,挣取佣金2000元
房屋到期,房东要收回房屋
帮房东收回了房子
4.2、底层原理
在学习JDK动态代理的底层原理之前,可以先考虑一个问题:ProxyFactory是代理类吗?
ProxyFactory 不是代理模式中所说的代理类,代理类是程序在运行过程中动态的在内存中生成的类。
我么可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构,操作步骤如下:
1、运行指令 java -jar arhtas-boot.jar 启动 Arthas;
2、运行指令 jad com.sun.proxy.$Proxy0 获取该类源码;
3、优化源码,优化后的源码如下:
package com.sun.proxy;
import com.design.pattern.proxy.jdkProxy.IHouseOwner;
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 IHouseOwner {
private static Method m1;
private static Method m2;
//构造方法提供了有参构造,参数为 InvocationHandler 对象,赋值给了父类 Proxy
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
//获取IHouseOwner字节码对象后获取方法
m1 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("rentHouse", new Class[0]);
m2 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("takeBackHouse", new Class[0]);
}
//调用了 InvocationHandler 的子实现类对象的 invoke 方法
public final void rentHouse() {
//此处h即为InvocationHandler对象,this表示本类,m1为rentHouse方法
this.h.invoke(this, m1, null);
}
public final void takeBackHouse() {
this.h.invoke(this, m2, null);
}
}
//父类Proxy
public class Proxy {
protected InvocationHandler invocationHandler;
}
从上面的类中,我们可以看到以下几个信息:
1、代理类($Proxy0)实现了 IHouseOwner ,这也就印证了我们之前说的真实类和代理类实现同样的接口;
2、代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
4.3、执行流程说明
根据JDK动态代理示例代码和底层原理代码,我们可以分析出JDK动态代理的执行流程如下:
1、在业务场景中通过代理对象调用 rentHouse() 和 takeBackHouse();
2、根据多态的特性,执行的是代理类($Proxy0)中的 rentHouse() 和 takeBackHouse();
3、代理类($Proxy0)中的 rentHouse() 方法和 takeBackHouse() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke() 方法;
4、invoke 方法通过反射执行了真实对象所属类(HouseOwner)中的rentHouse() 和 takeBackHouse()。
5、CGLib 动态代理
有时候目标对象只是一个单独的对象,并没有实现接口,这个时候就可以使用CGLIB代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
CGLIB动态代理基于继承来实现代理,所以无法对 final 类、private 和 static 方法实现代理。
5.1、案例代码
同样是上面的案例,这里我们使用CGLIB代理实现。
如果没有定义IHouseOwner接口,只定义了HouseOwner(房屋所有者类),很显然JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以在Spring框架下需要引入jar包的maven坐标。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
代码如下:
/**
* @ClassName HouseOwner
* @Description 房东-具体主题角色
* @Author chengjunyu
* @Date 2022/12/8
* @Version V1.0
*/
public class HouseOwner {
/**
* @Description: 出租房屋
* @Author: chengjunyu
*/
public void rentHouse() {
System.out.println("房屋闲置,房东要出租房屋");
}
/**
* @Description: 收回房屋
* @Author: chengjunyu
*/
public void takeBackHouse() {
System.out.println("房屋到期,房东要收回房屋");
}
}
/**
* @ClassName ProxyFactory
* @Description 代理工厂类
* @Author chengjunyu
* @Date 2022/12/10
* @Version V1.0
*/
public class ProxyFactory implements MethodInterceptor {
private HouseOwner houseOwner = new HouseOwner();
public HouseOwner getProxyObject() {
//1、创建Enhancer对象,类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
//2、设置父类的字节码对象,指定父类
enhancer.setSuperclass(HouseOwner.class);
//3、设置回调函数
enhancer.setCallback(this);
//4、创建代理对象,即目标类对象的子对象
HouseOwner proxyObject = (HouseOwner) enhancer.create();
return proxyObject;
}
/**
* @Description: 方法所属类的回调函数
* @Author: chengjunyu
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用了回调函数");
Object object = method.invoke(this.houseOwner, objects);
return object;
}
}
/**
* @ClassName Client
* @Description 业务场景类
* @Author chengjunyu
* @Date 2022/12/10
* @Version V1.0
*/
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
HouseOwner proxyObject = factory.getProxyObject();
//调用代理对象中的方法
proxyObject.rentHouse();
proxyObject.takeBackHouse();
}
}
6、三种代理的对比
6.1、JDK代理和CGLib代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行处理,因为CGLib原理是动态生成被代理的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib的代理效率低一些,但是到JDK1.8的时候,JDK代理效率高于CGLib代理,所以如果有接口使用JDK动态代理,如果没有接口则使用CGLib代理。
6.2、动态代理和静态代理
动态代理和静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中的方法处理(InvocationHandler.invoke)。这样,在接口中方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加了一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现这个方法,增加了代理维护的复杂度,而动态代理不会出现这样的问题。
7、优缺点
7.1、优点
(1)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
(2)代理对象可以扩展目标对象的功能;
(3)代理模式能够将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
7.2、缺点
(1)增加了系统的复杂度。
8、使用场景
8.1、远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个端口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
8.2、防火墙(Firewall)代理
当你将浏览器配置称为使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
8.3、保护(Project or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的访问权限。