Tomcat реализован в виде контейнера сервлетов, представляющего собой легкий сервер приложений, разработанный на основе языка Java. Поскольку Tomcat является сервером приложений, он обладает преимуществами полного открытого исходного кода, небольшого веса, стабильной производительности и низкой стоимости развертывания, поэтому он стал первым выбором для разработки Java и развертывания приложений.Его использовал почти каждый веб-разработчик Java. вы поняли и подумали об общем дизайне Tomcat?
Эта статья будет основана на Tomcat8 для анализа, конкретная версия является последней версией текущего официального сайта Tomcat8 (2019-11-21 09:28)v8.5.49
Общая структура
В общей структуре Tomcat много модулей.На следующем рисунке перечислены основные модули в структуре, которую мы будем анализировать. Среди них основной анализ — Service, Connector, Engine, Host, Context, Wrapper. Чтобы слой не выглядел слишком грязным, следующее изображениеn
От имени компонента может существовать более одного.
Как показано на рисунке выше: Server — это сервер tomcat, и в Server может быть несколько служб. Каждая служба может иметь несколько соединителей и один механизм Servlet Engine, а несколько соединителей в службе соответствуют одному движку. В каждом движке может быть несколько доменных имен.Здесь концепция виртуального хоста может использоваться для представления хоста. На каждом хосте может быть несколько контекстов приложений. Отношения между сервером, службой, соединителем, механизмом, хостом, контекстом и оболочкой, за исключением соединителя и механизма, которые являются параллельными отношениями, и существуют другие отношения. При этом они также наследуют интерфейс Lifecycle, обеспечивающий управление жизненным циклом, в том числе:Инициализировать (init), запустить (start), остановить (stop), уничтожить (уничтожить). Когда его родительский контейнер запускается, вызывается запуск его дочернего контейнера, и то же самое верно для остановки.
На рисунке выше вы также можете видеть, что Engine, Host, Context и Wrapper наследуются от Container. она имеетbackgroundProcess()
метод, асинхронная обработка в фоновом режиме, поэтому после его наследования можно легко создавать асинхронные потоки.
В Tomcat7 видно, что Service содержит Container вместо Engine. Предполагается, что именно поэтому добавлен метод Engine, названный в текущей версии.setContainer
.
Server
Доступно в исходном коде Tomcatorg.apache.catalina.Server
интерфейс, соответствующий класс реализации по умолчаниюorg.apache.catalina.core.StandardServer
, интерфейс предоставляет следующие методы.
На рисунке выше вы можете узнать, что делает сервер: операции управления сервисом, адресом, портом, каталиной и глобальными ресурсами именования. Когда сервер будет инициализирован, он загрузит данные, настроенные в нашем server.xml.
Вот работа Сервиса в немaddService
Добавьте новую услугу в определенный набор услуг для анализа:
// 保存服务的服务集
private Service services[] = new Service[0];
final PropertyChangeSupport support = new PropertyChangeSupport(this);
@Override
public void addService(Service service) {
// 相互关联
service.setServer(this);
// 利用同步锁,防止并发访问 来源:https://ytao.top
synchronized (servicesLock) {
Service results[] = new Service[services.length + 1];
// copy 旧的服务到新的数组中
System.arraycopy(services, 0, results, 0, services.length);
// 添加新的 service
results[services.length] = service;
services = results;
// 如果当前 server 已经启动,那么当前添加的 service 就开始启动
if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// 使用观察者模式,当被监听对象属性值发生变化时通知监听器,remove 是也会调用。
support.firePropertyChange("service", null, service);
}
}
Как видно в исходниках, после добавления службы на сервер, служба будет запускаться случайным образом, и по сути, она же и будет запускать службу.
Service
Основная обязанность сервисной службы заключается в сборке соединителя и двигателя вместе. Цель их разделения состоит в том, чтобы отделить мониторинг запросов и обработку запросов, а также обеспечить лучшую масштабируемость. Каждая служба независима друг от друга, но совместно использует JVM и библиотеку системных классов. предоставлено здесьorg.apache.catalina.Service
Интерфейсы и классы реализации по умолчаниюorg.apache.catalina.coreStandardService
.
В классе реализации StandardService основной анализsetContainer
иaddConnector
два метода.
private Engine engine = null;
protected final MapperListener mapperListener = new MapperListener(this);
@Override
public void setContainer(Engine engine) {
Engine oldEngine = this.engine;
// 判断当前 Service 是否有关联 Engine
if (oldEngine != null) {
// 如果当前 Service 有关联 Engine,就去掉当前关联的 Engine
oldEngine.setService(null);
}
// 如果当前新的 Engine 不为空,那么 Engine 关联当前 Service,这里是个双向关联
this.engine = engine;
if (this.engine != null) {
this.engine.setService(this);
}
// 如果当前 Service 启动了,那么就开始启动当前新的 Engine
if (getState().isAvailable()) {
if (this.engine != null) {
try {
this.engine.start();
} catch (LifecycleException e) {
log.error(sm.getString("standardService.engine.startFailed"), e);
}
}
// 重启 MapperListener ,获取一个新的 Engine ,一定是当前入参的 Engine
try {
mapperListener.stop();
} catch (LifecycleException e) {
log.error(sm.getString("standardService.mapperListener.stopFailed"), e);
}
try {
mapperListener.start();
} catch (LifecycleException e) {
log.error(sm.getString("standardService.mapperListener.startFailed"), e);
}
// 如果当前 Service 之前有 Engine 关联,那么停止之前的 Engine
if (oldEngine != null) {
try {
oldEngine.stop();
} catch (LifecycleException e) {
log.error(sm.getString("standardService.engine.stopFailed"), e);
}
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldEngine, this.engine);
}
/**
* 实现方式和 StandardServer#addService 类似,不在细述
* 注意,Connector 这里没有像 Engine 一样与 Service 实现双向关联
*/
@Override
public void addConnector(Connector connector) {
synchronized (connectorsLock) {
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (getState().isAvailable()) {
try {
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
Connector
Соединитель в основном используется для получения запросов, а затем передачи их в Engine для обработки запросов, а затем отправки их обратно клиенту после их обработки. Протоколы, поддерживаемые текущей версией: HTTP, HHTP/2, AJP, NIO, NIO2, APR. Ключевые особенности включают в себя:
- Прослушивает порт сервера для чтения клиентских запросов.
- Разберите протокол и передайте его соответствующему контейнеру для обработки запроса.
- Вернуть обработанную информацию клиенту
Пример сведений о конфигурации в коннекторе, соответствующем серверу server.xml:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
Здесь, настроив номер порта прослушиванияport
, указав протокол обработкиprotocol
и адрес перенаправленияredirectPort
.
Тип обработки протокола задается созданием экземпляра коннектора:
public Connector() {
// 无参构造,下面 setProtocol 中默认使用HTTP/1.1
this(null);
}
public Connector(String protocol) {
// 设置当前连接器协议处理类型
setProtocol(protocol);
// 实例化协议处理器,并保存到当前 Connector 中
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
}
/**
* 这个设置再 tomcat9 中被移除,改为必配项
*/
public void setProtocol(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
// 这里指定了默认协议和 HTTP/1.1 一样
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
}
} else {
// 最后如果不是通过指定 HTTP/1.1,AJP/1.3 类型的协议,就通过类名实例化一个协议处理器
setProtocolHandlerClassName(protocol);
}
}
ProtocolHandler — это обработчик протокола, предоставляющий разные реализации для разных запросов. Когда класс реализации AbstractProtocol инициализируется, в конце будет вызываться абстрактный класс AbstractEndpoint, чтобы запустить поток для прослушивания порта сервера.Когда запрос получен, процессор будет вызван для чтения запроса, а затем передан Движок для обработки запроса.
Engine
Двигатель соответствует,org.apache.catalina.Engine
интерфейс иorg.apache.catalina.core.StandardEngine
Класс реализации по умолчанию.
Функция Engine также относительно проста и связана с ассоциацией отношений контейнеров.
Но в классе реализацииaddChild()
Я не имею в виду субджин, а только Host. При этом родительского контейнера нет.setParent
Настройки операции не разрешены.
@Override
public void addChild(Container child) {
// 添加的子容器必须是 Host
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
@Override
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
server.xml может настроить наши данные:
<!-- 配置默认Host,及jvmRoute -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
Host
Хост представляет собой виртуальный хост. Мы должны настроить несколько доменных имен для нашего сервера, например demo.ytao.top, dev.ytao.top. Затем нам нужно настроить два разных хоста для обработки запросов на разные доменные имена. Когда доменное имя входящего запроса — demo.ytao.top, он будет искать контекст под доменным именем Host. Таким образом, наш файл конфигурации server.xml также содержит эту конфигурацию:
<!-- name 设置的时虚拟主机域名 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
Context
Когда вы переходите к Context, у вас есть рабочая среда Servlet. Engine и Host в основном поддерживают отношения контейнера, но не имеют рабочей среды.
Пока мы можем понимать Context как приложение, например, если у нас есть два приложения, ytao-demo-1 и ytao-demo-2 в корневом каталоге, то здесь есть два Context.
В основном представлено здесьaddChild
добавленным подконтейнером является Wrapper:
@Override
public void addChild(Container child) {
// Global JspServlet
Wrapper oldJspServlet = null;
// 这里添加的子容器只能时 Wrapper
if (!(child instanceof Wrapper)) {
throw new IllegalArgumentException
(sm.getString("standardContext.notWrapper"));
}
// 判断子容器 Wrapper 是否为 JspServlet
boolean isJspServlet = "jsp".equals(child.getName());
// Allow webapp to override JspServlet inherited from global web.xml.
if (isJspServlet) {
oldJspServlet = (Wrapper) findChild("jsp");
if (oldJspServlet != null) {
removeChild(oldJspServlet);
}
}
super.addChild(child);
// 将servlet映射添加到Context组件
if (isJspServlet && oldJspServlet != null) {
/*
* The webapp-specific JspServlet inherits all the mappings
* specified in the global web.xml, and may add additional ones.
*/
String[] jspMappings = oldJspServlet.findMappings();
for (int i=0; jspMappings!=null && i<jspMappings.length; i++) {
addServletMappingDecoded(jspMappings[i], child.getName());
}
}
}
Это также центр управления сервлетами в каждом приложении.
Wrapper
Wrapper — это центр управления сервлетами, он имеет весь жизненный цикл сервлетов, не имеет подконтейнеров, потому что сам является контейнером самого нижнего уровня. Вот основной анализ загрузки сервлета:
public synchronized Servlet loadServlet() throws ServletException {
// 如果已经实例化或者用实例化池,就直接返回
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// 如果 servlet 类名为空,直接抛出 Servlet 异常
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
// 从 Context 中获取 Servlet
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// 加载声明了 MultipartConfig 注解的信息
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
// 对 servlet 类型进行检查
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
// 初始化 servlet
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
Здесь загружается сервлет Если экземпляр сервлета не был создан, обязательно загрузите его.
До сих пор я примерно представил основные компоненты Tomcat8, а также имею общее представление об общей архитектуре Tomcat.После рефакторинга исходного кода Tomcat читабельность действительно намного лучше.Рекомендуется попробовать проанализировать некоторые из используемых шаблонов проектирования, у нас все еще есть определенное эталонное значение в фактическом процессе кодирования.
личный блог: ytao.top
Мой официальный аккаунт ytao