title: How Tomcat Works(基于Tomcat4)
date: 2019/08/12 15:51
一、一个简单的web服务器
基于Java的Http服务器需要两个重要的类java.net.ServerSocket
和java.net.Socket
。
1.1 HTTP
Http使用可靠的TCP连接,默认使用TCP/80端口
1.2 Socket类
套接字是网络连接的端点。套接字使程序可以从网络中读取数据,可以向网络中写入数据。不同计算机上的两个应用程序可以通过连接发送或接受字节流。
Socket类表示客户端套接字(想要连接到远程服务器应用程序时需要创建的套接字)、ServerSocket类表示服务器套接字(等待客户端的连接请求的套接字),当服务器套接字收到请求后,他会创建一个Socket实例来处理与客户端的通信。
二、一个简单的Servlet容器
2.1 Servlet接口
2.2 应用程序1
一个功能健全的servlet容器对Http请求需要做以下几件事:
- 第一次调用某个servlet时,要载入该servlet类,之后调用它的init()方法。
// 连接器调用pipline,pipline调用基础阀,基础阀执行wrapper.allocate();
Servlet servlet = this.loadServlet();
servlet.init();
- 针对每个request请求,创建一个
javax.servlet.ServletRequest
和javax.servlet.ServletResponse
对象。(连接器干的) - 调用servlet的service方法(基础阀干的,通过过滤器链传递到servlet)
- 调用destory方法并卸载此类
连接器最终生成的是HttpRequestImpl的对象,由于HttpRequestImpl类中有部分公有方法不想让servlet程序员使用,有两种办法能够解决:
- 使用访问修饰符
- 使用Faced类
三、连接器
连接器主要负责创建HttpServletRequest和HttpServletResponse对象,并将他们作为参数传给servlet的service方法。
3.1 StringManager类
该类用来处理Catalina中错误消息的国际化操作。
Tomcat将错误消息存储到properties文件中,如果所有错误信息储存再一个文件中,会很难维护,所以Tomcat将properties文件划分到不同的包中,每个properties文件使用一个StringManager对象来处理。
private static Hashtable managers = new Hashtable();
public synchronized static StringManager getManager(String packageName) {
StringManager mgr = (StringManager)managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);
managers.put(packageName, mgr);
}
return mgr;
}
StringManager会根据该应用程序的服务器的语言环境选择使用哪个文件。
3.2 应用程序
连接器需要在请求来的时候从socket中一些值设置给Request对象,这些值包括:URI、查询字符串、参数(body中)、cookie、请求头等信息;由于查询字符串和参数可能巨大,所以一般再连接器中不解析它们,而是在servlet真正用到的时候进行解析的。
连接器解析Http请求主要分为以下5块:
3.2.1 启动程序
public final class Bootstrap {
public static void main(String[] args) {
// HttpConnector负责接受连接,并调用HttpProcessor.process(Socket socket)方法
HttpConnector connector = new HttpConnector();
connector.start();
}
}
3.2.2 HttpConnector类
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public String getScheme() {
return scheme;
}
public void run() {
ServerSocket serverSocket = null;
int port = 8080;
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
while (!stopped) {
// 等待http请求
Socket socket = serverSocket.accept();
// 调用连接器的process方法,对请求进行解析
HttpProcessor processor = new HttpProcessor(this);
processor.process(socket);
}
}
public void start() {
Thread thread = new Thread(this);
thread.start();
}
}
3.2.3 HttpProcessor类
public void process(Socket socket) {
SocketInputStream input = null;
OutputStream output = null;
try {
// 获取socket中的输入流、输出流
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
// 创建Request和Response对象
request = new HttpRequest(input);
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "Pyrmont Servlet Container");
// 解析请求
parseRequest(input, output);
parseHeaders(input);
// 检查这是对servlet还是静态资源的请求,调用不同的处理程序
// 对servlet的请求以/servlet/开头
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
} else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
// no shutdown for this application
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.3.1 创建HttpRequest对象
主要包含五部分:
- 读取套接字的输入流
-
解析请求行
- 解析请求头
- 解析cookie
- 读取参数
前面说过读取参数会延迟到servlet第一次获取参数时进行解析,所以在request对象中有一个parseParameters()
方法,会在第一次获取参数时进行解析。
3.2.3.2 创建HttpResponse对象
获取输出流,转换成PrintWirter对象
四、Tomcat的默认连接器
功能:负责创建Request、Response对象,然后调用Container接口的invoke方法。
由于HttpProcessor将Request对象作为全局变量,所以多线程访问的时候有线程安全问题,所以HttpConnector内部维护一个对象池。
4.2 Connector接口
Tomcat的连接器必须实现该接口。
public interface Connector {
// 返回与之关联的容器
public Container getContainer();
// 将连接器与某个servlet相关联;当request、response对象创建好了之后调用它的invoke方法
public void setContainer(Container container);
// 为引入的Http请求创建request对象
public Request createRequest();
// 创建response对象
public Response createResponse();
// 初始化连接器
public void initialize() throws LifecycleException;
...
}
注:Tomcat8中没有Connector接口,改为
org.apache.catalina.connector.Connector
类,使用的是Coyote连接器。
4.3 HttpConnector类(连接器)
public final class HttpConnector
implements Connector, Lifecycle, Runnable {
4.3.1 创建服务器套接字
HttpConnector的initialize()方法会调用open()
方法(私有方法)从服务器套接字工厂得到一个服务器套接字实例。
4.3.2 维护HttpProcessor实例(具体干活的人)
上面说了,由于一个HttpProcessor实例只能串行的干活,才能保证线程安全,所以默认连接器维护了一个处理器栈:
private Stack<HttpProcessor> processors = new Stack<>();
内部的数量由两个变量决定:
private int maxProcessors = 20;
protected int minProcessors = 5;
在HttpConnector启动(Lifecycle的start方法)的时候会对内部对象进行填充:
public void start() throws LifecycleException {
// Validate and update our current state
if (started)
throw new LifecycleException
(sm.getString("httpConnector.alreadyStarted"));
threadName = "HttpConnector[" + port + "]";
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Start our background thread
threadStart();
// 创建指定的**最小**处理器数
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
// 创建对象
HttpProcessor processor = newProcessor();
// 压入栈
recycle(processor);
}
}
4.3.3 提供Http请求服务
HttpConnector实现了Runable接口,上面的threadStart()方法将其启动:
public void run() {
// Loop until we receive a shutdown command
while (!stopped) {
Socket socket = null;
try {
// 等待http请求
socket = serverSocket.accept();
if (connectionTimeout > 0)
socket.setSoTimeout(connectionTimeout);
socket.setTcpNoDelay(tcpNoDelay);
} catch (IOException e) {
try {
// If reopening fails, exit
synchronized (threadSync) {
if (started && !stopped)
log("accept error: ", e);
if (!stopped) {
serverSocket.close();
serverSocket = open();
}
} ...
continue;
}
// 获取一个HttpProcessor对象(可能是从栈中取出,可能是创建的,如果max <= current返回null)
HttpProcessor processor = createProcessor();
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
} catch (IOException e) {
;
}
continue;
}
// 传入socket对象,唤醒processor的run方法
processor.assign(socket);
}
synchronized (threadSync) {
threadSync.notifyAll();
}
}
4.3.4 HttpProcessor类
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// 获取套接字对象,processor.assign(socket)传入的
Socket socket = await();
if (socket == null)
continue;
// 处理
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// 将当前实例压会栈中
connector.recycle(this);
}
// 告诉 threadstop()我们已经成功关闭了自己
synchronized (threadSync) {
threadSync.notifyAll();
}
}
private void process(Socket socket) {
// 表示处理过程是否出错
boolean ok = true;
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputStream(),
connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
keepAlive = true;
// 不断的读取输入流,知道HttpProcessor实例终止
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
// request、response对象的初始化工作
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
ok = false;
}
// Parse the incoming request
try {
if (ok) {
// 解析连接,取出internet地址赋值给Request对象
parseConnection(socket);
// 解析各种东西,参见第三章
parseRequest(input, output);
if (!request.getRequest().getProtocol()
.startsWith("HTTP/0"))
// 解析请求头
parseHeaders(input);
if (http11) {
// Sending a request acknowledge back to the client if
// requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
}
} catch (EOFException e) {
// It's very likely to be a socket disconnect on either the
// client or the server
ok = false;
finishResponse = false;
} catch (ServletException e) {
ok = false;
try {
((HttpServletResponse) response.getResponse())
.sendError(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
} catch (InterruptedIOException e) {
if (debug > 1) {
try {
log("process.parse", e);
((HttpServletResponse) response.getResponse())
.sendError(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
}
ok = false;
} catch (Exception e) {
try {
log("process.parse", e);
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
ok = false;
}
// Ask our Container to process this request
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
// *** 获取容器执行invoke方法,最终到达servlet.service() ***
connector.getContainer().invoke(request, response);
}
} catch (ServletException e) {
log("process.invoke", e);
try {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception f) {
;
}
ok = false;
} catch (InterruptedIOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
try {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception f) {
;
}
ok = false;
}
// 直到finishResponse为true表示处理结束(包括循环读取请求头,调用service方法等),将结果发送给客户端。
if (finishResponse) {
try {
response.finishResponse();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
request.finishRequest();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
if (output != null)
output.flush();
} catch (IOException e) {
ok = false;
}
}
// We have to check if the connection closure has been requested
// by the application or the response stream (in case of HTTP/1.0
// and keep-alive).
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
try {
shutdownInput(input);
socket.close();
} catch (IOException e) {
;
} catch (Throwable e) {
log("process.invoke", e);
}
socket = null;
}
五、Servlet容器
servlet容器是用来处理请求servlet资源,并为客户端填充response对象的模块。
Tomcat中有下面4中容器:
- Engine:解析请求,分配到适当的虚拟主机
- Host:虚拟主机;运行多个Web应用(一个Context代表一个Web应用),并负责安装、展开、启动和结束每个Web应用。
- Context:代表在特定虚拟主机上运行的一个Web应用
- Wrapper:表示一个独立的servlet
它们所有的实现类都继承ContainerBase抽象类。
https://www.cnblogs.com/kismetv/p/7228274.html#title3-1
5.1 Container接口
public interface Container {
// 添加子容器;Host容器下只能添加Context容器。。。
void addChild(Container child);
// 移除子容器
void removeChild(Container child);
// 根据名称查找子容器
public Container findChild(String name);
// 查找子容器集合
public Container[] findChildren();
// 其它组件的get/set方法,包括:载入器(Loader)、记录器(Logger)、Session管理器(Manager)、领域(Realm)、资源(Resource)
容器是Tomcat的核心,所以才将所有组件都与容器连接起来,而且通过Lifecyle接口,使我们可以只启动容器组件就可以了(他帮我们启动其它组件)
}
注:这是组合模式的一种使用
5.2 管道任务
当连接器调用容器的invoke()方法后,容器会调用pipline的invoke()方法(pipline是管理阀的容器,类似FilterChain;阀表示具体的执行任务),pipline会调用ValveContext的invokeNext方法,当所有阀全都调用完成之后就会调用基础阀的invoke方法。
看下StandardWrapperValve的invoke方法的部分代码:
StandardWrapper wrapper = (StandardWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
// 如果是第一次的话,这个方法里面调用了servlet的init方法
Servlet servlet = wrapper.allocate();;
// 构造Filter责任链
ApplicationFilterChain filterChain = createFilterChain(request, servlet);
// 执行责任链和servlet的service方法
filterChain.doFilter(sreq, sres);
5.3 Wrapper接口
Wrapper的实现类主要负责管理其基础的servlet类的生命周期
// 载入并加载servlet,在它的子类StandardWrapper中直接调用的loadServlet方法
void load() throws ServletException;
// 该方法会返回已加载的servlet类,还要考虑它是否实现了SingleThreadModel
Servlet allocate() throws ServletException;
六、生命周期
Catalina包含很多组件,当Catalina启动时,这些组件也会一起启动,关闭时也会一起关闭。
通过实现Lifecyle接口就可以达到统一启动/关闭这些组件的效果。
Lifecycle可以触发下面6个事件,实现LifecycleListener的类可以通过addLifecycleListener()
注册到相应的生命周期对象上。
public interface Lifecycle {
// 添加事件监听器
void addLifecycleListener(LifecycleListener listener);
/**
* The LifecycleEvent type for the "component start" event.
*/
public static final String START_EVENT = "start";
/**
* The LifecycleEvent type for the "component before start" event.
*/
public static final String BEFORE_START_EVENT = "before_start";
/**
* The LifecycleEvent type for the "component after start" event.
*/
public static final String AFTER_START_EVENT = "after_start";
/**
* The LifecycleEvent type for the "component stop" event.
*/
public static final String STOP_EVENT = "stop";
/**
* The LifecycleEvent type for the "component before stop" event.
*/
public static final String BEFORE_STOP_EVENT = "before_stop";
/**
* The LifecycleEvent type for the "component after stop" event.
*/
public static final String AFTER_STOP_EVENT = "after_stop";
6.1 Lifecycle接口
void addLifecycleListener(LifecycleListener listener);
public LifecycleListener[] findLifecycleListeners();
public void removeLifecycleListener(LifecycleListener listener);
// 启动操作
public void start() throws LifecycleException;
// 关闭操作
public void stop() throws LifecycleException;
其实也是组合模式的使用
6.2 LifecycleEvent
当事件触发的时候传给listener的对象
6.3 LifecycleListener
事件触发是的处理程序要放在lifecycleEvent方法中。
public interface LifecycleListener {
public void lifecycleEvent(LifecycleEvent event);
}
6.4 LifecycleSupport(工具类)
public final class LifecycleSupport {
// 构造需要一个生命周期对象
public LifecycleSupport(Lifecycle lifecycle) {
super();
this.lifecycle = lifecycle;
}
private Lifecycle lifecycle = null;
// 监听器集合
private LifecycleListener listeners[] = new LifecycleListener[0];
// 当调用生命周期的addLifecycleListener方法时,大多数代码的实现直接调用的LifecycleSupport的这个方法
public void addLifecycleListener(LifecycleListener listener) {
synchronized (listeners) {
LifecycleListener results[] =
new LifecycleListener[listeners.length + 1];
for (int i = 0; i < listeners.length; i++)
results[i] = listeners[i];
results[listeners.length] = listener;
listeners = results;
}
}
public LifecycleListener[] findLifecycleListeners() {
return listeners;
}
// 触发事件,type是上面Lifecyle中定义的那6个常量,data是要传给listener的数据
public void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = null;
synchronized (listeners) {
interested = (LifecycleListener[]) listeners.clone();
}
for (int i = 0; i < interested.length; i++)
interested[i].lifecycleEvent(event);
}
public void removeLifecycleListener(LifecycleListener listener) {
synchronized (listeners) {
int n = -1;
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == listener) {
n = i;
break;
}
}
if (n < 0)
return;
LifecycleListener results[] =
new LifecycleListener[listeners.length - 1];
int j = 0;
for (int i = 0; i < listeners.length; i++) {
if (i != n)
results[j++] = listeners[i];
}
listeners = results;
}
}
}
6.5 应用程序
能体现Lifecyle的就是启动类中的代码了:
public final class Bootstrap {
public static void main(String[] args) {
Connector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2);
Mapper mapper = new SimpleContextMapper();
mapper.setProtocol("http");
// 将监听器绑定到context容器中
LifecycleListener listener = new SimpleContextLifecycleListener();
((Lifecycle) context).addLifecycleListener(listener);
context.addMapper(mapper);
Loader loader = new SimpleLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
connector.setContainer(context);
try {
// 初始化连接器(创建ServerSocket)
connector.initialize();
// 启动连接器(创建HttpProcesser集合)
((Lifecycle) connector).start();
// 启动容器(它又会调用子容器的start()方法)
((Lifecycle) context).start();
// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.6 总结
通过生命周期就可以采用伪递归的方式让组件做一些开始、收尾工作。
LifecyleListener就是在启动/关闭过程中埋下的钩子。
七、日志记录器
日志记录器是用来记录消息的组件。
https://wiki.jikexueyuan.com/project/tomcat/logging.html
7.1 Logger接口
日志记录器都必须实现这个接口。
public interface Logger {
// 定义的日志的等级
public static final int FATAL = Integer.MIN_VALUE;
public static final int ERROR = 1;
public static final int WARNING = 2;
public static final int INFORMATION = 3;
public static final int DEBUG = 4;
7.2 Tomcat的日志记录器
Tomcat提供了3种日志记录器:FileLogger、SystemOutLogger、SystemErrLogger,它们3个都继承了LoggerBase抽象类。
7.2.1 LoggerBase类
实现了所有Logger接口的方法,除了下面这个:
public abstract void log(String msg);
LoggerBase中有几个重载的log方法,最终都会调用这个方法。
日志等级是由protected int verbosity = ERROR;
变量定义的,默认为Error。
7.2.2 SystemOutLogger类
public class SystemOutLogger extends LoggerBase {
public void log(String msg) {
System.out.println(msg);
}
}
7.2.3 SystemErrLogger类
public class SystemErrLogger extends LoggerBase {
public void log(String msg) {
System.err.println(msg);
}
}
7.2.4 FileLogger
当我知道Tomcat8中没有Logger接口,就不想写了
八、载入器
由于整个Tomcat共用一个JVM,如果采用系统类加载器的话所有web应用程序(Context)之间可以互相使用对方的类,违背了下面的规范:
为部署在单个Tomcat实例中的每个Web应用程序创建一个类加载器。/WEB-INF/classesWeb应用程序目录中的所有解压缩的类和资源,以及Web应用程序/WEB-INF/lib目录下的JAR文件中的类和资源,都对此Web应用程序可见,但对其他应用程序不可见。 —— Tomcat7官方文档
还有另外一个原因是为了提供自动重载功能,当WEB-INF/classes和WEB-INF/lib目录发生变化时,Web应用要重新载入这些类。
Tomcat要自定义类加载器原因主要是以下3条:
- 在载入类中指定某些规则
- 缓存已经载入的类
- 实现类的预载入
8.1 类加载器
JVM使用了3种类加载器来载入需要的类,分别是引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、系统类加载器(System ClassLoader)。
3种类加载器是父子继承关系,引导类加载器在最上层,系统类加载器在最下层。
引导类加载器(本地代码编写):引导启动Java虚拟机。用于加载JVM需要的类,以及所有Java核心类(例如java.lang,java.io包下的类);它会在rt.jar、i18n.jar中搜索要载入的类。
扩展类加载器:负责载入标准扩展目录中的类。标准扩展目录是/jdk/jre/lib/ext
系统类加载器:它会搜索在环境变量CLASSPATH中指明的路径和文件。
类载入过程(双亲委托机制):要载入一个类的时候,会先交给系统类加载器,系统类加载器交给扩展类加载器,扩展类加载器又会交给引导类加载器,如果引导类加载器无法加载就交给扩展类加载器,扩展类加载器无法加载就交给系统类加载器,系统类加载器无法加载就抛出ClassNotFoundException
异常。
为啥要执行这样的循环过程呢?
如果我们自定义一个java.lang.Object
类,假设他能被载入,由于JVM是信任java.lang.Object
类的,安全管理器就不会监视这个类的活动,这是非常危险的。
采用双亲委托机制,就会将java.lang.Object
类的加载交给引导类加载器,而引导类加载器会在rt.jar、i18n.jar
中去寻找这个类进行加载,我们自定义的类并不会被加载。
package java.lang;
public class Object {
public static void main(String[] args) {
System.out.println(123);
}
}
// 证明自定义Object类并没有加载进去
错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
8.2 Loader接口
仓库:表示类载入器会在哪里搜索要载入的类
资源:一个类载入器中的DirContext对象(代表着一个web应用的目录吗??走流程的时候看一下)
public interface Loader {
// 返回类加载器
ClassLoader getClassLoader();
Context getContext();
// 这个方法会在容器启动(start方法)的时候调用,setContext(this)
void setContext(Context var1);
boolean getDelegate();
// 是否委托给父类载入器
void setDelegate(boolean var1);
boolean getReloadable();
void setReloadable(boolean var1);
void addPropertyChangeListener(PropertyChangeListener var1);
boolean modified();
void removePropertyChangeListener(PropertyChangeListener var1);
}
Loader的实现类
public class WebappLoader
implements Lifecycle, Loader, PropertyChangeListener, Runnable {
// ClassLoader的子类
private WebappClassLoader classLoader = null;
8.3 Reloader接口
public interface Reloader {
/**
* 设置仓库路径(在哪里搜索要载入的类)
*/
public void addRepository(String repository);
/**
* Return a String array of the current repositories for this class
* loader. If there are no repositories, a zero-length array is
* returned.
*/
public String[] findRepositories();
/**
* 是否修改了一个或多个类或资源
*/
public boolean modified();
}
8.4 WebappLoader
public class WebappLoader
implements Lifecycle, Loader, PropertyChangeListener, Runnable {
// ClassLoader的子类
private WebappClassLoader classLoader = null;
它负责载入Web应用程序中所使用到的类,支持自动重载功能(通过Runable的run方法不断轮询其类加载器的modified()实现)
当调用该类的start()方法时会做以下几项工作:
8.4.1 创建类载入器
private WebappClassLoader createClassLoader()
throws Exception {
// loaderClass可以通过setLoaderClass方法改变
Class clazz = Class.forName(loaderClass);
8.4.2 设置仓库
// 伪代码
private void setRepositories() {
String classesPath = "/WEB-INF/classes";
// addRepository的重载方法,第二个参数传入的是 new File(应用/WEB-INF/classes)对象
classLoader.addRepository(classesPath + "/", classRepository);
String libPath = "/WEB-INF/lib";
classLoader.setJarPath(libPath);
8.4.3 设置类路径
为jasper JSP编译器设置一个字符串形式的属性来表明类路径信息。
8.4.4 设置访问权限
如果运行时使用了安全管理器,setPermissions()方法会为泪在如期设置访问目录的权限,例如:设置只能访问WEB-INF/classes和WEB-INF/lib目录。
8.4.5 开启新线程执行类的重新载入
public void run() {
// Loop until the termination semaphore is set
while (!threadDone) {
// Wait for our check interval
threadSleep();
if (!started)
break;
try {
// Perform our modification check
if (!classLoader.modified())
continue;
} catch (Exception e) {
log(sm.getString("webappLoader.failModifiedCheck"), e);
continue;
}
// 重新加载;最终调用的是context.reload()方法
notifyContext();
// 为啥这里要退出呢??会不会在上面reload的过程中重新启动了一个WebappLoader呢?
break;
}
}
Tomcat5看书 P119
8.5 WebappClassLoader类
public class WebappClassLoader
extends URLClassLoader
implements Reloader, Lifecycle {
继承自URLClassLoader,实现了Reloader接口,支持重载。
它会缓存之前已经加载过的类来提高性能,它还会缓存加载失败的类的名字,当再次请求的时候就会直接抛出ClassNotFoundException
异常,而不会在指定的仓库或jar包中搜索需要载入的类。
为了安全性,不让这个加载器加载某些类,类的名称存储在一个字符串数组变量triggerd中:
private static final String[] triggers = {
"javax.servlet.Servlet" // Servlet API
};
此外,某些特殊包下的类也是不允许载入的:
private static final String[] packageTriggers = {
"javax", // Java extensions
"org.xml.sax", // SAX 1 & 2
"org.w3c.dom", // DOM 1 & 2
"org.apache.xerces", // Xerces 1 & 2
"org.apache.xalan" // Xalan
};
8.5.1 类缓存
java.lang.ClassLoader
类会维护一个Vector<Class<?>>
对象,保存已经载入的类,防止这些类在不使用的时候被垃圾回收了。
每个由WebappClassLoader载入的类(从/WEB-INF/classes,/WEB-INF/lib等目录下载入的类),都视为“资源”,“资源”使用ResourceEntry
进行表示:
public class ResourceEntry {
/**
* The "last modified" time of the origin file at the time this class
* was loaded, in milliseconds since the epoch.
*/
public long lastModified = -1;
/**
* Binary content of the resource.
*/
public byte[] binaryContent = null;
/**
* Loaded class.
*/
public Class loadedClass = null;
/**
* URL source from where the object was loaded.
*/
public URL source = null;
/**
* URL of the codebase from where the object was loaded.
*/
public URL codeBase = null;
/**
* Manifest (if the resource was loaded from a JAR).
*/
public Manifest manifest = null;
/**
* Certificates (if the resource was loaded from a JAR).
*/
public Certificate[] certificates = null;
}
所有已经缓存的类会存储在一个HashMap中:
protected HashMap<String, ResourceEntry> resourceEntries = new HashMap<>();
8.5.2 载入类
- 先检查本地缓存
- 本地缓存没有,则检查上一层缓存(调用ClassLoader.findLoadedClass())
- 如果两个缓存中没有,则使用系统类加载器加载,防止Web应用程序的类覆盖J2EE的类(原因前面讲了)
- 如果使用了安全管理器(与8.4.4对应),则检查是否允许载入该类,如果不允许则抛出
ClassNotFoundException
- 如果设置了
Delegate
属性或待载入的类属于包触发器(packageTriggers)的包名,则调用父类加载器来载入相关类,如果父类加载器为null,则使用系统类加载器。 - 从当前仓库载入相关类
- 如果当前仓库没有需要的类,且标志位delegate关闭,则使用父类加载器;如果父类加载器为null,则使用系统类加载器
- 如果还是没找到需要的类,则抛出
ClassNotFoundException
异常。
九、Session管理
Catalina通过一个称为Session管理器的组件来管理建立的Session对象,该组件必须实现Manager接口。Session管理器必须与一个Context容器相关联。
我们可以通过request.getSession()
来获取Session:
// HttpRequestBase.java
public HttpSession getSession() {
return (getSession(true));
}
public HttpSession getSession(boolean create) {
if( System.getSecurityManager() != null ) {
PrivilegedGetSession dp = new PrivilegedGetSession(create);
return (HttpSession)AccessController.doPrivileged(dp);
}
return doGetSession(create);
}
private HttpSession doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session.getSession());
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
// 从context中获取session管理器
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
// 如果sessionId不为空,那么从管理器中寻找他
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
return (session.getSession());
}
}
// Create a new session if requested and the response is not committed
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("httpRequestBase.createCommitted"));
}
// 通过管理器创建一个session
session = manager.createSession();
if (session != null)
return (session.getSession());
else
return (null);
}
9.1 Session对象
9.1.1 Session接口
public interface Session {
// 获取该session的标识符
public String getId();
public void setId(String id);
// 获取session管理器
public Manager getManager();
// 设置session管理器
public void setManager(Manager manager);
// 返回客户端上次发送与此会话关联的请求的时间(可以通过该返回值判断session对象的有效性)
public long getLastAccessedTime();
// 设置session的有效性
public void setValid(boolean isValid);
// 当访问一个session实例的时候,session管理器会调用该方法修改session对象的最终访问时间
public void access();
// 将session设置为过期
public void expire();
// 获取一个被外观类包装的HttpSession类
public HttpSession getSession();
}
9.1.2 StandardSession类
class StandardSession
implements HttpSession, Session, Serializable {
// 构造函数接收的是session管理器对象
public StandardSession(Manager manager) {
super();
this.manager = manager;
if (manager instanceof ManagerBase)
this.debug = ((ManagerBase) manager).getDebug();
}
public HttpSession getSession() {
if (facade == null)
facade = new StandardSessionFacade(this);
return facade;
}
// 设置session过期
public void expire() {
expire(true);
}
public void expire(boolean notify) {
// Mark this session as "being expired" if needed
if (expiring)
return;
// 将过期标识设置为true,有效标识设置为false
expiring = true;
setValid(false);
// 从session管理器移除当前session对象
if (manager != null)
manager.remove(this);
// 解绑与当前session相关联的任何对象
String keys[] = keys(); // 获取session域的所有key值
for (int i = 0; i < keys.length; i++)
removeAttribute(keys[i], notify);
// 触发session过期事件
if (notify) {
fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
}
// Notify interested application event listeners
// FIXME - Assumes we call listeners in reverse order
Context context = (Context) manager.getContainer();
Object listeners[] = context.getApplicationListeners();
if (notify && (listeners != null)) {
HttpSessionEvent event =
new HttpSessionEvent(getSession());
for (int i = 0; i < listeners.length; i++) {
int j = (listeners.length - 1) - i;
if (!(listeners[j] instanceof HttpSessionListener))
continue;
HttpSessionListener listener =
(HttpSessionListener) listeners[j];
try {
// 触发容器事件
fireContainerEvent(context,
"beforeSessionDestroyed",
listener);
listener.sessionDestroyed(event);
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Throwable t) {
try {
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Exception e) {
;
}
// FIXME - should we do anything besides log these?
log(sm.getString("standardSession.sessionEvent"), t);
}
}
}
// We have completed expire of this session
expiring = false;
if ((manager != null) && (manager instanceof ManagerBase)) {
recycle();
}
}
9.2 Manager
Session管理器负责管理Session对象(Session对象的创建、摧毁)。
ManagerBase抽象类对Manager接口进行了部分实现,它有2个直接子类:
-
StandardManager:将session对象储存在内存中,当Catalina关闭时,他会将Session对象存储到一个文件中(Lifecycle的stop方法中),重启后,又会将这些Session对象重新载入内存中。
-
PersistentManagerBase:它也是一个抽象类,用于将session对象存储到辅助存储器中。
public interface Manager {
// 将Manager与容器相关联
public void setContainer(Container container);
// 创建一个session实例
public Session createSession();
// 将session实例添加到Session池中
public void add(Session session);
// 将session实例从Session池中移除
public void remove(Session session);
// 设置此管理器创建的session对象的默认最长存活时间
public void setMaxInactiveInterval(int interval);
// 持久化到储存器中
public void unload() throws IOException;
// 从储存器中拿出来
public void load() throws ClassNotFoundException, IOException;
9.2.2 ManagerBase类
Context容器中所有活动的Session对象都存储在一个名为sessions的HashMap变量中:
protected HashMap sessions = new HashMap();
public void add(Session session) {
synchronized (sessions) {
sessions.put(session.getId(), session);
}
}
9.2.3 StandardManager类
实现了Lifecyle接口,通过它对Session对象进行load和unload;会生成一个SESSION.ser的文件中。
session管理器还要负责销毁那些失效的Session对象:
public void run() {
while (!threadDone) {
threadSleep();
processExpires();
}
}
9.2.4 PersistentManagerBase类
// 使用stroe代表辅助储存器
private Store store = null;
本章剩下的看书把。
十一、StandardWrapper
11.1 方法调用序列
1、连接器创建request和response对象
2、连接器调用StandardContext实例的invoke()方法
3、StandardContext实例的invoke()方法会调用其管道对象(Pipline)的invoke()方法。最终会调用管道对象中的基础阀StandardContextValve实例的invoke()方法。
4、StandardContextValve实例的invoke()方法会获取相应的Wrapper实例处理Http请求,调用Wrapper实例的invoke方法。
5、同上,StandardWrapper实例的invoke()方法调用其管道对象的invoke()方法。
6、同上,调用基础阀StandardWrapperValve实例的invoke()方法,invoke方法会调用Wrapper实例的allocate()方法获取servlet实例。
7、allocate()调用load()方法载入相应的servlet类,如果已经载入,无需重新载入。
8、load()方法会调用servlet实例的init()方法
9、StandardWrapper实例调用servlet实例的service()方法。
11.3 StandardWrapper
StandardWrapper对象的主要任务是载入(loader.load())它所代表的servlet类,并进行实例化(clazz.newInstance())。
11.3.1 分配servlet实例(Wrapper的allocate()方法)
public Servlet allocate() throws ServletException {
// SingleThreadedModel现在已经废弃,就不介绍了
if (!singleThreadModel) {
// 双重检查锁,检查servlet之前是否加载过
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
}
}
}
if (!singleThreadModel) {
if (debug >= 2)
log(" Returning non-STM instance");
// 将已分配的计数+1
countAllocated++;
return (instance);
}
}
}
11.3.2 载入Servlet类(Wrapper的load()方法)
public synchronized void load() throws ServletException {
instance = loadServlet();
}
// allocate()和load()方法都调用了该方法
public synchronized Servlet loadServlet() throws ServletException {
// 如果之前已经加载过了,直接返回
if (instance != null)
return instance;
PrintStream out = System.out;
SystemLogHandler.startCapture();
Servlet servlet = null;
try {
// 将servlet类的类名赋值给actualClass
String actualClass = servletClass;
// 检查请求的servlet是不是一个jsp页面,如果是的话重新赋值actualClass
if ((actualClass == null) && (jspFile != null)) {
Wrapper jspWrapper = (Wrapper)
((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
if (jspWrapper != null)
actualClass = jspWrapper.getServletClass();
}
// 如果它是servlet,但是没有setServletClass()就会报错
if (actualClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
// 获取载入器(如果当前容器没有载入器,就获取它父容器的载入器)
Loader loader = getLoader();
if (loader == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.missingLoader", getName()));
}
// 获取载入器中的类加载器
ClassLoader classLoader = loader.getClassLoader();
// Catalina提供了一些用于访问servlet容器内部数据专用的servlet类,如果要加载的类是这种专用的servlet(参见19章),则将ClassLoader辅值为当前类的加载器
if (isContainerProvidedServlet(actualClass)) {
classLoader = this.getClass().getClassLoader();
log(sm.getString
("standardWrapper.containerServlet", getName()));
}
// Load the specified servlet class from the appropriate class loader
Class classClass = null;
try {
if (classLoader != null) {
System.out.println("Using classLoader.loadClass");
// 使用类加载器加载该类
classClass = classLoader.loadClass(actualClass);
} else {
System.out.println("Using forName");
classClass = Class.forName(actualClass);
}
} catch (ClassNotFoundException e) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.missingClass", actualClass),
e);
}
if (classClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.missingClass", actualClass));
}
// Instantiate and initialize an instance of the servlet class itself
try {
// 实例化
servlet = (Servlet) classClass.newInstance();
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", actualClass), e);
} catch (Throwable e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", actualClass), e);
}
// 检查该类是否允许载入
if (!isServletAllowed(servlet)) {
throw new SecurityException
(sm.getString("standardWrapper.privilegedServlet",
actualClass));
}
// 检查该servlet是否实现了ContainerServlet接口(参见19章),如果实现了,将当前Wrapper对象设置进去
if ((servlet instanceof ContainerServlet) &&
isContainerProvidedServlet(actualClass)) {
System.out.println("calling setWrapper");
((ContainerServlet) servlet).setWrapper(this);
System.out.println("after calling setWrapper");
}
try {
// 触发BEFORE_INIT_EVENT事件
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet);
// 初始化servlet
servlet.init(facade);
// loadOnStartup:此servlet的启动时加载顺序值,负值表示首次调用时加载
// 如果是jsp页面,调用该servlet的service方法
if ((loadOnStartup > 0) && (jspFile != null)) {
// Invoking jspInit
HttpRequestBase req = new HttpRequestBase();
HttpResponseBase res = new HttpResponseBase();
req.setServletPath(jspFile);
req.setQueryString("jsp_precompile=true");
servlet.service(req, res);
}
// 触发AFTER_INIT_EVENT事件
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet);
} catch (UnavailableException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
unavailable(f);
throw f;
} catch (ServletException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException
(sm.getString("standardWrapper.initException", getName()), f);
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
return servlet;
}
11.3.3 ServletConfig对象
StandardWrapper本身就是ServletConfig对象。
上面loadServlet()方法中加载完servlet之后对其进行初始化,init()方法需要传入一个ServletConfig对象:
// 初始化servlet;因为不信任servlet程序员所以使用一个门面进行包装一下
servlet.init(facade);
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration getInitParameterNames();
}
public final class StandardWrapper
extends ContainerBase
implements ServletConfig, Wrapper {
public String getServletName() {
return name;
}
public ServletContext getServletContext() {
if (parent == null)
return (null);
else if (!(parent instanceof Context))
return (null);
else
return (((Context) parent).getServletContext());
}
private HashMap parameters = new HashMap();
public String getInitParameter(String name) {
return (findInitParameter(name));
}
public String findInitParameter(String name) {
synchronized (parameters) {
return ((String) parameters.get(name));
}
}
public Enumeration getInitParameterNames() {
synchronized (parameters) {
return (new Enumerator(parameters.keySet()));
}
}
11.3.4 Servlet容器的父子关系
Wrapper实例代表一个servlet实例,不能再有子容器,所以调用它的addChildren()方法会报错。
11.5 StandardWrapperValve(基础阀)
它主要要干以下两个操作:
- 执行与该servlet实例关联的全部过滤器(Filter)
- 调用servlet实例的service()方法
它的invoke方法:
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
...
// Allocate a servlet instance to process this request
try {
if (!unavailable) {
// 获取servlet实例
servlet = wrapper.allocate();
}
} catch (ServletException e) {
log(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
} catch (Throwable e) {
log(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
// Acknowlege the request
try {
response.sendAcknowledgement();
} catch (IOException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString("standardWrapper.acknowledgeException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
log(sm.getString("standardWrapper.acknowledgeException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
// 创建过滤器链
ApplicationFilterChain filterChain = createFilterChain(request, servlet);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
String jspFile = wrapper.getJspFile();
if (jspFile != null)
sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
// 调用过滤器链
filterChain.doFilter(sreq, sres);
}
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
} catch (IOException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString("standardWrapper.serviceException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
} catch (UnavailableException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString("standardWrapper.serviceException",
wrapper.getName()), e);
// throwable = e;
// exception(request, response, e);
wrapper.unavailable(e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE))
hres.setDateHeader("Retry-After", available);
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
// Do not save exception in 'throwable', because we
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString("standardWrapper.serviceException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString("standardWrapper.serviceException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
}
// Release the filter chain (if any) for this request
try {
if (filterChain != null)
// 释放过滤器链
filterChain.release();
} catch (Throwable e) {
log(sm.getString("standardWrapper.releaseFilters",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
// Deallocate the allocated servlet instance
try {
if (servlet != null) {
// 释放对该servlet的“引用计数”
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
log(sm.getString("standardWrapper.deallocateException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
// If this servlet has been marked permanently unavailable,
// unload it and release this instance
try {
if ((servlet != null) &&
(wrapper.getAvailable() == Long.MAX_VALUE)) {
// 如果该servlet类被标记为永久不可用,则调用Wrapper.unload()方法
wrapper.unload();
}
} catch (Throwable e) {
log(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
}
11.6 FilterDef
// FilterDef类中的每个属性表示在定义filter元素时声明的子元素(web.xml)。parameters中保存的是初始化过滤器时所需要的参数。
public final class FilterDef {
/**
* The description of this filter.
*/
private String description = null;
/**
* The display name of this filter.
*/
private String displayName = null;
/**
* The fully qualified name of the Java class that implements this filter.
*/
private String filterClass = null;
/**
* The name of this filter, which must be unique among the filters
* defined for a particular web application.
*/
private String filterName = null;
/**
* The large icon associated with this filter.
*/
private String largeIcon = null;
/**
* The set of initialization parameters for this filter, keyed by
* parameter name.
*/
private Map parameters = new HashMap();
/**
* The small icon associated with this filter.
*/
private String smallIcon = null;
// --------------------------------------------------------- Public Methods
/**
* Add an initialization parameter to the set of parameters associated
* with this filter.
*
* @param name The initialization parameter name
* @param value The initialization parameter value
*/
public void addInitParameter(String name, String value) {
parameters.put(name, value);
}
11.7 ApplicationFilterConfig类
final class ApplicationFilterConfig implements FilterConfig {
// context对象表示一个web应用程序
// filterDef表示一个过滤器的定义
public ApplicationFilterConfig(Context context, FilterDef filterDef)
throws ClassCastException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
ServletException {
super();
this.context = context;
setFilterDef(filterDef);
// 尝试从filterDef中获取Filter对象,如果为空则通过反射的形式创建filter对象
if (filterDef.getFilter() == null) {
getFilter();
} else {
this.filter = filterDef.getFilter();
getInstanceManager().newInstance(filter);
initFilter();
}
}
// 负责载入并实例化一个过滤器类
Filter getFilter() throws ClassCastException, ClassNotFoundException,
IllegalAccessException, InstantiationException, ServletException {
// Return the existing filter instance, if any
if (this.filter != null)
return (this.filter);
// 获取过滤器的全类路径
String filterClass = filterDef.getFilterClass();
ClassLoader classLoader = null;
if (filterClass.startsWith("org.apache.catalina."))
classLoader = this.getClass().getClassLoader();
else
classLoader = context.getLoader().getClassLoader();
ClassLoader oldCtxClassLoader =
Thread.currentThread().getContextClassLoader();
// 使用类加载器加载该类
Class clazz = classLoader.loadClass(filterClass);
// 构造+初始化
this.filter = (Filter) clazz.newInstance();
filter.init(this);
return (this.filter);
}
11.9 ApplicationFilterChain类
Filter的doFilter()方法会接受一个ApplicationFilterChain对象,可以直接调用chain.doFilter()调用下一个过滤器。
十二、StandardContext类
Context容器需要其它组件支持,例如Session管理器,载入器等。
12.1 StandardContext的配置
StandardContext实例创建后会调用它的start()方法,但是可能会启动失败,这时它的available
属性会被设置为false,该属性表明StandardContext对象是否可用。
StandardContext类的configured
属性是一个布尔变量,表明StandardContext实例是否正确设置。当start()方法被调用时,他会触发一个生命周期事件,事件触发监听器(ContextConfig),对StandardContext实例进行配置。
12.1.2 启动流程
1、触发BEFORE_START事件
2、将available
属性设置为false
3、将configured
属性设置为false
4、配置资源(设置web应用的目录)
5、设置载入器(setLoder(new WebappLoader()))
6、设置session管理器(setManger(new Manger()))
7、初始化字符集映射器
8、启动与该Context容器相关联的组件
9、启动子容器
10、启动管道对象
11、启动session管理器
12、触发START事件,此时ContextConfig监听器会对Context进行一些配置操作,如果设置成功就会将configured
属性设置为true
13、检查configured属性的值,如果为true则调用postWelcomePages()方法,载入哪些在启动时就载入的子容器(Wrapper实例),将available
属性设置为true。若configured
属性为false
12.1.3 invoke()方法
该方法由与Context相关的连接器或者父容器(Host)的invoke()方法调用。
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 如果正在重载(paused属性为true),则等待
while (getPaused()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
;
}
}
// Normal request processing
if (swallowOutput) {
try {
SystemLogHandler.startCapture();
// 调用父类ContainerBase的invoke()方法
super.invoke(request, response);
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
log(log);
}
}
} else {
super.invoke(request, response);
}
}
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
12.2 映射器
Tomcat5中映射器已经移除,通过request对象获取Wrapper实例:
// 是不是解析URI的工作交给了连接器??
Wrapper wrapper = request.getWrapper();
12.3 对重载的支持
StandardContext通过reloadable
属性来表明应用程序是否启用了重载功能,当启用了重载功能后,web.xml文件发生变化或WEB-INF/classes目录下的其中一个文件被重新编译后,应用程序就会重载。
StandardContext时通过其载入器实现应用程序的重载的(见8.4)。WebappLoader实现了Runable接口使用另一个线程来不断检查WEB-INF目录中的所有类和JAR文件的时间戳。只需要调用WebappLoader的setContainer()
方法将容器和载入器相关联就可以启动该检查线程。
// WebappLoader.java
public void setContainer(Container container) {
// Deregister from the old Container (if any)
if ((this.container != null) && (this.container instanceof Context))
((Context) this.container).removePropertyChangeListener(this);
// Process this property change
Container oldContainer = this.container;
this.container = container;
support.firePropertyChange("container", oldContainer, this.container);
// Register with the new Container (if any)
if ((this.container != null) && (this.container instanceof Context)) {
// 启动检查线程
setReloadable( ((Context) this.container).getReloadable() );
((Context) this.container).addPropertyChangeListener(this);
}
}
public void setReloadable(boolean reloadable) {
// Process this property change
boolean oldReloadable = this.reloadable;
this.reloadable = reloadable;
support.firePropertyChange("reloadable",
new Boolean(oldReloadable),
new Boolean(this.reloadable));
// Start or stop our background thread if required
if (!started)
return;
// 之前没启动,现在启动了,则启动重载功能
if (!oldReloadable && this.reloadable)
// run()方法参见8.4.5
threadStart();
// 之前启动了,现在没启动,则关闭
else if (oldReloadable && !this.reloadable)
threadStop();
}
12.4 backgroundProcess()方法
Context容器的运行需要其它组件的支持,例如:载入器、Session管理器。而它们大多都需要执行一个后台任务,Tomcat4中它们都实现了Runable接口,拥有自己的线程。
在Tomcat5中为了节省资源,所有后台处理共享一个线程。这个共享线程有ContainerBase的start()方法调用threadStart()创建。
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString(
"containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this);
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
// 调用容器的后台进程
container.backgroundProcess();
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
// StandardContext的backgroundProcess()方法
@Override
public void backgroundProcess() {
if (!getState().isAvailable())
return;
Loader loader = getLoader();
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.loader", loader), e);
}
}
Manager manager = getManager();
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.manager", manager),
e);
}
}
WebResourceRoot resources = getResources();
if (resources != null) {
try {
resources.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.resources",
resources), e);
}
}
InstanceManager instanceManager = getInstanceManager();
if (instanceManager instanceof DefaultInstanceManager) {
try {
((DefaultInstanceManager)instanceManager).backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.instanceManager",
resources), e);
}
}
super.backgroundProcess();
}
十三、Host和Engine
如果想要在同一个Tomcat部署多个Context容器的话,就需要使用Host容器。
13.1 Host接口
// 用于返回用于处理引入的Http请求的Context容器实例(Tomcat5后已经移除,从request对象中获取)
Context map(String uri);
13.2 StandardHost
public class StandardHost
extends ContainerBase
implements Deployer, Host {
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
public synchronized void start() throws LifecycleException {
// Set error report valve
if ((errorReportValveClass != null)
&& (!errorReportValveClass.equals(""))) {
try {
Valve valve = (Valve) Class.forName(errorReportValveClass)
.newInstance();
// 添加一个ErrorReportValve阀
addValve(valve);
} catch (Throwable t) {
log(sm.getString
("standardHost.invalidErrorReportValveClass",
errorReportValveClass));
}
}
// 添加一个ErrorDispatcherValve阀
addValve(new ErrorDispatcherValve());
super.start();
}
后面的放弃了,感觉没啥用...
十四、服务器组件(Service)和服务组件(Server)
Server代表着整个tomcat
Service作用将容器(engine)和连接器组装起来,为外界提供服务
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps/1"
unpackWARs="true" autoDeploy="true">
</Host>
<Host name="www.baidu.com" appBase="webapps/3"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
14.1 服务器组件
它提供了一种优雅的方法来启动/关闭整个系统,不需要再对连接器和容器分别启动/关闭。
public interface Server {
// 关闭命令
String getShutdown();
// 监听的端口号
int getPort();
// 添加服务组件
void addService(Service service);
// 删除服务组件
void removeService(Service service);
14.2 StandardServer类
StandardServer类有4个与生命周期相关的方法,分别是initialize()
方法、start()
方法、stop()
方法、await()
方法。
调用await()方法后会一直阻塞住,直到8085端口上接收到关闭命令。当await()方法返回后,会运行stop()方法来关闭其下的所有子组件(Catalina的start()方法调用的)。
public final class StandardServer
implements Lifecycle, Server {
14.2.1 initialize()方法
初始化服务器组件
public void initialize() throws LifecycleException {
// 使用这个变量防止服务器组件初始化两次
if (initialized)
throw new LifecycleException (sm.getString("standardServer.initialize.initialized"));
initialized = true;
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
// 初始化它下面的服务组件
services[i].initialize();
}
}
14.2.2 start()方法
启动服务器组件
public void start() throws LifecycleException {
// Validate and update our current component state
if (started)
throw new LifecycleException
(sm.getString("standardServer.start.started"));
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Start our defined Services
synchronized (services) {
for (int i = 0; i < services.length; i++) {
// 启动它下面的服务组件,服务组件启动时会启动连接器组件和servlet容器
if (services[i] instanceof Lifecycle)
((Lifecycle) services[i]).start();
}
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
14.2.3 stop()方法
关闭服务器组件
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException
(sm.getString("standardServer.stop.notStarted"));
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
// 调用下面的服务组件的stop方法
for (int i = 0; i < services.length; i++) {
if (services[i] instanceof Lifecycle)
((Lifecycle) services[i]).stop();
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
14.2.4 await()方法
负责等待关闭整个Tomcat部署的命令
14.3 Service接口
一个服务组件可以有一个servlet容器和多个连接器实例。
14.1 StandardService类
它的initialize()方法用于初始化添加到其中的所有连接器。
start()方法负责启动被添加到该服务组件的连接器和servlet容器。
十五、Digester库
Digester可以将xml转换成对象。
15.2 ContextConfig(见12.1)
与其它容器不同,Context容器必须设置一个监听器来对Context进行配置,设置成功后将configured属性为true。
工作内容:
- 他会为Context的pipline添加一个许可阀和验证器阀;
- 读取和解析默认的web.xml和应用自定义的web.xml文件,将xml转换成java对象;
- 为每个servlet创建一个StandardWrapper对象。
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
if (context instanceof StandardContext) {
int contextDebug = ((StandardContext) context).getDebug();
if (contextDebug > this.debug)
this.debug = contextDebug;
}
} catch (ClassCastException e) {
log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// 处理开始和结束事件
if (event.getType().equals(Lifecycle.START_EVENT))
start();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop();
}
private synchronized void start() {
if (debug > 0)
log(sm.getString("contextConfig.start"));
context.setConfigured(false);
ok = true;
// Set properties based on DefaultContext
Container container = context.getParent();
if( !context.getOverride() ) {
if( container instanceof Host ) {
// 和DefaultContext有关(它加载了好多的东西,包括listener)
((Host)container).importDefaultContext(context);
container = container.getParent();
}
if( container instanceof Engine ) {
// 和DefaultContext有关(它加载了好多的东西,包括listener)
((Engine)container).importDefaultContext(context);
}
}
// 读取默认的web.xml
defaultConfig();
// 读取应用的web.xml
applicationConfig();
if (ok) {
validateSecurityRoles();
}
// Scan tag library descriptor files for additional listener classes
if (ok) {
try {
tldScan();
} catch (Exception e) {
log(e.getMessage(), e);
ok = false;
}
}
// Configure a certificates exposer valve, if required
if (ok)
certificatesConfig();
// Configure an authenticator if we need one
if (ok)
authenticatorConfig();
// Dump the contents of this pipeline if requested
if ((debug >= 1) && (context instanceof ContainerBase)) {
log("Pipline Configuration:");
Pipeline pipeline = ((ContainerBase) context).getPipeline();
Valve valves[] = null;
if (pipeline != null)
valves = pipeline.getValves();
if (valves != null) {
for (int i = 0; i < valves.length; i++) {
log(" " + valves[i].getInfo());
}
}
log("======================");
}
// Make our application available if no problems were encountered
if (ok)
context.setConfigured(true);
else {
log(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
十六、关闭钩子
Java虚拟机关闭之前会先启动所有的关闭钩子。
Tomcat利用关闭钩子进行清理工作(调用组件们的stop()方法)
protected class CatalinaShutdownHook extends Thread {
public void run() {
if (server != null) {
try {
((Lifecycle) server).stop();
} catch (LifecycleException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
}
}
十七、启动Tomcat
Tomcat启动时会用到两个类,分别时Catalina类和Bootstrap类。
Catalina类用于启动和关闭Server对象,并负责解析Tomcat配置文件server.xml。
Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process方法。
17.1 Catalina类
其中包含一个Digester对象,用于解析server.xml。
process()方法:
public void process(String args[]) {
// 设置连个系统属性,分别时catalina.home和catalina.base.catalina.home
setCatalinaHome();
setCatalinaBase();
try {
// argument()处理命令行参数
if (arguments(args))
//
execute();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
protected void execute() throws Exception {
if (starting)
start();
else if (stopping)
stop();
}
17.1.1 start()方法
start()方法部分代码:
// 使用Digester对象解析server.xml
...
if (server instanceof Lifecycle) {
try {
// 初始化服务器组件
server.initialize();
// 启动服务器组件
((Lifecycle) server).start();
try {
// 注册关闭时的钩子
Runtime.getRuntime().addShutdownHook(shutdownHook);
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Wait for the server to be told to shut down
server.await();
} catch (LifecycleException e) {
System.out.println("Catalina.start: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
17.1.2 stop()方法
protected void stop() {
// Create and execute our Digester
Digester digester = createStopDigester();
File file = configFile();
try {
InputSource is =
new InputSource("file://" + file.getAbsolutePath());
FileInputStream fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
fis.close();
} catch (Exception e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
System.exit(1);
}
// Stop the existing server
try {
// 向server组件监听的端口发送SHUTDOWN命令
Socket socket = new Socket("127.0.0.1", server.getPort());
OutputStream stream = socket.getOutputStream();
String shutdown = server.getShutdown();
for (int i = 0; i < shutdown.length(); i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
} catch (IOException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
System.exit(1);
}
}
Bootstrap主要是创建了3个类加载器,将sharedClassLoader设置给了Catalina的parentClassLoader属性。
十八、部署器
要使用一个web应用程序,必须要将该应用程序的Context实例部署到一个Host实例中。在Tomcat中,Context实例可以用war文件的形式来部署。
部署器是Deployer接口的实例。部署器与Host实例相关联,用来安装Context实例(既创建一个StandardContext实例,并添加到Host实例中)
Tomcat8中已经找不到它的身影。