前面讲到了servlet是一种规范,不能够独立提供服务,需要被部署到容器内部,由servlet容器来管理和运行servlet。servlet容器提供功能:
先明白几个基本概念:
用一张清晰的图片呈现:
图来自:各种容器与服务器的区别与联系:Servlet容器、WEB容器、Java EE容器、应用服务器、WEB服务器、Java EE服务器 - ppjj - 博客园
所以容器是位于应用程序/组件和服务器平台之间的接口集合,使得应用程序/组件可以方便部署到服务器上运行。
图来自:各种容器与服务器的区别与联系:Servlet容器、WEB容器、Java EE容器、应用服务器、WEB服务器、Java EE服务器 - ppjj - 博客园
web容器是管理servlet(通过servlet容器),以及监听器(Listener)和过滤器(Filter)的。没有servlet容器,也可以用web容器提供静态页面访问,比如安装一个apache等。web容器主要有:Apache、IIS、Tomcat、Jetty、JBoss、webLogic等,而Tomcat、Jetty、JBoss、webLogic同时也是servlet容器,或者说他们还包含了servlet容器。
几种常见web容器的比较:
Tomcat |
|
Jboss |
|
Weblogic |
|
WebSphere |
|
Resin |
|
Tomcat是一个JSP/Servlet容器,也是一个web容器。作为一个servlet容器,有三种工作模式:
根据进入tomcat容器的请求,工作模式分为如下两类:
解压tomcat,目录结构有:
tomcat:
|---bin:存放启动和关闭tomcat脚本
|---conf:存放不同的配置文件(server.xml和web.xml)
|---doc:存放Tomcat文档;
|---lib/japser/common:存放Tomcat运行需要的库文件(JARS);
|---logs:存放Tomcat执行时的LOG文件;
|---src:存放Tomcat的源代码;
|---webapps:Tomcat的主要Web发布目录(包括应用程序示例);
|---work:存放jsp编译后产生的class文件;
顶层结构:
Tomcat中最顶层的容器是Server,一个Server可以包含多个Service,一个Service只有一个Engine,但是可以有多个Connectors。Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;Engine用于封装和管理Servlet,以及具体处理Request请求;
这种架构设计的优点:
server.xml位于$TOMCAT_HOME/conf目录下;
Server表示正在运行的Tomcat实例,可包含一个或多个Service子容器;
图片来自:Tomcat整体架构分析 - 灌木大叔 - 博客园
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。JNDI已经成J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI服务。
Naming,所谓名称服务,简单来说就是通过名称查找实际对象的服务;
Directory,是一种特殊的名称服务,目录服务(Directory Service)提供了对目录中对象(directory objects)的属性进行增删改查的操作。比如命名服务中根据打印机名称去获取打印机对象(引用),然后进行打印操作;同时打印机拥有速率、分辨率、颜色等属性,作为目录服务,用户可以根据打印机的分辨率去搜索对应的打印机对象。
Connector的主要功能,是接收连接请求,创建Request和Response对象用于和请求端交换数据;然后分配线程让Engine来处理这个请求,并把产生的Request和Response对象传给Engine。 通过配置Connector,可以控制请求Service的协议及端口号. 如上配置:
# 默认BIO模式
#默认NIO模式
#默认APR模式
Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式。
模式 | 默认运行版本 | 处理方式 |
---|---|---|
BIO运行模式 | Tomcat7或以下版本 | 一个线程处理一个请求;缺点:并发量高是,线程数较多,浪费资源 |
NIO运行模式 | Tomcat8版本 | 利用Java的异步IO处理,可通过少量的线程处理大量请求; |
APR运行模式 | Tomcat7 或 8 在win7或以上系统中默认使用 | APR是使用原生C语言编写的非堵塞I/O,利用了操作系统的网络连接功能,速度很快; 以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作; 需先安装apr和native; |
Engine组件从一个或多个Connector中接收请求并处理,并将完成的响应返回给Connector,最终传递给客户端。
Host是Engine的子容器。Engine组件中可以内嵌1个或多个Host组件,每个Host组件代表Engine中的一个虚拟主机。Host组件至少有一个,且其中一个的name必须与Engine组件的defaultHost属性相匹配。
Context是Host的子容器,每个Host中可以定义任意多的Context元素,Context元素代表在特定虚拟主机上运行的一个Web应用。
Tomcat可以开启自动部署,当Web应用没有在server.xml中配置静态部署,可以由Tomcat通过特定的规则自动部署。要开启Web应用的自动部署,需要配置所在的虚拟主机deployOnStartup和autoDeploy属性。deployOnStartup为true时,Tomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用;autoDeploy为true时,Tomcat在运行时定期检查新的Web应用或Web应用的更新。
除了自动部署,也可以在server.xml中通过
server.xml中使用Context元素配置Web应用,Context元素应该位于Host元素中。举例如下:
docBase:静态部署时,docBase可以在appBase目录下,也可以不在;
path:静态部署时,可以显式指定path属性,但是仍然受到了严格的限制:只有当自动部署完全关闭(deployOnStartup和autoDeploy都为false)或docBase不在appBase中时,才可以设置path属性。
tomcat的三种部署模式:
自动部署 | 复制war包到tomcat的webapps目录中 |
增加web部署文件 | |
在server.xml中,在 节点中添加一个context |
|
valve是处理元素,它可以被包含在每个Tomcat容器的处理路径中--如engine、host、context以及servelt包装器。若要增加Valve到Tomcat容器则需要在server.xml中使用
如果使用Java自带的类加载器,就不能加载两个相同类库的不同版本。
图片来自:Tomcat怎么打破双亲委派机制的_Jackyyl729的博客-CSDN博客_tomcat打破
WebAppClassLoader中,查找class方法源码:
@Override
public Class> findClass(String name) throws ClassNotFoundException {
...
Class> clazz = null;
try {
try {
if (securityManager != null) {
PrivilegedAction> dp =
new PrivilegedFindClassByName(name);
clazz = AccessController.doPrivileged(dp);
} else {
// 先在应用内找
clazz = findClassInternal(name);
}
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
// 委托父类找
if ((clazz == null) && hasExternalRepositories) {
try {
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
...
return clazz;
}
加载类的实现:
@Override
public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.1) Check our previously loaded class cache
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes.
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
if (securityManager != null) {
PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
tryLoadingFromJavaseLoader = true;
}
// 核心类加载器加载
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested,委托给父类
if (delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
try {
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally,自己加载
if (!delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
Tomcat运行在JVM上,所以存在JVM调优。修改TOMCAT_HOME/bin/catalina.sh
JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m
Tomcat8以上版本,默认使用的就是NIO模式,如果是BIO更改为NIO。apr是Tomcat生产环境运行的首选方式.
将静态资源从Tomcat分离,交由Nginx处理。
修改server.xml文件中的enableLookups参数值改为false:enableLookups="false"。
参考文献:
几种常见web 容器比较 - kaleidoscopic - 博客园
Tomcat原理详解及请求过程 - 奋斗de程序猿 - 博客园
详解Tomcat 配置文件server.xml - 编程迷思 - 博客园
Tomcat的三种运行模式_Jack Tian的博客-CSDN博客