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() 连接协议的默认实现:Http11NioProtocol
↓
org.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 "%r" %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 "%r" %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
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