Tomcat 架构原理解析到架构设计借鉴 (6)

我们再来考虑另一个问题,那就是系统的可扩展性。因为各个组件init() 和 start() 方法的具体实现是复杂多变的,比如在 Host 容器的启动方法里需要扫描 webapps 目录下的 Web 应用,创建相应的 Context 容器,如果将来需要增加新的逻辑,直接修改start() 方法?这样会违反开闭原则,那如何解决这个问题呢?开闭原则说的是为了扩展系统的功能,你不能直接修改系统中已有的类,但是你可以定义新的类。

组件的 init() 和 start() 调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式

以下就是 Lyfecycle 接口的定义:

重用性:LifeCycleBase 抽象基类

再次看到抽象模板设计模式。

有了接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。

Tomcat 定义一个基类 LifeCycleBase 来实现 LifeCycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。

public abstract class LifecycleBase implements Lifecycle{ // 持有所有的观察者 private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>(); /** * 发布事件 * * @param type Event type * @param data Data associated with event. */ protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } } // 模板方法定义整个启动流程,启动所有容器 @Override public final synchronized void init() throws LifecycleException { //1. 状态检查 if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { //2. 触发 INITIALIZING 事件的监听器 setStateInternal(LifecycleState.INITIALIZING, null, false); // 3. 调用具体子类的初始化方法 initInternal(); // 4. 触发 INITIALIZED 事件的监听器 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); throw new LifecycleException( sm.getString("lifecycleBase.initFail",toString()), t); } } }

Tomcat 为了实现一键式启停以及优雅的生命周期管理,并考虑到了可扩展性和可重用性,将面向对象思想和设计模式发挥到了极致,Containaer接口维护了容器的父子关系,Lifecycle 组合模式实现组件的生命周期维护,生命周期每个组件有变与不变的点,运用模板方法模式。 分别运用了组合模式、观察者模式、骨架抽象类和模板方法

如果你需要维护一堆具有父子关系的实体,可以考虑使用组合模式。

观察者模式听起来 “高大上”,其实就是当一个事件发生后,需要执行一连串更新操作。实现了低耦合、非侵入式的通知与更新机制。

Container 继承了 LifeCycle,StandardEngine、StandardHost、StandardContext 和 StandardWrapper 是相应容器组件的具体实现类,因为它们都是容器,所以继承了 ContainerBase 抽象基类,而 ContainerBase 实现了 Container 接口,也继承了 LifeCycleBase 类,它们的生命周期管理接口和功能接口是分开的,这也符合设计中接口分离的原则

Tomcat 为何打破双亲委派机制 双亲委派

我们知道 JVM的类加载器加载 Class 的时候基于双亲委派机制,也就是会将加载交给自己的父加载器加载,如果 父加载器为空则查找Bootstrap 是否加载过,当无法加载的时候才让自己加载。JDK 提供一个抽象类 ClassLoader,这个抽象类中定义了三个关键方法。对外使用loadClass(String name) 用于子类重写打破双亲委派:loadClass(String name, boolean resolve)

public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 查找该 class 是否已经被加载过 Class<?> c = findLoadedClass(name); // 如果没有加载过 if (c == null) { // 委托给父加载器去加载,递归调用 if (parent != null) { c = parent.loadClass(name, false); } else { // 如果父加载器为空,查找 Bootstrap 是否加载过 c = findBootstrapClassOrNull(name); } // 若果依然加载不到,则调用自己的 findClass 去加载 if (c == null) { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } protected Class<?> findClass(String name){ //1. 根据传入的类名 name,到在特定目录下去寻找类文件,把.class 文件读入内存 ... //2. 调用 defineClass 将字节数组转成 Class 对象 return defineClass(buf, off, len); } // 将字节码数组解析成一个 Class 对象,用 native 方法实现 protected final Class<?> defineClass(byte[] b, int off, int len){ ... }

JDK 中有 3 个类加载器,另外你也可以自定义类加载器,它们的关系如下图所示。

类加载器

BootstrapClassLoader是启动类加载器,由 C 语言实现,用来加载 JVM启动时所需要的核心类,比如rt.jar、resources.jar等。

ExtClassLoader是扩展类加载器,用来加载\jre\lib\ext目录下 JAR 包。

AppClassLoader是系统类加载器,用来加载 classpath下的类,应用程序默认用它来加载类。

自定义类加载器,用来加载自定义路径下的类。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjzfz.html