06.动态代理设计模式
目录介绍
- 01.为何要动态代理
- 1.1 为何要动态代理
- 1.2 动态代理思考
- 02.动态代理的概念
- 2.1 动态代理定义
- 2.2 动态代理类比理解
- 2.3 动态代理参与者
- 2.4 动态代理步骤
- 03.动态代理的实现
- 3.1 罗列一个场景
- 3.2 用一个例子理解代理
- 3.3 基于接口动态代理
- 3.4 基于类动态代理
- 3.5 动态代理模版代码
- 04.动态代理案例
- 4.1 动态代理和反射
- 4.2 Java中代理
- 4.3 Retrofit核心思想
- 05.动态代理思想和原理
- 5.1 动态代理实现机制
- 5.2 动态代理思想
- 5.3 动态代理存在的问题
- 5.4 动态代理的优势
- 06.动态代理总结
- 6.1 总结一下学习
- 6.2 更多内容推荐
推荐一个好玩网站
一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网
https://yccoding.com/
01.为何要动态代理
1.0 本博客AI摘要
本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在Retrofit中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。
1.1 为何要动态代理
一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
- 代码复用:动态代理可以通过一个通用的代理类来代理多个类,实现代码的复用。不需要为每个被代理类编写一个代理类,减少了代码的冗余。
- 灵活性和扩展性:动态代理在运行时动态地创建代理对象,并可以根据需要动态地添加、修改或删除代理行为。这使得代理行为可以根据不同的需求进行定制和扩展,提供了更大的灵活性和扩展性。
- 解耦合:动态代理通过使用接口或基类来代理对象,实现了代理类和被代理类之间的松耦合。客户端只需要与代理对象进行交互,无需关心具体的实现类,提高了代码的可维护性和可扩展性。
总结起来,静态代理在一些简单的场景下可以使用,但在需要更大的灵活性、扩展性和可维护性时,动态代理更为适合,动态代理在解耦方面提供了巨大的便利。
1.2 动态代理思考
当涉及到动态代理时,有几个方面值得思考:
- 性能考虑:动态代理可能会引入一定的性能开销。在使用动态代理时,需要考虑代理对象的创建和方法调用的性能影响,并权衡是否值得使用代理。
- 异常处理:在代理对象中处理异常是很重要的。确定如何处理原始对象方法中可能抛出的异常,并决定是否需要在代理对象中捕获、处理或转发异常。
- 安全性考虑:了解代理对象的安全性问题。代理对象可能会暴露原始对象的敏感信息或功能,因此需要确保适当的安全措施。
02.动态代理的概念
2.1 动态代理定义
动态代理的概括
代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到JVM中。
动态代理定义
给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
2.2 动态代理类比理解
第一个类比:代理对象是中介,原对象是房东的房子,客户就是我们需要找房的人。
静态代理:房东的房子让中介代理出租,中介代理可以直接控制房子出租,用户是不能找房东直接租房子的,而是要间接通过中介租房子。
动态代理:现在中介代理有很多,链家中介,中原地产中介,21世纪中介,贝壳中介等等,用户在所在区域搜索心仪的房子(公寓,单间,隔断房,床位,整租房等等)后才知道找具体的中介租房子。这就需要用到动态代理!
2.3 动态代理参与者
代理模式的角色分四种:
如下所示
- 主题接口:Subject,是委托对象和代理对象都共同实现的接口,即代理类的所实现的行为接口。
- 目标对象:ReaSubject,是原对象,也就是被代理的对象。
- 代理对象:Proxy,是代理对象,用来封装真是主题类的代理类。
- 客户端:使用代理类和主题接口完成一些工作。
2.4 动态代理步骤
定义业务接口和实现:习惯性地首先定义一个接口,然后提供一个或多个实现。
创建调用处理器:实现InvocationHandler接口的类,它定义了代理实例的调用处理程序。
生成代理实例:通过调用Proxy.newProxyInstance(),传入目标类的类加载器、接口数组和调用处理器来创建。
03.动态代理原理和实现
3.1 罗列一个场景
现在有一个需求:用户找中介租房子,假如根据租房的条件,将客户需求分类:比如需要公寓,单间,隔断房,床位,整租房等等。
为其他对象提供一种代理以控制对这个对象的访问。
某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以再两者之间起到中介作用。运行阶段才指定代理哪个对象。
组成元素:
- 抽象类接口
- 被代理类(具体实现抽象类接口的类)
- 动态代理类,实际调用被代理类的方法和属性
3.2 用一个例子理解代理
Java 实现动态代理主要涉及以下几个类:
- java.lang.reflect.Proxy: 这是生成代理类的主类,通过 Proxy 类生成的代理类都继承了 Proxy 类,即 DynamicProxyClass extends Proxy。
- java.lang.reflect.InvocationHandler: 这里称他为"调用处理器",他是一个接口,我们动态生成的代理类需要完成的具体内容需要自己定义一个类,而这个类必须实现 InvocationHandler 接口。
3.3 基于接口动态代理
基于接口的动态代理是一种常见的动态代理方式,它通过实现Java的Proxy类和InvocationHandler接口来实现。以下是一个基于接口的动态代理的示例:
首先,定义一个接口(例如MyInterface):
public interface MyInterface {
void doSomething();
}
然后,创建一个实现InvocationHandler接口的代理处理器类(例如MyInvocationHandler):
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法调用前执行额外的逻辑
System.out.println("Before method invocation");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 在方法调用后执行额外的逻辑
System.out.println("After method invocation");
return result;
}
}
接下来,创建原始对象并生成代理对象:
MyInterface originalObject = new MyOriginalObject(); // 创建原始对象
MyInvocationHandler invocationHandler = new MyInvocationHandler(originalObject); // 创建代理处理器
MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[]{MyInterface.class},
invocationHandler); // 创建代理对象
最后,通过代理对象调用方法:
proxyObject.doSomething();
在调用proxyObject.doSomething()时,会先执行代理处理器的invoke()方法中的前置逻辑,然后调用原始对象的doSomething()方法,最后执行代理处理器的后置逻辑。
通过基于接口的动态代理,我们可以在不修改原始对象的情况下,为其添加额外的功能或行为,例如日志记录、权限检查、性能监控等。
3.4 基于类动态代理
基于类的动态代理是另一种常见的动态代理方式,它使用第三方库或框架来实现。在Java中,常用的类动态代理库包括CGLIB和Byte Buddy。以下是一个基于CGLIB的类动态代理的示例:
首先,确保在项目中引入CGLIB库的依赖。
然后,定义一个原始类(例如MyClass):
public class MyClass {
public void doSomething() {
System.out.println("Original method");
}
}
接下来,创建一个类动态代理处理器类(例如MyMethodInterceptor),继承自CGLIB的MethodInterceptor接口:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在方法调用前执行额外的逻辑
System.out.println("Before method invocation");
// 调用原始对象的方法
Object result = proxy.invokeSuper(obj, args);
// 在方法调用后执行额外的逻辑
System.out.println("After method invocation");
return result;
}
}
在上述代码中,我们实现了MethodInterceptor接口的intercept()方法,该方法在方法调用前后执行额外的逻辑。通过调用proxy.invokeSuper(obj, args)来调用原始对象的方法。
接着,创建原始对象并生成代理对象:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class); // 设置原始类
enhancer.setCallback(new MyMethodInterceptor()); // 设置代理处理器
MyClass proxyObject = (MyClass) enhancer.create(); // 创建代理对象
使用Enhancer类来创建代理对象。通过setSuperclass()方法设置原始类,setCallback()方法设置代理处理器,然后调用create()方法生成代理对象。
最后,通过代理对象调用方法:
proxyObject.doSomething(); // 调用代理对象的方法
在调用proxyObject.doSomething()时,会先执行代理处理器的intercept()方法中的前置逻辑,然后调用原始对象的doSomething()方法,最后执行代理处理器的后置逻辑。
通过基于类的动态代理,我们可以在不修改原始类的情况下,为其添加额外的功能或行为,例如日志记录、权限检查、性能监控等。
3.5 动态代理模版代码
代码如下所示Demo代码
private void testProxy() {
RealSubject realSubject = new RealSubject();
ProxyHandler proxyHandler = new ProxyHandler(realSubject);
Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(), proxyHandler);
subject.request();
}
interface Subject{
void request();
}
class RealSubject implements Subject{
@Override
public void request(){
System.out.println("request");
}
}
/**
* 代理类的调用处理器
*/
class ProxyHandler implements InvocationHandler {
private final Subject subject;
public ProxyHandler(Subject subject){
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作
System.out.println("====before====");
Object result = method.invoke(subject, args);
System.out.println("====after====");
return result;
}
}
04.动态代理案例
4.1 动态代理和反射
先来了解下反射
- Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
- 这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。
- 反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。
那么为什么要用到反射呢?
4.3 Retrofit核心思想
Retrofit,想必大家都很熟悉,retrofit其实核心内容就是用了动态代理。
想想retrofit是怎么工作的?在interface里面写上需要配置的请求方法,并添加一些注解 然后创建出interface的实例,就可以直接调用方法进行网络请求了。看看代码:
public interface ApiService {
@POST(RetrofitHelper.APP_V1 + "/banner")
Observable<BaseEntity<List<Banner>>> getBanners();
}
//如何调用,如下所示
ApiService service = new Retrofit.Builder().baseUrl("").build().create(ApiService.class);
service.getBanners().enqueue(callback);
只是写了ApiService接口和接口下面的getBanners方法,然后就可以进行网络请求。所以retrofit是代替我们写了网络请求的具体逻辑,也就是完成了代理的这样一个作用。
具体怎么代理的呢?奥秘主要就在这个***.create(ApiService.class)方法***里面,看看源码:
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
看到这个newProxyInstance方法了吧,这就是创建动态代理类的方法。invoke方法里面就是具体去拆解,接口里面方法的一些参数,然后完成网络请求的整个过程了,也就是代理帮你做的一些事情。
05.动态代理思想和原理
5.1 动态代理实现机制
1,通过实现 InvocationHandler 接口创建自己的调用处理器;
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);
2,通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
3,通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
4,通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );
需要注意的是:
- 生成的代理类为public final,不能被继承。
- 类名:格式是“$ProxyN”,N是逐一递增的数字,代表Proxy被第N次动态生成的代理类,要注意,对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。
- Proxy 类是$ProxyN的父类,这个规则适用于所有由 Proxy 创建的动态代理类。(也算是java动态代理的一处缺陷,java不支持多继承,所以无法实现对class的动态代理,只能对于Interface的代理)而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
- 代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,
5.2 动态代理思想
动态代理的思想基于以下几个关键概念:
- 代理对象:代理对象是一个与原始对象具有相同接口的对象,它可以替代原始对象进行方法调用。代理对象在运行时动态生成,可以在方法调用前后执行额外的逻辑。
- 原始对象:原始对象是需要被代理的对象,它定义了业务逻辑的实现。代理对象通过调用原始对象的方法来实现相同的功能。
- 代理处理器:代理处理器是一个中间组件,它实现了代理对象的生成和方法调用的处理逻辑。代理处理器在方法调用前后可以执行额外的逻辑,例如记录日志、权限检查、性能监控等。
- 动态生成:动态代理是在运行时动态生成代理对象的过程。通过使用反射或第三方库,可以在运行时创建代理对象,并将其与原始对象关联起来。
5.3 动态代理存在的问题
尽管动态代理在许多情况下非常有用,但也存在一些问题需要考虑:
- 性能开销:动态代理涉及到反射机制,这可能导致一定的性能开销。相比直接调用原始对象的方法,动态代理需要额外的方法调用和反射操作,可能会导致一些性能损失。
- 限制于接口:Java的动态代理机制只能代理接口,无法直接代理类。这是因为动态代理是基于接口的,它生成的代理对象实现了与原始对象相同的接口。
- 无法代理final类和方法:动态代理无法代理被final修饰的类和方法。这是因为final修饰的类和方法无法被继承或重写,而动态代理需要生成子类或子类的方法来实现代理。
- 无法直接访问私有方法和字段:动态代理无法直接访问私有方法和字段。这是因为反射机制只能访问公共的方法和字段。如果需要访问私有成员,可以通过反射设置可访问性,但这可能会破坏封装性。
- 序列化问题:动态代理对象通常无法序列化。由于代理对象是在运行时动态生成的,无法保证其可序列化。如果需要序列化代理对象,可能需要采用其他方法或框架。
- 调试困难:由于动态代理的逻辑是在运行时生成的,调试可能会变得更加困难。在调试代理对象时,可能需要跟踪反射调用和代理处理器的逻辑。
5.4 动态代理的优势
- 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
- 动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。
06.动态代理总结
6.1 总结一下学习
01.为何要动态代理
一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
02.动态代理的概念
动态代理是指:代理类是在运行期间生成的,也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到JVM中。
动态代理定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
03.动态代理的实现
代理模式的角色分四种:
- 主题接口:Subject,是委托对象和代理对象都共同实现的接口,即代理类的所实现的行为接口。
- 目标对象:ReaSubject,是原对象,也就是被代理的对象。
- 代理对象:Proxy,是代理对象,用来封装真是主题类的代理类。
- 客户端:使用代理类和主题接口完成一些工作。
04.动态代理案例
组成元素:
- 抽象类接口
- 被代理类(具体实现抽象类接口的类)
- 动态代理类,实际调用被代理类的方法和属性
动态代理有两种实现方式:
- 基于接口动态代理。一种常见的动态代理方式,它通过实现Java的Proxy类和InvocationHandler接口来实现。
- 基于类动态代理。另一种常见的动态代理方式,它使用第三方库或框架来实现。在Java中,常用的类动态代理库包括CGLIB。
05.动态代理思想和原理
动态代理实现机制,主要是通过关键类Proxy
和InvocationHandler
来实现的。
- 创建
InvocationHandler
处理器对象,主要作用是通过实现方法,调用从代理类到委托类的分派转发。 - 通过
Proxy
创建代理对象,传入指定 ClassLoader 对象和一组 interface 来创建动态代理类,里面实现是通过反射去动态创建对象。
6.2 更多内容推荐
模块 | 描述 | 备注 |
---|---|---|
GitHub | 多个YC系列开源项目,包含Android组件库,以及多个案例 | GitHub |
博客汇总 | 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 | YCBlogs |
设计模式 | 六大设计原则,23种设计模式,设计模式案例,面向对象思想 | 设计模式 |
Java进阶 | 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM | Java高级 |
网络协议 | 网络实际案例,网络原理和分层,Https,网络请求,故障排查 | 网络协议 |
计算机原理 | 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 | 计算机基础 |
学习C编程 | C语言入门级别系统全面的学习教程,学习三到四个综合案例 | C编程 |
C++编程 | C++语言入门级别系统全面的教学教程,并发编程,核心原理 | C++编程 |
算法实践 | 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 | Leetcode |
Android | 基础入门,开源库解读,性能优化,Framework,方案设计 | Android |
23种设计模式
23种设计模式 & 描述 & 核心作用 | 包括 |
---|---|
创建型模式 提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离 | 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
结构型模式 关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构 | 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
行为型模式 特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题 | 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
6.3 更多内容
- GitHub:https://github.com/yangchong211
- 我的编程网站:https://yccoding.com
- 博客汇总:https://github.com/yangchong211/YCBlogs
- 设计模式专栏:https://github.com/yangchong211/YCDesignBlog
- Java高级进阶专栏:https://github.com/yangchong211/YCJavaBlog
- 网络协议专栏:https://github.com/yangchong211/YCNetwork
- 计算机基础原理专栏:https://github.com/yangchong211/YCComputerBlog