Web容器/Servlet容器 的原理、设计、配置与优化(Tomcat&Jetty)

导读:本篇文章讲解 Web容器/Servlet容器 的原理、设计、配置与优化(Tomcat&Jetty),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.常见应用服务器简介

Tomcat

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器

Jetty

Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境

2.web服务器/servlet容器

Tomcat、Jetty等服务器又叫web服务器/servlet容器,这也说明了他们的主要功能。

Web服务器

概念

Web服务器指的是,它能够对外提供Web服务,如支持HTTP访问8080端口

简单实现

我们写一段简单的代码实现一下,使用Java封装好的Socket作为监听

class OxTomcat{
    //使用socket监听端口
	ServerSocket server=new ServerSocket(8080);
	Socket socket=server.accept();
}

这个是一个BIO实现,实际上Tomcat支持更多种类的实现,我们看一下Tomcat的源码

源码验证

server.xml的连接器可以配置端口

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

源码中也有Connector这个类,我们追踪一下

org.apache.catalina.startup.Bootstrap main()Tomcat启动入口
	↓
org.apache.catalina.startup.Catalina load() 加载server实例
    ↓
org.apache.catalina.core.StandardServer initInternal() server
    ↓
org.apache.catalina.core.StandardService initInternal() service
    ↓
org.apache.catalina.connector.Connector initInternal() 连接器
    ↓
org.apache.coyote.http11.AbstractHttp11JsseProtocol init() 连接协议的默认实现:Http11NioProtocolorg.apache.coyote.AbstractProtocol init()org.apache.tomcat.util.net.AbstractEndpoint init()org.apache.tomcat.util.net.NioEndpoint bind() 默认为nio,可在配置文件中改变绑定连接的方式
    

绑定连接方式有nio、nio2、jio、apr
在这里插入图片描述

看一下nio的bind方法

public void bind() throws Exception {
    serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
    serverSock.socket().bind(addr,getBacklog());
    serverSock.configureBlocking(true); //mimic APR behavior
    serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    stopLatch = new CountDownLatch(pollerThreadCount);

    // Initialize SSL if needed
    if (isSSLEnabled()) {
        SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);

        sslContext = sslUtil.createSSLContext();
        sslContext.init(wrap(sslUtil.getKeyManagers()),
                sslUtil.getTrustManagers(), null);

        SSLSessionContext sessionContext =
            sslContext.getServerSessionContext();
        if (sessionContext != null) {
            sslUtil.configureSessionContext(sessionContext);
        }
        // Determine which cipher suites and protocols to enable
        enabledCiphers = sslUtil.getEnableableCiphers(sslContext);
        enabledProtocols = sslUtil.getEnableableProtocols(sslContext);
    }

    if (oomParachute>0) reclaimParachute(true);
    selectorPool.open();
}

Servlet容器

概念

Servlet容器指的是,它维护了支持对外提供服务的Servlet集合,对外提供服务

简单实现

我们写一段简单的代码实现一下servlet容器

class OxTomcat{
	ServerSocket server=new ServerSocket(8080);
	Socket socket=server.accept();
    //把请求和响应都封装在业务代码中的servlet添加到tomcat中即可
    List list=new ArrayList();
    list.add(servlets);
}

servlet规范

看一下javax.servlet.Servlet

/**
 * 定义所有 servlet 必须实现的方法。
 * servlet 是在 Web 服务器中运行的小型 Java 程序。 Servlet 接收和响应来自 Web 客户端的请求,通常是通过 HTTP(超文本传输​​协议)。
 * 为了实现这个接口,您可以编写延伸的通用servlet javax.servlet.GenericServlet或的HTTP Servlet扩展javax.servlet.http.HttpServlet 。
 * 该接口定义了初始化 servlet、服务请求和从服务器中删除 servlet 的方法。 这些被称为生命周期方法,按以下顺序调用:
 * 1.servlet 被构造,然后用init方法初始化。
 * 2.处理从客户端到service方法的任何调用。
 * 3.servlet 停止服务,然后使用destroy方法destroy ,然后垃圾收集并完成。
 * 除了生命周期方法之外,该接口还提供了getServletConfig方法,servlet 可以使用它来获取任何启动信息,以及getServletInfo方法,它允许 servlet 返回关于它自己的基本信息,例如作者、版本和版权。
 */
