文章目录
- 一、代理模式
- 1.1 代理模式概念
- 1.2 代理模式的目的
- 1.3 代理模式的三个角色
- 1.4 代理模式的两种实现方式
- 1.5 代理模式的优点
- 1.6 代理模式的缺点
- 1.7 适用场景
- 二、静态代理
- 2.1 静态代理
- 2.2 动态代理
- 2.2.1 JDK动态代理
- 2.2.2 CGLIB动态代理
- 2.2.3 JDK 动态代理和 CGLIB 动态代理对比
一、代理模式
1.1 代理模式概念
代理模式(Proxy Pattern)
,属于结构型模式。使用一个类代表另一个类的功能,在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
比如生活中常见的中介与租房,租房者不需要直接与房东交互,房东把房屋租赁交给中介代理。
1.2 代理模式的目的
代理模式的目的有:
- 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性
- 通过代理对象对访问进行控制;
1.3 代理模式的三个角色
代理模式一般会有三个角色:
-
抽象角色:
指代理角色和真实角色对外提供的公共方法,一般为一个接口。 -
真实角色:
需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是。真正的业务逻辑在此。 -
代理角色
需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法。并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!
1.4 代理模式的两种实现方式
代理模式可以分为静态代理和动态代理两种实现方式:
-
静态代理
在编译时已经确定代理类的具体实现,需要为每个被代理类编写一个代理类。静态代理不灵活,但易于理解和实现。
-
动态代理
在运行时动态创建代理对象,无需为每个被代理类编写单独的代理类。Java
提供了java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来支持动态代理
,通过这些类可以在运行时生成代理对象
1.5 代理模式的优点
- 代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。
- 代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证等。
- 代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。
- 代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。
1.6 代理模式的缺点
- 引入代理类会增加系统的复杂性,增加了学习和理解的成本。
- 由于增加了代理层,导致请求处理速度变慢。
1.7 适用场景
代理模式主要适用于需要控制、增强或隐藏对象访问的场景。适合代理访问的具体场景如下:
- 远程代理:当客户端需要访问远程对象(位于不同地址空间或网络中)时,可以使用代理模式来隐藏底层网络通信的复杂性,代理对象负责处理网络通信,并将结果返回给客户端。
- 虚拟代理:当创建和初始化对象的开销很大时,可以使用代理模式延迟对象的实例化,只有在需要真正使用对象时才进行初始化。这样可以提高系统的性能和资源利用率。
- 安全代理:代理模式可以用于控制对敏感资源的访问,代理对象可以验证客户端的权限或者在访问资源前执行一些安全检查,从而保护真实对象。
- 日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。
- 延迟加载代理:当需要使用的对象具有较大的开销时,可以使用代理模式来实现延迟加载,只有在真正需要时才加载对象,以节省资源和提高响应速度。
- 缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。
二、静态代理
2.1 静态代理
静态代理是在编译时确定代理类的具体实现,即代理类的源代码在编译期间就已经确定。通常情况下,需要手动编写代理类,且代理类与委托类实现同样的接口或继承同样的父类。静态代理的实现比较简单,但是当需要代理的类较多时,会导致代理类数量庞大,维护困难。
以下是静态代理的示例代码:
// 定义接口
interface Subject {
void doAction();
}
// 目标对象类
class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("RealSubject: executing action.");
}
}
// 代理对象类
class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doAction() {
System.out.println("ProxySubject: before action.");
realSubject.doAction();
System.out.println("ProxySubject: after action.");
}
}
// 使用代理对象
public class StaticProxyExample {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxySubject proxy = new ProxySubject(realSubject);
proxy.doAction();
}
}
2.2 动态代理
2.2.1 JDK动态代理
动态代理是在运行时动态生成代理类的方式,无需手动编写代理类。Java 提供了 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来支持动态代理。通过 Proxy.newProxyInstance
方法创建代理对象,并传入一个实现了 InvocationHandler
接口的对象,动态代理对象会在运行时生成。
以下是动态代理的示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Subject {
void doAction();
}
// 目标对象类
class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("RealSubject: executing action.");
}
}
// 动态代理处理器
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("ProxySubject: before action.");
Object result = method.invoke(target, args);
System.out.println("ProxySubject: after action.");
return result;
}
}
// 使用动态代理
public class DynamicProxyExample {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
new DynamicProxyHandler(realSubject)
);
proxy.doAction();
}
}
在动态代理示例中,Proxy.newProxyInstance
方法会根据指定的类加载器、接口和 InvocationHandler
对象动态生成代理对象,实现了对目标对象的动态代理。
2.2.2 CGLIB动态代理
首先,你需要在项目中添加CGLIB
的依赖。如果你使用的是Maven
,可以在pom.xml
文件中添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.4.0</version>
</dependency>
接下来,我们创建一个接口Subject
,它代表真实对象和代理对象之间的共享接口:
public interface Subject {
void doSomething();
}
然后,创建一个实现了Subject
接口的真实对象RealSubject
类:
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject: 正在做一些事情...");
}
}
接下来,创建一个实现了CGLIB的MethodInterceptor
接口的ProxyInterceptor
类:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyInterceptor implements MethodInterceptor {
private Object target;
// 构造方法,接受一个目标对象
public ProxyInterceptor(Object target) {
this.target = target;
}
// 拦截方法调用
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法调用前");
// 调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("方法调用后");
return result;
}
}
最后,我们创建一个主类Main
来演示如何使用CGLIB进行动态代理:
import net.sf.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
// 创建真实对象
RealSubject realSubject = new RealSubject();
// 创建拦截器
ProxyInterceptor interceptor = new ProxyInterceptor(realSubject);
// 使用CGLIB的Enhancer来创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class); // 设置代理类的父类为真实对象类
enhancer.setCallback(interceptor); // 设置回调为我们的拦截器
// 创建代理对象
Subject proxySubject = (Subject) enhancer.create();
// 调用方法,通过代理对象
proxySubject.doSomething();
}
}
运行Main
类时,它将使用CGLIB创建一个代理对象,并通过代理对象调用doSomething()
方法。ProxyInterceptor
将拦截方法调用,在方法调用前后执行一些操作,然后将实际的方法调用委托给真实对象。
这个例子演示了如何使用CGLIB来实现动态代理,允许我们在不修改原始类的情况下增强其功能。希望这个解释对你理解CGLIB动态代理有所帮助。
2.2.3 JDK 动态代理和 CGLIB 动态代理对比
JDK动态代理
和CGLIB动态代理
是两种常见的Java动态代理实现方式,它们在实现原理、适用场景和特点上有一些区别。
-
实现原理:
- JDK动态代理:基于接口的动态代理。JDK动态代理要求目标类必须实现一个或多个接口,它利用Java的反射机制生成目标类的代理对象,代理对象实现了目标接口,并且可以拦截接口方法的调用。
- CGLIB动态代理:基于继承的动态代理。CGLIB通过继承目标类并重写其方法来实现代理,因此不要求目标类必须实现接口。
-
适用场景:
- JDK动态代理适用于要代理的类实现了接口的情况,因为JDK动态代理生成的代理对象实现了接口,可以被强制类型转换为接口类型。
- CGLIB动态代理适用于要代理的类没有实现接口的情况,或者你希望在运行时创建目标类的子类来作为代理。
-
性能表现:
- 一般情况下,JDK动态代理比CGLIB动态代理更快,因为JDK动态代理是基于Java自带的反射包实现的,而CGLIB则需要通过ASM字节码处理库来生成代理类。
- 但是,对于目标类没有实现接口的情况,使用CGLIB可以实现代理,而JDK动态代理则无法对非接口类进行代理。
总的来说,如果目标类已经实现了接口,并且你希望代理对象是强制类型转换后的接口类型,那么可以选择JDK动态代理;如果目标类没有实现接口,或者你希望在运行时创建目标类的子类来作为代理,那么可以选择CGLIB动态代理。