Оригинальная ссылка:Анализ принципа реализации встроенного Tomcat в SpringBoot
Для веб-проекта SpringBoot основным флагом зависимости является starter spring-boot-starter-web Модуль spring-boot-starter-web не имеет кода в весенней загрузке, но содержится только в pom.xml Некоторые зависимости, включая веб , webmvc, tomcat и т. д.:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
Контейнер веб-службы Spring Boot по умолчанию — tomcat.Если вы хотите использовать Jetty для замены Tomcat, вы можете обратиться к официальной документации, чтобы решить эту проблему.
web, webmvc, tomcat и т. д. обеспечивают рабочую среду веб-приложений, а spring-boot-starter — это переключатель, позволяющий этим рабочим средам работать (поскольку spring-boot-starter косвенно представит spring-boot-autoconfigure ).
Автоматическая настройка веб-сервера
В модуле spring-boot-autoconfigure есть класс автоконфигурации ServletWebServerFactoryAutoConfiguration, который имеет дело с WebServer.
ServletWebServerFactoryAutoConfiguration
Фрагмент кода выглядит следующим образом:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration
Два условия указывают, что текущая операционная среда является веб-службой, основанной на стандартной спецификации сервлета:
- ConditionalOnClass(ServletRequest.class): указывает, что в настоящее время должна быть зависимость servlet-api.
- ConditionalOnWebApplication(type = Type.SERVLET): только веб-приложение на основе сервлета.
@EnableConfigurationProperties(ServerProperties.class): конфигурация ServerProperties включает общие свойства конфигурации, такие как server.port.
пройти через @ImportИмпортируйте классы автоматической конфигурации, связанные со встроенным контейнером, включая EmbeddedTomcat, EmbeddedJetty и EmbeddedUndertow.
В целом, класс автоматической настройки ServletWebServerFactoryAutoConfiguration в основном выполняет следующие функции:
- Импортируется внутренний класс BeanPostProcessorsRegistrar, который реализует ImportBeanDefinitionRegistrar, который может реализовать ImportBeanDefinitionRegistrar для регистрации дополнительных BeanDefinitions.
- Сначала импортируется ServletWebServerFactoryConfiguration.EmbeddedTomcat и другая конфигурация встроенного контейнера (в основном мы фокусируемся на конфигурации, связанной с tomcat).
- ServletWebServerFactoryCustomizer, TomcatServletWebServerFactoryCustomizer зарегистрированы два компонента типа WebServerFactoryCustomizer.
Ниже приводится подробный анализ этих моментов.
BeanPostProcessorsRegistrar
Код внутреннего класса BeanPostProcessorsRegistrar выглядит следующим образом (часть кода опущена):
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
// 省略代码
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
// 注册 WebServerFactoryCustomizerBeanPostProcessor
registerSyntheticBeanIfMissing(registry,
"webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
// 注册 errorPageRegistrarBeanPostProcessor
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
// 省略代码
}
В приведенном выше коде зарегистрированы два bean-компонента, один WebServerFactoryCustomizerBeanPostProcessor и один errorPageRegistrarBeanPostProcessor; оба они реализуют BeanPostProcessor-подобный интерфейс и принадлежат постпроцессору bean-компонента, который используется для добавления некоторой логической обработки до и после инициализации Боб.
- WebServerFactoryCustomizerBeanPostProcessor: Функция состоит в том, чтобы вызывать те WebServerFactoryCustomizer, которые вводятся вышеприведенным классом автоматической конфигурации при инициализации WebServerFactory, а затем вызывать метод настройки в WebServerFactoryCustomizer для обработки WebServerFactory.
- errorPageRegistrarBeanPostProcessor: аналогично предыдущему, но обрабатывает ErrorPageRegistrar.
Вот краткий обзор кода в WebServerFactoryCustomizerBeanPostProcessor:
public class WebServerFactoryCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
// 省略部分代码
// 在 postProcessBeforeInitialization 方法中,如果当前 bean 是 WebServerFactory,则进行
// 一些后置处理
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
// 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
// 省略部分代码
}
Два компонента Customizer Bean, зарегистрированные в классе автоконфигурации
Эти два кастомайзера фактически обрабатывают некоторые значения конфигурации и привязывают их к своим соответствующим фабричным классам.
WebServerFactoryCustomizer
Привяжите значение конфигурации serverProperties к экземпляру объекта ConfigurableServletWebServerFactory.
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
// 端口
map.from(this.serverProperties::getPort).to(factory::setPort);
// address
map.from(this.serverProperties::getAddress).to(factory::setAddress);
// contextPath
map.from(this.serverProperties.getServlet()::getContextPath)
.to(factory::setContextPath);
// displayName
map.from(this.serverProperties.getServlet()::getApplicationDisplayName)
.to(factory::setDisplayName);
// session 配置
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
// ssl
map.from(this.serverProperties::getSsl).to(factory::setSsl);
// jsp
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
// 压缩配置策略实现
map.from(this.serverProperties::getCompression).to(factory::setCompression);
// http2
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
// serverHeader
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
// contextParameters
map.from(this.serverProperties.getServlet()::getContextParameters)
.to(factory::setInitParameters);
}
TomcatServletWebServerFactoryCustomizer
По сравнению с приведенным выше, этот настройщик в основном имеет дело со значениями конфигурации, связанными с Tomcat.
@Override
public void customize(TomcatServletWebServerFactory factory) {
// 拿到 tomcat 相关的配置
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
// server.tomcat.additional-tld-skip-patterns
if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.getTldSkipPatterns()
.addAll(tomcatProperties.getAdditionalTldSkipPatterns());
}
// server.redirectContextRoot
if (tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory,
tomcatProperties.getRedirectContextRoot());
}
// server.useRelativeRedirects
if (tomcatProperties.getUseRelativeRedirects() != null) {
customizeUseRelativeRedirects(factory,
tomcatProperties.getUseRelativeRedirects());
}
}
WebServerFactory
Интерфейс маркера для фабрик, создающих веб-серверы.
архитектура класса
На приведенном выше рисунке показаны все отношения структуры классов WebServerFactory -> TomcatServletWebServerFactory.
TomcatServletWebServerFactory
TomcatServletWebServerFactory — это реализация фабричного класса для получения Tomcat в качестве веб-сервера.Основным методом является getWebServer, который получает экземпляр объекта веб-сервера.
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建一个 Tomcat 实例
Tomcat tomcat = new Tomcat();
// 创建一个 Tomcat 实例工作空间目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建连接对象
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
// 1
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
// 配置 Engine,没有什么实质性的操作,可忽略
configureEngine(tomcat.getEngine());
// 一些附加链接,默认是 0 个
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 2
prepareContext(tomcat.getHost(), initializers);
// 返回 webServer
return getTomcatWebServer(tomcat);
}
- 1. CustomizeConnector: установите порт, protocolHandler, uriEncoding и т. д. для соединителя. Логика построения соединителя в основном состоит в том, чтобы выбрать протокол в выборе NIO и APR, а затем подумать о создании экземпляра и принудительно использовать его в ProtocolHandler.
- 2. prepareContext Это означает не подготовку контекстной информации текущей операционной среды Tomcat, а подготовку StandardContext, то есть подготовку веб-приложения.
Подготовьте контейнер контекста веб-приложения
Для Tomcat каждый контекст сопоставляется с веб-приложением, поэтому prepareContext сопоставляет веб-приложение с TomcatEmbeddedContext, а затем добавляет его в Host.
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
// 创建一个 TomcatEmbeddedContext 对象
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
// 设置描述此容器的名称字符串。在属于特定父项的子容器集内,容器名称必须唯一。
context.setName(getContextPath());
// 设置此Web应用程序的显示名称。
context.setDisplayName(getDisplayName());
// 设置 webContextPath 默认是 /
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot
: createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
// 注册一个FixContextListener监听,这个监听用于设置context的配置状态以及是否加入登录验证的逻辑
context.addLifecycleListener(new FixContextListener());
// 设置 父 ClassLoader
context.setParentClassLoader(
(this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
// 覆盖Tomcat的默认语言环境映射以与其他服务器对齐。
resetDefaultLocaleMapping(context);
// 添加区域设置编码映射(请参阅Servlet规范2.4的5.4节)
addLocaleMappings(context);
// 设置是否使用相对地址重定向
context.setUseRelativeRedirects(false);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldSkipPatterns(context);
// 设置 WebappLoader ,并且将 父 classLoader 作为构建参数
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
// 设置 WebappLoader 的 loaderClass 值
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
// 会将加载类向上委托
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
// 是否注册 jspServlet
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
// 在 host 中 加入一个 context 容器
// add时给context注册了个内存泄漏跟踪的监听MemoryLeakTrackingListener,详见 addChild 方法
host.addChild(context);
//对context做了些设置工作,包括TomcatStarter(实例化并set给context),
// LifecycleListener,contextValue,errorpage,Mime,session超时持久化等以及一些自定义工作
configureContext(context, initializersToUse);
// postProcessContext 方法是空的,留给子类重写用的
postProcessContext(context);
}
Как видно из вышеизложенного, WebappLoader может изменить значение loaderClass с помощью двух методов setLoaderClass и getLoaderClass. Таким образом, это означает, что мы можем сами определить класс, который наследует webappClassLoader, чтобы заменить реализацию по умолчанию, поставляемую с системой.
Инициализировать TomcatWebServer
В конце метода getWebServer нужно построить TomcatWebServer.
// org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
// new 一个 TomcatWebServer
return new TomcatWebServer(tomcat, getPort() >= 0);
}
// org.springframework.boot.web.embedded.tomcat.TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 初始化
initialize();
}
Вот в основном метод инициализации, который запустит службу tomcat
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
// 对全局原子变量 containerCounter+1,由于初始值是-1,
// 所以 addInstanceIdToEngineName 方法内后续的获取引擎并设置名字的逻辑不会执行
addInstanceIdToEngineName();
// 获取 Context
Context context = findContext();
// 给 Context 对象实例生命周期监听器
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// 将上面new的connection以service(这里是StandardService[Tomcat])做key保存到
// serviceConnectors中,并将 StandardService 中的connectors 与 service 解绑(connector.setService((Service)null);),
// 解绑后下面利用LifecycleBase启动容器就不会启动到Connector了
removeServiceConnectors();
}
});
// 启动服务器以触发初始化监听器
this.tomcat.start();
// 这个方法检查初始化过程中的异常,如果有直接在主线程抛出,
// 检查方法是TomcatStarter中的 startUpException,这个值是在 Context 启动过程中记录的
rethrowDeferredStartupExceptions();
try {
// 绑定命名的上下文和classloader,
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// 设置失败不需要关心
}
// :与Jetty不同,Tomcat所有的线程都是守护线程,所以创建一个非守护线程
// (例:Thread[container-0,5,main])来避免服务到这就shutdown了
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
Поиск контекста — это фактически поиск веб-приложения в Tomcat. хост-контейнера, если тип контейнера — контекст, он будет возвращен напрямую.
private Context findContext() {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof Context) {
return (Context) child;
}
}
throw new IllegalStateException("The host does not contain a Context");
}
Процесс запуска Tomcat
Запуск Tomcat выполняется в методе инициализации TomcatWebServer.
// Start the server to trigger initialization listeners
this.tomcat.start();
Метод запуска org.apache.catalina.startup.Tomcat:
public void start() throws LifecycleException {
// 初始化 server
getServer();
// 启动 server
server.start();
}
Инициализировать сервер
Инициализация сервера фактически создает экземпляр объекта StandardServer.Для сервера в Tomcat см. инструкции в приложении.
public Server getServer() {
// 如果已经存在的话就直接返回
if (server != null) {
return server;
}
// 设置系统属性 catalina.useNaming
System.setProperty("catalina.useNaming", "false");
// 直接 new 一个 StandardServer
server = new StandardServer();
// 初始化 baseDir (catalina.base、catalina.home、 ~/tomcat.{port})
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
резюме
Процесс встраивания Tomcat в SpringBoot разобран выше, процесс этот на самом деле не сложный, то есть в процессе обновления контекста Spring запускается контейнер Tomcat, и текущее приложение привязывается к Context, а затем к Host добавляется. . На следующем рисунке показан стек выполнения программы и время инициализации и запуска встроенного Tomcat.
Весь процесс кратко описан ниже:
- Зарегистрируйте связанные Bean-компоненты с помощью пользовательской конфигурации, включая некоторые фабрики и постпроцессоры и т. д.
- На этапе обновления контекста выполните создание WebServer, где необходимо использовать Bean, зарегистрированный на предыдущем этапе.
- В том числе создание ServletContext
- создать экземпляр веб-сервера
- Создайте экземпляр Tomcat, создайте коннектор Connector
- Привязка применяется к ServletContext, добавляются связанные слушатели в рамках жизненного цикла, а затем к хосту добавляется контекст.
- Создайте экземпляр веб-сервера и запустите службу Tomcat
Метод Fatjar SpringBoot не обеспечивает логику реализации совместного использования Tomcat, то есть два запуска FATJAT могут создавать только один экземпляр Tomcat (включая Connector и Host).Из предыдущего анализа мы знаем, что каждое веб-приложение (соответствующее приложение FATJAT ) Экземпляр Сопоставляется с Контекстом; для режима войны несколько Контекстов могут быть смонтированы под Хостом.
Приложение: Описание компонента Tomcat
имя компонента | инструкция |
---|---|
Server | Представляет весь контейнер сервлета, поэтому в среде выполнения Tomcat имеется только один экземпляр сервера. |
Service | Служба представляет собой набор из одного или нескольких соединителей, которые совместно используют один и тот же контейнер для обработки своих запросов. В одном экземпляре Tomcat может содержаться любое количество экземпляров Service, они независимы друг от друга. |
Connector | Коннектор Tomcat используется для мониторинга и преобразования запросов Socket, а также для передачи запросов чтения Socket в контейнер для обработки.Он поддерживает разные протоколы и разные методы ввода-вывода. |
Container | Контейнер представляет собой класс объектов, которые могут выполнять клиентские запросы и возвращать ответы.В Tomcat существуют разные уровни контейнеров: Engine, Host, Context, Wrapper. |
Engine | Engine представляет весь механизм сервлета. В Tomcat Engine является объектом контейнера самого высокого уровня.Хотя Engine не является контейнером, который непосредственно обрабатывает запросы, он действительно является точкой входа для получения целевого контейнера. |
Host | Как тип контейнера Host представляет виртуальную машину в механизме сервлета (т.е. Engine) и связан с сетевым именем сервера, таким как доменное имя. Клиенты могут использовать это сетевое имя для подключения к серверу, это имя должно быть зарегистрировано на DNS-сервере. |
Context | Контекст — это тип контейнера, используемый для представления ServletContext.В спецификации сервлета ServletContext представляет собой независимое веб-приложение. |
Wrapper | Обертка — это контейнер для представления сервлетов, определенных в веб-приложениях. |
Executor | Представляет пул потоков, который может совместно использоваться компонентами Tomcat. |