代理模式是一种结构型设计模式,用于控制对其他对象的访问。代理模式在访问对象时引入了一个代理对象,这个代理对象可以充当访问对象的接口,以控制对实际对象的访问。代理模式可以用于多种情况,包括延迟加载、访问控制、监视和远程代理等。、
结构
代理模式通常包含以下几个结构:
-
Subject(主题): 定义了一个共用的接口,代理和真实主题都实现这个接口。这是客户端直接访问的对象。
-
Real Subject(真实主题): 实际的对象,代理所代表的真实对象。代理模式的目的是控制对真实主题的访问,因此代理对象通常持有一个真实主题的引用。
-
Proxy(代理): 代理对象,实现了与主题相同的接口,并持有一个对真实主题的引用。代理对象控制客户端对真实主题的访问,并在必要时将请求传递给真实主题。
java
// 主题接口
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
realSubject = new RealSubject();
}
@Override
public void request() {
// 在访问真实主题之前可以执行一些额外的操作
System.out.println("Proxy: Performing pre-processing.");
realSubject.request();
// 在访问真实主题之后可以执行一些额外的操作
System.out.println("Proxy: Performing post-processing.");
}
}
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request();
}
}
在这个示例中,Proxy
类充当了代理,它持有一个对 RealSubject
的引用,并在访问 RealSubject
之前和之后执行额外的操作。客户端代码通过代理访问主题对象,代理控制了对真实主题的访问。这个示例演示了代理模式的基本概念。
在Java中,代理可以分为两种主要类型:静态代理和动态代理。它们都可以用于生成不同的代理类,但它们的生成方式和使用场景有所不同。
静态代理
静态代理是在编译时生成代理类的方式。代理类通常是手动编写的,并且与真实主题接口具有相同的方法签名。静态代理通过组合或继承真实主题类,然后在代理类中调用真实主题类的方法,可以在调用前后添加额外的逻辑。
静态代理的典型应用场景包括权限验证、日志记录和性能监控等。
下面是一个简单的静态代理示例:
java
// 主题接口
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
realSubject = new RealSubject();
}
@Override
public void request() {
System.out.println("Proxy: Performing pre-processing.");
realSubject.request();
System.out.println("Proxy: Performing post-processing.");
}
}
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request();
}
}
动态代理
动态代理是在运行时生成代理类的方式。Java提供了两种动态代理机制:基于接口的动态代理和基于类的动态代理。它们都使用Java的反射机制来创建代理对象。
动态代理通常更灵活,因为它可以代理任何实现了接口的类,而不需要手动编写代理类。它的应用场景包括AOP(面向切面编程)和远程方法调用等。
在Java中,有两种主要的动态代理方式:
-
基于接口的动态代理 :这是最常见的动态代理方式。在这种方式下,代理类必须实现一个或多个接口,代理对象会实现这些接口并代理接口中的方法。Java提供了
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现基于接口的动态代理。示例代码中的
Proxy.newProxyInstance()
就是基于接口的动态代理的典型使用方式。 -
基于类的动态代理:这种方式与基于接口的动态代理不同,它不要求代理类实现接口,而是要求代理类继承自特定的类。基于类的动态代理通常使用字节码生成库,如CGLIB(Code Generation Library)来生成代理类。CGLIB会生成一个继承自目标类的代理类,并覆盖目标类中的方法,实现代理逻辑。
基于接口的动态代理
以下是一个基于接口的动态代理示例:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 主题接口
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 动态代理处理器
class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
/**
* Object proxy:代理对象。在invoke方法中基本不用
* Method method:对接口中的方法进行封装的method对象
* Object[] args:调用方法的实际参数
* 返回值
*
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Dynamic Proxy: Performing pre-processing.");
Object result = method.invoke(realSubject, args);
System.out.println("Dynamic Proxy: Performing post-processing.");
return result;
}
}
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
/**
* ClassLoader loader:类加载器,用于加载代理类。可以通过目标对象获取类加载器
* Class<?>[] interfaces:代理类实现的接口的字节码对象
* InvocationHandler h:代理对象的调用处理程序
*/
Subject proxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
proxy.request();
}
}
在上面代码中,Proxy.newProxyInstance
方法用于创建动态代理对象,DynamicProxyHandler
作为代理处理器处理代理方法的调用,并在调用前后添加额外的逻辑。
或许上面动态代理的代码有点晦涩难懂,下面就举个具体的例子
实现一个简单计算器(整数的加减乘除)
首先定义一个接口 Calculator
,在其中定义了一些数学计算操作:
java
interface Calculator {
int add(int a,int b);
int subtract(int a,int b);
int multiply(int a, int b);
int divide(int a,int b);
}
创建一个该接口的具体实现类
java
class BasicCalculator implements Calculator{
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
@Override
public int multiply(int a, int b) {
return a * b;
}
@Override
public int divide(int a, int b) {
if(b==0){
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
}
使用动态代理来创建一个代理对象,该代理对象可以拦截 Calculator
接口的方法调用并在方法调用前后执行一些额外的逻辑。
首先,创建一个实现了 InvocationHandler
接口的代理处理器类
java
class CalculatorProxy implements InvocationHandler {
private final Object calculate;
public CalculatorProxy(Object calculate) {
this.calculate = calculate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的逻辑
System.out.println("执行的是"+method.getName()+"计算");
// 调用目标对象的方法
Object result = method.invoke(calculate, args);
// 方法调用后的逻辑
if(method.getExceptionTypes().length!=0){
System.out.println("计算出错!!");
}else{
System.out.print("计算结果为:");
}
return result;
}
}
最后创建一个Client
类,并使用Proxy来创建代理对象
java
class Client {
public static void main(String[] args) {
// 创建目标对象
Calculator calculator = new BasicCalculator();
// 创建代理处理器
CalculatorProxy handler = new CalculatorProxy(calculator);
// 创建动态代理对象
Calculator proxy = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class<?>[] {Calculator.class},
handler
);
// 调用代理对象的方法
System.out.println(proxy.add(10, 20));
System.out.println(proxy.subtract(15, 9));
System.out.println(proxy.multiply(3, 4));
System.out.println(proxy.divide(10, 3));
System.out.println(proxy.divide(10, 0));
}
}
值得注意的是,CalculatorProxy
是动态代理中的代理处理器,它用于生成动态代理对象,而不是实际的代理类。动态代理对象实际上是在运行时(在内存中)创建的,可以用于代理多个不同的目标对象,而不需要为每个目标对象手动创建一个代理类。
基于类的动态代理
如果没有定义接口,则无法使用基于接口的动态代理方式,当目标类无法修改或不允许修改,但仍然需要在不改变其代码的情况下添加额外的行为等场景下,都需要使用基于类的动态代理
基于类的动态代理是一种通过生成继承目标类的代理类来实现代理的方式。这种代理方式不要求目标类实现接口,因为它是通过继承目标类并重写方法来实现代理的。在Java中,常用的基于类的动态代理实现工具是CGLIB(Code Generation Library)。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
由于CGLIB是第三方包,所以这里采用maven方式将其引入
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
接下来,还是以上面计算器为例。
假设只有一个目标类,其没有实现任何接口
java
class BasicCalculator{
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
if(b==0){
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
}
想要创建一个基于类的动态代理,以在方法调用前后记录日志。首先,需要使用CGLIB库创建代理类。
java
class CalculatorProxy implements MethodInterceptor {
private BasicCalculator calculator = new BasicCalculator();
public BasicCalculator getProxy(){
// 创建Enhancer对象,相当于JDK代理(基于接口动态代理)中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setSuperclass(BasicCalculator.class);
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
return (BasicCalculator) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 方法调用前的逻辑
System.out.println("执行的是"+method.getName()+"计算");
// 调用目标对象的方法
Object result = method.invoke(calculator, args);
// 方法调用后的逻辑
if(method.getExceptionTypes().length!=0){
System.out.println("计算出错!!");
}else{
System.out.print("计算结果为:");
}
return result;
}
}
接着实现Client类测试
java
class Client {
public static void main(String[] args) {
// 创建代理工厂对象
CalculatorProxy calculatorProxy = new CalculatorProxy();
// 获取代理对象
BasicCalculator proxy = calculatorProxy.getProxy();
// 调用代理对象中的方法
System.out.println(proxy.add(10, 20));
System.out.println(proxy.subtract(15, 9));
System.out.println(proxy.multiply(3, 4));
System.out.println(proxy.divide(10, 3));
System.out.println(proxy.divide(10, 0));
}
}
三种代理方式的对比
这三种代理方式分别是:静态代理、JDK动态代理和CGLIB动态代理。它们在实现方式和使用场景上有不同的特点。
静态代理:
- 实现方式:在编译期间手动创建代理类,代理类与目标类实现相同的接口或继承相同的父类,并且在代理类中调用目标类的方法。
- 优点 :
- 易于理解和实现。
- 可以在编译期间进行类型检查。
- 缺点 :
- 需要为每个目标类创建一个代理类,导致类数量增加。
- 静态代理不够灵活,如果有多个目标类需要代理,会导致代码重复。
- 适用场景:适用于目标类不频繁变化,且只需要代理少数几个类的情况。
JDK动态代理:
- 实现方式 :使用Java的反射机制,通过
java.lang.reflect.Proxy
类和InvocationHandler
接口创建代理类,代理类必须实现一个或多个接口。 - 优点 :
- 不需要手动创建代理类,减少了代码重复。
- 可以代理实现了接口的目标类。
- 支持动态代理多个目标类。
- 缺点 :
- 只能代理实现了接口的类。
- 对于非接口方法无法代理。
- 适用场景:适用于需要代理的类实现了接口的情况,例如AOP。
CGLIB动态代理:
- 实现方式:使用字节码生成技术,通过生成目标类的子类作为代理类,代理类继承自目标类。
- 优点 :
- 可以代理没有实现接口的类。
- 可以代理非公共方法。
- 不要求目标类实现接口,更灵活。
- 缺点 :
- 生成的代理类可能会增加类加载时间和内存使用。
- 不能代理
final
方法和类。
- 适用场景:适用于需要代理非接口类或非公共方法的情况,例如Hibernate的持久化框架。
相同点
- 代理目的相同:静态代理、JDK动态代理和CGLIB动态代理都用于在不修改目标对象的情况下,为目标对象提供额外的功能或控制访问。
- 代理模式:它们都是代理设计模式的实现方式,具有相似的结构,包括代理类、目标类和接口(或父类)。
不同点
- 实现方式 :
- 静态代理:在编译期间手动创建代理类,代理类与目标类实现相同的接口或继承相同的父类。
- JDK动态代理:使用Java的反射机制,在运行时动态生成代理类,代理类必须实现一个或多个接口。
- CGLIB动态代理:通过生成目标类的子类作为代理类,在运行时动态生成代理类,不要求目标类实现接口。
- 代理能力 :
- 静态代理:代理类必须手动编写,不能代理多个不同的目标类,只能代理实现了接口的目标类。
- JDK动态代理:可以代理多个不同的目标类,但只能代理实现了接口的目标类。
- CGLIB动态代理:可以代理多个不同的目标类,包括没有实现接口的类。
- 性能 :
- 静态代理:性能一般较好,因为代理类在编译期间生成,不会引入运行时的性能开销。
- JDK动态代理:性能相对较好,但比静态代理略有性能开销,因为需要使用反射生成代理类。
- CGLIB动态代理:性能较差,因为它需要在运行时生成字节码,可能会引入更多的性能开销。
- 代理对象类型 :
- 静态代理:代理对象是编译时已知的,因此类型是确定的。
- JDK动态代理:代理对象在运行时生成,类型是动态确定的。
- CGLIB动态代理:代理对象也在运行时生成,类型是动态确定的,通常是目标类的子类。
总之,选择静态代理、JDK动态代理还是CGLIB动态代理取决于具体的需求。静态代理适用于简单的情况,JDK动态代理适用于代理接口实现的类,而CGLIB动态代理适用于代理非接口类和非公共方法的情况。
代理模式使用场景
代理模式是一种结构型设计模式,它用于控制对其他对象的访问,允许为对象提供间接的代理。代理对象通常作为被代理对象的接口的替代者,用于控制对被代理对象的访问。以下是一些代理模式的常见使用场景:
-
远程代理(Remote Proxy):
- 场景:当对象位于不同的地址空间(例如,网络服务器上)时,代理对象可以在客户端和远程对象之间充当中介,使客户端能够访问远程对象。
- 示例:远程方法调用(RMI)、Web服务代理。
-
虚拟代理(Virtual Proxy):
- 场景:用于延迟创建或初始化昂贵的对象,只有在真正需要时才会创建。
- 示例:加载大型图像或复杂文档时的虚拟代理,以避免启动时的性能问题。
-
保护代理(Protection Proxy):
- 场景:用于控制对对象的访问权限,只有在满足特定条件时才能访问。
- 示例:文件权限控制、访问控制列表(ACL)。
-
缓存代理(Caching Proxy):
- 场景:用于缓存对象的操作结果,以提高访问速度。
- 示例:缓存代理可以用于数据库查询结果的缓存,以减少数据库访问。
-
智能引用代理(Smart Reference Proxy):
- 场景:用于在访问对象时添加额外的控制逻辑,如引用计数、垃圾回收等。
- 示例:Java中的
java.lang.ref
包中的弱引用、软引用等。
-
日志记录代理(Logging Proxy):
- 场景:用于记录对象的方法调用和操作,以进行调试或日志记录。
- 示例:应用程序日志记录、性能分析。
-
计算属性代理(Virtual Proxy for Calculated Properties):
- 场景:用于延迟计算某些属性的值,只有在需要时才进行计算。
- 示例:计算图形对象的面积或周长时的虚拟代理。
-
动态代理(Dynamic Proxy):
- 场景:用于在运行时创建代理对象,通常用于实现AOP(面向切面编程)。
- 示例:Spring框架中的AOP就是一种动态代理的应用。
代理模式的核心思想是通过引入一个代理对象来控制对目标对象的访问,从而实现各种不同的功能和控制策略。在实际应用中,代理模式有助于解耦和提供更好的控制和管理对象的访问。