public interface Servlet {
    //初始化
	void init(ServletConfig config) throws ServletException;
	ServletConfig getServletConfig();
    //提供服务
	void service(ServletRequest req, ServletResponse res)throws ServletException,IOException;
	String getServletInfo();
    //销毁
	void destroy();
}

它的实现类有很多,如常见的 javax.servlet.http.HttpServlet

public abstract class HttpServlet extends GenericServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException;
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException;
   	protected void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException;
    protected void doDelete(HttpServletRequest req,HttpServletResponse resp)
        throws ServletException, IOException;
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException;
}

注入servlet容器

一个测试Servlet

class TestServlet extends HttpServlet{
	doGet(request,response){}
	doPost(request,response){}
}

将其配到web.xml中

	<servlet>
		<servlet-name>OxServlet</servlet-name>
		<servlet-class>com.oxye.OxServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>OxServlet</servlet-name>
		<url-pattern>/ox</url-pattern>
	</servlet-mapping>

自带servlet

web.xml中默认拥有的servlet,如default、jsp

    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

	<servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

servlet容器在哪里

我们看一下context.xml

<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
</Context>

以前项目中,我们把包放到Tomcat会读取项目中的web.xml,项目要做如下配置

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector port="7777" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"  useBodyEncodingForURI="true" URIEncoding="UTF-8"/>

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
		<context path=/OxApp docBase="/opt/application/OxApp"/>
      </Host>
    </Engine>
  </Service>
</Server>

Springboot为什么没有Web.xml?放到最后的问题中说明

源码验证

源码中也有context这个类,我们追踪一下

org.apache.catalina.startup.Bootstrap main()Tomcat启动入口
	↓
org.apache.catalina.startup.Catalina load() 加载server实例
    ↓
org.apache.catalina.core.StandardServer initInternal() server
    ↓
org.apache.catalina.core.StandardService initInternal() service
    ↓
org.apache.catalina.core.StandardEngine startInternal() engine
    ↓
org.apache.catalina.core.StandardHost startInternal() host
    ↓
org.apache.catalina.startup.HostConfig lifecycleEvent()  去应用程序根目录中找到的任何目录或 WAR 文件部署应用程序
    ↓
org.apache.catalina.core.StandardContext loadOnStartup() 将已配置的servlet加载到Tomcat的servlet容器中
    

将已配置的servlet加载到Tomcat的servlet容器中

在这里插入图片描述

3.Tomcat架构

server

server 表示整个servlet容器,Tomcat容器中只有一个Server,接收客户端发来的请求数据并进行解析,完成相关业务处理,然后把处理结果作为相应返回给客户端

server管理一到多个service组件,调用其init和start方法

service

service是对外提供服务的

service 表示一个或多个connector的集合。这些connector共享同一个container来处理其他请求。在一个server中可以包含多个service,这些service相互独立

对外connector

用于接收请求、并将请求封装成Request和Response来具体处理

connector

连接器,用于监听并转换为Socket请求,将该请求交由Container处理,支持不同的协议及不同IO方式

executor

表示Tomcat组件间可以共享的线程池

EndPoint

提供字节流给Processor

Processor

提供Tomcat Request对象给Adapter

在这里插入图片描述

Adapter

提供ServletRequest给容器

对内container

用于管理和封装Servlet,以及处理具体Request请求

在这里插入图片描述

engine

表示整个Servlet引擎,负责具体的请求处理

host

为了提供对多个域名的服务,我们可以将每个域名视为一个虚拟的主机。在每个Host下包含多个Context

context

表示ServletContext,在Servlet规范中,一个ServletContext表示一个Web应用

wrapper

