1. Java中常见的机制
1.1 委托机制
委托机制更符合开闭原则和聚合原则,基于水平扩展来增强一个对象的功能,而不是频繁改动原有的代码设计。
委托机制在各种设计模式中特别常见,比如代理模式、装饰者模式、异步调用、回调等等,都是将某一个功能的处理委托给被委托类代为处理,从而实现水平扩展,实现高内聚低耦合的设计原则。
委托实际上是java代码执行流程的灵魂,好多模式都应用了委托机制。通过委托机制可以方便的实现请求流程的转发,即所谓的方法转发。
注意:造成委托机制可以畅通无阻的原因,应该感谢Java中的接口和多态的语法特性,没有多态很多功能将寸步难行。
1.2 调用机制
调用机制是任何程序默认都会存在的机制,程序要想运行下去就必须进行调用。
调用机制分为:
同步调用
异步调用
同步回调
异步回调
1.3 反射机制
反射,最实用的技术,可以在运行的JVM中动态增加新的功能,动态调用正在运行的任何一个类的任何属性和任何方法,可以对任何一个正在运行的java对象进行操作而不需要显式的在编译阶段通过import导入目标类。
反射机制的核心是动态加载机制。
动态代理模式结合反射,使用反射可以获取目标接口的Class对象,然后使用动态字节码机制可以直接生成目标接口的实现类,即字节码class文件,从而达到动态创建代理类的效果。
1.4 动态加载机制
反射的核心原理,通过动态加载编译好的目标类(实际上是从内存中直接获取到目标类的class对象),而不用在编译期间确定.class和.class文件之间的委托关系,从而达到在JVM运行期间动态的加载目标类(加载、链接、初始化),达到动态实例化目标类的目的。
1.5 动态字节码机制
从动态代理模式中可以看出JDK底层的ProxyGenerator.generateProxyClass()方法是最终生成代理类的字节码。
ProxyGenerator是sun.misc包中的类,它没有开源,不过我们可以将其生成的动态字节码通过流的形式保存到本地文件中,然后反编译成java文件去查看1其对应的代理类。
动态字节码机制能够直接在内存中动态的生成字节码文件*.class,该文件存在于内存中,需要的话可以通过流的方式持久化到本地。
动态字节码机制是动态代理模式的核心。
1.6 序列化和反序列化机制
java序列化提供了以二进制字符串的形式在中介上进行对象的传输机制。中介可以是内存、文件、网络io等。
java对象序列化成二进制字符串之后,在中介上传输或者保存。比如通过socket将一个对象序列化后的二进制传传输到另外一个应用节点上。
获取到序列化后的对象之后,可以将二进制字符串反序列化为java。
序列化和反序列化机制是RMQ和其他RPC框架的底层机制。比如dubbo实现远程过程调用依靠的就是序列化和反序列化。
1.7 异常处理机制
jdk对应用程序提供了容错性,即提供了异常处理机制,来允许程序出现异常时不意外终止。
1.8 多线程机制
允许多个线程并发执行,每个线程按照顺序执行,多个线程抢占CPU资源并发交替执行。
在多核CPU的世界中真正实现了多个线程对象并行;单核CPU中是多个线程并发交替抢占CPU资源。
1.9 通用回调接口机制
通用接口在java中是多态的基础,通过所有子类实现通用接口可以方便的实现责任链模式、命名模式等,从而达到多态性统一管理的效果。
在回调模式中,可以将回调逻辑单独抽象出来使用一个接口表示,这个接口叫做回调接口,一般命名为XXXCallback。
如下:
public interface MyCallback{
public void callback();
}
定义完回调接口后,凡是有需要的子类都可以实现该回调接口,实现回调接口的子类我们称之为客户端类。
客户端类必须持有一个服务端类的对象,服务端也必须持有一个客户端类的对象。
当客户端对象调用服务端的某个方法时,服务端回过来再调用客户端的callback方法。
此时客户端和服务端就完成了一套回调逻辑。
回调模式也使用了委托机制,只不过客户端委托了服务端,服务端又反过来委托了客户端,这正是回调模式之所以叫做“回”的原因。
因此,回调模式也能够方便的实现方法转发,将自己发起的请求最终又回转到自己身上。
1.10 通用钩子接口机制
java中有一种机制和上面说的回调接口机制特别像。
先来分析下上面的回调接口机制:
回调接口由服务端指定
回调逻辑实现由客户端实现
请求流程发起必须由客户端操作服务端对象发起
服务端对象持有客户端对象,反过来调用客户端对象的回调方法
上面的流程中,客户端持有服务端对象,服务端也必须持有客户端对象,这是回调能够完成“回”的核心前提。
但是假如现在有这样一个场景:
客户端持有服务端的对象,发起请求
服务端持有一个其他对象,当接收到客户端请求后执行该对象的方法
在整个流程中,随着服务端的启动,服务端内部预留了很多钩子,这些钩子需要用户自己去实现。
这些钩子就叫做钩子接口。
通用钩子接口的核心是:
将客户端发起的请求转发到钩子接口的实现对象上去,而这个钩子对象并不是客户端去实现的,而是服务端暴露给用户在其他地方实现的。
举例子吧:
比如Servlet里面的Servlet、Filter、Listener,都是web容器启动时预留的钩子对象,需要我们用户自己去实现。只要你实现了,则服务启动后就会在合适的时机调用这些钩子对象。
再比如大名鼎鼎的AOP,它的底层使用jdk的动态代理或者cglib的动态代理。
不论是jdk的动态代理还是cglib的动态代理,它底层都是委托了一个钩子对象(jdk是InvocationHandler接口,cglib是MethodInterceptor接口),这些钩子对象是代理类预留给我们的口子,需要我们自己去实现。
实现后,代理类就能将请求转发到该钩子对象上。
2. Java中想要扩展一个对象的功能一般有三种方案
2.1 继承
继承的方式去扩展父类的功能,和代理模式、装饰者模式相比,会增加很多子类的实现,而且类和类之间的耦合度比较高,比较臃肿,不符合高内聚低耦合的设计原则。
2.2 代理模式
代理模式增强对象功能,很强大,尤其是动态代理模式,最难理解的一种设计模式。
2.3 装饰者模式
装饰者模式分为全量装饰和增量装饰。
全量装饰意味着覆盖被装饰者的目标方法;增量装饰意味着依旧调用被装饰者的目标方法,即委托被装饰者对象去处理;但是在调用方法前或者方法后可以加入自己的逻辑。
一般都使用增量装饰,因为增量装饰更符合装饰者模式的设计理念。
但是有的情况下,不得不用全量装饰。
最常见的是全量装饰和增量装饰结合使用。
文档信息
- 本文作者:Marshall