在一个Web应用中,可以包含多个servlet实例来处理不同链接的请求。因此,需要一个组件概念来表示Servlet定义,在Tomcat中servlet定义被称为Wrapper

3.文件结构

Tomcat

在这里插入图片描述

bin目录主要是用来存放tomcat的命令,主要有两大类,一类是以.sh结尾的(linux命令),另一类是以.bat结尾的(windows命令)

conf目录主要是用来存放tomcat的一些配置文件

在这里插入图片描述

lib目录主要用来存放tomcat运行需要加载的jar包,如servlet-api包

logs目录用来存放tomcat在运行过程中产生的日志文件,非常重要的是在控制台输出的日志

temp目录用户存放tomcat在运行过程中产生的临时文件

webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用

work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件

Jetty

在这里插入图片描述

bin 存放Windows和linux等系统中使用的Jetty启动脚本和相关文件

etc 配置文件

在这里插入图片描述

modules 相关模块程序源代码

4.配置文件

Tomcat

conf/server.xml

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector port="7777" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"  useBodyEncodingForURI="true" URIEncoding="UTF-8"/>

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

conf/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>


    <mime-mapping>
        <extension>123</extension>
        <mime-type>application/vnd.lotus-1-2-3</mime-type>
    </mime-mapping>
    ----省略过多mime-mapping----
    <mime-mapping>
        <extension>zmm</extension>
        <mime-type>application/vnd.handheld-entertainment+xml</mime-type>
    </mime-mapping>
    
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

5.优化点

JVM层面

连接器

自动部署/热部署

其他多余的配置

如多余的连接器、监听器、非必要的默认servlet、内存监控、系统监控、自带控制台等

6.问题

1.SpringBoot如何选择容器(内置或外置、Tomcat或Jetty或Netty)?

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext

private void createWebServer()

多种可选,自动装配,引入那种的依赖就使用哪种

在这里插入图片描述

可以看到,内嵌容器和外置容器的结构别无二致

在这里插入图片描述

2.内嵌tomcat的配置在哪?

yml 中server:下的配置

见org.springframework.boot.autoconfigure.web.ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

   /**
    * Server HTTP port.
    */
   private Integer port;

   /**
    * Network address to which the server should bind.
    */
   private InetAddress address;

   @NestedConfigurationProperty
   private final ErrorProperties error = new ErrorProperties();

   /**
    * Whether X-Forwarded-* headers should be applied to the HttpRequest.
    */
   private Boolean useForwardHeaders;

   /**
    * Value to use for the Server response header (if empty, no header is sent).
    */
   private String serverHeader;

   /**
    * Maximum size of the HTTP message header.
    */
   private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);

   /**
    * Time that connectors wait for another HTTP request before closing the connection.
    * When not set, the connector's container-specific default is used. Use a value of -1
    * to indicate no (that is, an infinite) timeout.
    */
   private Duration connectionTimeout;

   @NestedConfigurationProperty
   private Ssl ssl;

   @NestedConfigurationProperty
   private final Compression compression = new Compression();

   @NestedConfigurationProperty
   private final Http2 http2 = new Http2();

   private final Servlet servlet = new Servlet();

   private final Tomcat tomcat = new Tomcat();

   private final Jetty jetty = new Jetty();

   private final Undertow undertow = new Undertow();

3.使用外置web容器后,应用yml的server配置还生效吗?

显而易见,以外部为准

4.SpringBoot项目如何提供servlet?

业务系统的servlet举例,org.springframework.web.servlet.DispatcherServlet

在这里插入图片描述

img

5.SpringBoot为什么没用到web.xml?

国产化的时候我们让主类继承SpringBootServletInitializer

public class WebApplication extends SpringBootServletInitializer

SpringBootServletInitializer 作用是,支持SpringApplication打成的WAR包运行在外置容器

/**
 * 一个的WebApplicationInitializer的实现类,用作将SpringApplication打成的WAR包运行在外置的容器,将Servlet 、 Filter和ServletContextInitializer bean 从应用程序上下文绑定到服务器。
 * 要配置应用程序,要么覆盖configure(SpringApplicationBuilder)方法(调用SpringApplicationBuilder.sources(Class...) )或使初始化程序本身成为@Configuration 。 如果您将SpringBootServletInitializer与其他WebApplicationInitializers结合使用,您可能还需要添加@Ordered注释来配置特定的启动顺序。
 * 请注意,只有在构建 war 文件并部署它时才需要 WebApplicationInitializer。 如果您更喜欢运行嵌入式 Web 服务器,那么您根本不需要它。
 */
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    /**
    * 使用初始化此 Web 应用程序所需的任何 servlet、过滤器、侦听器上下文参数和属性配置给定的ServletContext 
    */
    public void onStartup(ServletContext servletContext) throws ServletException
}

其接口 WebApplicationInitializer

/**
 * 例子
 * 传统的、基于 XML 的方法
 * 大多数构建 Web 应用程序的 Spring 用户需要注册 Spring 的DispatcherServlet 。 作为参考,在 WEB-INF/web.xml 中,这通常按如下方式完成:
 *    <servlet>
 *      <servlet-name>dispatcher</servlet-name>
 *      <servlet-class>
 *        org.springframework.web.servlet.DispatcherServlet
 *      </servlet-class>
 *      <init-param>
 *        <param-name>contextConfigLocation</param-name>
 *        <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
 *      </init-param>
 *      <load-on-startup>1</load-on-startup>
 *    </servlet>
 *
 *    <servlet-mapping>
 *      <servlet-name>dispatcher</servlet-name>
 *      <url-pattern>/</url-pattern>
 *    </servlet-mapping>
 * 使用WebApplicationInitializer的基于代码的方法
 * 这是等效的DispatcherServlet注册逻辑, WebApplicationInitializer样式:
 *    public class MyWebAppInitializer implements WebApplicationInitializer {
 *
 *       @Override
 *       public void onStartup(ServletContext container) {
 *         XmlWebApplicationContext appContext = new XmlWebApplicationContext();
 *         appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
 *
 *         ServletRegistration.Dynamic dispatcher =
 *           container.addServlet("dispatcher", new DispatcherServlet(appContext));
 *         dispatcher.setLoadOnStartup(1);
 *         dispatcher.addMapping("/");
 *       }
 *
 *    }
 * 作为上述的替代方案,您还可以从org.springframework.web.servlet.support.AbstractDispatcherServletInitializer扩展。
 * 由于 Servlet 3.0 的新ServletContext.addServlet方法,我们实际上注册了DispatcherServlet的实例,
 * 这意味着DispatcherServlet现在可以像任何其他对象一样对待——接收其应用程序上下文的构造函数注入在这种情况下。
 * 这种风格既简单又简洁。 无需处理 init-params 等,只需处理普通的 JavaBean 样式属性和构造函数参数。 
 * 在将 Spring 应用程序上下文注入DispatcherServlet之前,您可以根据需要自由创建和使用它们。
 * 大多数主要的 Spring Web 组件都已更新以支持这种注册方式。 如DispatcherServlet 、 FrameworkServlet 、 ContextLoaderListener
 * 和DelegatingFilterProxy现在都支持构造函数参数。 即使组件(例如非 Spring、其他第三方)尚未专门更新以在WebApplicationInitializers使用,
 * 它们仍然可以在任何情况下使用。 Servlet 3.0 ServletContext API 允许以编程方式设置 init-params、context-params 等。
 */
public interface WebApplicationInitializer {

	/**
    * 使用初始化此 Web 应用程序所需的任何 servlet、过滤器、侦听器上下文参数和属性配置给定的ServletContext 
    */
	void onStartup(ServletContext servletContext) throws ServletException;

}

查看其启动时的入参,与外置Tomcat容器中的别无二致

在这里插入图片描述

7.学习手册

Tomcat

https://tomcat.apache.org/tomcat-10.0-doc/architecture/overview.html

Jetty

https://www.eclipse.org/jetty/documentation/jetty-9/index.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/93696.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!