Tomcat概述
一级目录
- Servlet容器如何工作?
- Catalina的设计和实现?
- Servlet容器 loadServlet()
3.连接器
以下讨论的并非实际使用的连接器(Coyote)
3.1 连接器作用
解析Http请求的所有信息,交由Servlet实例使用。改为若参数未被servlet实例调用,则默认连接器不会解析。连接器只解析请求头信息,HttpRequest解析请求体或查询字符串中的参数,从而提高效率。
3.2 StringManager/ResourceBundle类
StringManager/ResourceBundle类
错误消息存储在properties文件中,Tomcat创建不同的Properties,StringManager实现了按包单例的模式。
获取错误消息,可以通过sm.getString(key)来获取。
// 可以通过编写properties来获取日志,而非直接编写日志 todo 理解这种好处
if (unloading)
throw new ServletException
(sm.getString("standardWrapper.unloading", getName()));
3.3 应用程序
分为连接器模块,启动模块,核心模块
HttpServer==HttpConnector(等待http请求) + HttpProcessor(创建Request/Response对象)
HttpConnector:等待HTTP请求,创建HttpProcessor实例,调用HttpProcessor的processor()方法
启动模块:Bootstrap
连接器模块:1.连接器及其支持类(HttpConnector/HttpProcessor)
2.HttpRequest/HttpResponse/HttpRequestFacade/HttpResponseFacade/
3.常量类
HttpConnector类:
实现了Runnable接口:run方法中有以下三步操作:
- 等待HTTP请求
- 为每个请求创建一个HttpProcessor实例
- 调用HttpProcessor对象的process()方法
扩展HttpConnector中创建HttpProcessor的方法及process()的内部实现
3.4 Tomcat4中默认连接器
3.4.1导读
对连接器的要求:
- 实现Connector接口
- 创建Request/Response对象
工作原理:
等待HTTP请求,创建Request/Response对象,调用Container invoke()方法,将request,response传给servlet容器
Tomcat4中的默认连接器支持HTTP1.1,HTTP0.9,HTTP1.0
3.4.2 HTTP1.1的新特性
3.4.3 Connector接口
具有get/setContainer(),createRequest/Response()方法
3.4.4 HttpConnector类
创建ServerSocket,维护HttpProcessor池,提供Http请求服务
- 创建ServerSocket
private ServerSocket open(){
// 获取factory
ServerSocketFactory factory = getFactory();
// 创建ServerSocket
return factory.createSocket();
}
public ServerSocketFactory getFactory() {
if (this.factory == null) {
synchronized (this) {
this.factory = new DefaultServerSocketFactory();
}
}
return (this.factory);
}
- 维护HttpProcessor池
此处细看todo
3.提供Http请求服务
通过run方法
通过createProcessor()获得一个HttpProcessor对象
3.5 HttpProcessor类
HttpProcessor类有拥有run()方法,每个HttpProcessor可以运行在自己的线程(处理器线程)
当BootStrap调用容器线程,修改available为true,后调用assign方法,唤醒HttpProcess中的await()方法继续执行。
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
//采取局部变量,是为了在执行结束后,继续接受下一个socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
3.6 处理请求
HttpConnector将收到的请求转化为Socket,并将Socket传给processor的assign方法,assign通过设置available=true且唤醒await方法,将run()方法继续执行。run()方法中调用process()方法将创建request/response,并且解析Http的请求头等。
4 Container接口
容器必须实现Container接口,Tomcat共有四种容器:Engine,Host,Context,Wrapper,其标准实现均为:StandardXXX,四个标准实现均包含抽象类ContainerBase.在部署应用时,可通过编辑server.xml决定使用何种容器。
4.1 管道任务
管道(Pipeline):包含servlet容器所要执行的具体任务
阀(Valve):一个任务就是一个阀
可通过编辑server.xml动态添加阀,每个容器都有一个基础阀(基础阀最后执行)。
当调用容器的invoke()方法后,容器将处理工作交由管道完成。
整体流程:容器调用HttpProcessor.process()方法,其中执行容器的invoke()方法,容器的invoke()方法将调用管道pipeline.invoke(),管道invoke()将调用
(new StandardPipelineValveContext()).invokeNext(request, response);
从而执行invokeNext()方法
// subscript为初始值
// stage比subscript大1;
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
4.1.1 Pipeline接口
Pipeline共有以下六种方法
其中addValve(Valve valve)方法实现细节如下
public void addValve(Valve valve) {
// Validate that we can add this Valve
if (valve instanceof Contained)
((Contained) valve).setContainer(this.container);
// Start the new component if necessary
if (started && (valve instanceof Lifecycle)) {
try {
((Lifecycle) valve).start();
} catch (LifecycleException e) {
log("StandardPipeline.addValve: start: ", e);
}
}
// Add this Valve to the set associated with this Pipeline
synchronized (valves) {
Valve results[] = new Valve[valves.length +1];
System.arraycopy(valves, 0, results, 0, valves.length);
results[valves.length] = valve;
valves = results;
}
}
4.1.2 Valve接口
其中getInfo()用来表明自己的属性。
4.1.3 ValveContext接口
此接口主要用来表示Valve上下文关系,主要有getInfo()和invokeNext()方法
问题:管道中如何保证Valve从第一个执行到最后一个
4.1.4 Contained接口
通过此方法可以实现至多与一个servlet容器相关联。
4.2 Wrapper接口
主要方法load():初始化一个Servlet
allocate():分配一个已经初始化的servlet
todo:SingleThreadModel(第11节讲述)
load()/allocate()两个方法均会调用:loadServlet()方法获得servlet
扩展阅读:todo loadServlet()方法详解
4.3 Context接口
主要方法addWrapper(),createWrapper()方法
todo instanceListeners是干什么的
instanceListeners:监听器的类名数组
public Wrapper createWrapper() {
Wrapper wrapper = new StandardWrapper();
synchronized (instanceListeners) {
for (int i = 0; i < instanceListeners.length; i++) {
try {
Class clazz = Class.forName(instanceListeners[i]);
InstanceListener listener =
(InstanceListener) clazz.newInstance();
// 创建了一个监听器
wrapper.addInstanceListener(listener);
} catch (Throwable t) {
log("createWrapper", t);
return (null);
}
}
}
4.4 其它知识
基础阀:不调用ValveContext接口的invokeNext()方法,因为是基础阀
同时,Valve的invoke()中将调用servlet.service()方法;注意是Wrapper实例的基础阀调用servlet.service()方法,而不是Wrapper实例。
4.5 Context应用程序
有多个servlet类时,使用映射器
todo 映射器学一下:映射器其实使用HashMap存储(key,value):(protocol,container)的
5. Lifecycle
生命周期:众多组件随Catalina启动和关闭,这些组件需实现Lifecycle接口。父组件负责启动/关闭它的子组件。
5.1 Lifecycle接口
5.2 LifecycleEvent类
LifecycleEvent:生命周期事件
5.3 LifecycleListener接口
生命周期监听器:包含lifecycleEvent()方法。
扩展阅读:监听器模式
5.4 LifecycleSupport类
LifecycleSupport是Tomcat提供的生命周期支持类
public final class LifecycleSupport {
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);
}
}
在Lifecycle所管理的组件中,有
/**
* The lifecycle event support for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
然后调用lifecycle.fireLifecycleEvent()触发事件
6 日志系统
Tomcat中代表日志的接口Logger
日志系统主要有LoggerBase类,LoggerBase类未实现log(String):void方法。其FileLogger,SystemErrLogger,SystemOutLoggger三个类均继承LoggerBase类。其中带有verbosity形参的log方法,只有当this.verbosity>=verbosity时才会调用log(String):void
// LoggerBase的两个子类
public class SystemOutLogger
extends LoggerBase {
protected static final String info =
"org.apache.catalina.logger.SystemOutLogger/1.0";
public void log(String msg) {
System.out.println(msg);
}
}
public class SystemErrLogger
extends LoggerBase {
protected static final String info =
"org.apache.catalina.logger.SystemErrLogger/1.0";
public void log(String msg) {
System.err.println(msg);
}
}
public class FileLogger{
public void log(String msg) {
// Construct the timestamp we will use, if requested
Timestamp ts = new Timestamp(System.currentTimeMillis());
String tsString = ts.toString().substring(0, 19);
String tsDate = tsString.substring(0, 10);
// If the date has changed, switch log files
if (!date.equals(tsDate)) {
synchronized (this) {
if (!date.equals(tsDate)) {
close();
date = tsDate;
open();
}
}
}
// Log this message, timestamped if necessary
if (writer != null) {
if (timestamp) {
writer.println(tsString + " " + msg);
} else {
writer.println(msg);
}
}
}
private void close() {
if (writer == null)
return;
writer.flush();
writer.close();
writer = null;
date = "";
}
*/
private void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())
dir = new File(System.getProperty("catalina.base"), directory);
dir.mkdirs();
// Open the current log file
try {
String pathname = dir.getAbsolutePath() + File.separator +
prefix + date + suffix;
writer = new PrintWriter(new FileWriter(pathname, true), true);
} catch (IOException e) {
writer = null;
}
}
}
7 载入器
出于安全的考虑,Servlet容器不应信任Servlet类,只让其访问WEB-INF/classes的类,且提供自动重载的功能。若要支持自动重载,需实现Loader接口。
自定义类载入器:载入类中指定规则,缓存已经存在的类,实现类的预载入。
7.1 类载入器
三种类载入器:Bootstrap(启动jvm,java核心类),extension(加入标准扩展目录中的类),system class loader(CLASSPATH)
7.2 Loader接口
WebappLoader类作为Loader的实现,WebappClassLoader类作为其类载入器。
7.3 Reload接口
如果Web应用程序的某个servlet类被修改则返回true
7.4 WebappLoader类
实现了Runnable,不断的调用modified(),查找是否修改。
public void run() {
if (debug >= 1)
log("BACKGROUND THREAD Starting");
// 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;
}
// Handle a need for reloading
notifyContext();
break;
}
if (debug >= 1)
log("BACKGROUND THREAD Stopping");
}
7.4.1 创建类载入器
可以通过setLoaderClass(String):void方法填写自定义的类载入器。但是自定义类载入器需要继承WebappClassLoader,否则会报错。
private WebappClassLoader createClassLoader()
throws Exception {
Class clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
// Will cause a ClassCast is the class does not extend WCL, but
// this is on purpose (the exception will be caught and rethrown)
classLoader = (WebappClassLoader) clazz.newInstance();
} else {
Class[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
}
return classLoader;
}
7.4.2 设置仓库
通过setRepository()将仓库添加到类载入器。
7.4.3 设置类路径
setClassPath()
7.4.4 设置访问权限
setPermissions()
7.4.5 开启新线程执行类的重载
当modified()==true时,执行notifyContext()方法执行通知·
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;
}
// Handle a need for reloading
notifyContext();
break;
}
//
private void notifyContext() {
WebappContextNotifier notifier = new WebappContextNotifier();
// 开一个新线程
(new Thread(notifier)).start();
}
// WebappContextNotifier为内部类
protected class WebappContextNotifier implements Runnable {
/**
* Perform the requested notification.
*/
public void run() {
((Context) container).reload();
}
}
7.5 WebappClassLoader类
WebappClassLoader会缓存之前已经载入的类和加载失败的类的名字,当再次请求加载失败的类时,会直接报错。
triggers代表不允许载入的类,packageTriggers代表不 允许载入的包和子包。
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
};
7.5.1 类缓存
// 缓存的类(已经加载的类)
protected HashMap resourceEntries = new HashMap();
// 载入失败的类
protected HashMap notFoundResources = new HashMap();
8 Session
8.1 Manager
Manger是Session管理器,负责管理Session对象。Manager有一个工具类:ManagerBase,其有两个直接子类:StandardManager,PerisistentManagerBase。
8.1.1 Manager接口
setContainer()用于将Manager与指定容器关联,load()用于将对象从辅助存储器加载到内存,unload()相反。
8.1.2 ManagerBase类
使用createSession()创建新的session,add(),remove()等增加和移除session。
public Session createSession() {
// Recycle or create a Session instance
Session session = createEmptySession();
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
String sessionId = generateSessionId();
String jvmRoute = getJvmRoute();
// @todo Move appending of jvmRoute generateSessionId()???
if (jvmRoute != null) {
sessionId += '.' + jvmRoute;
}
synchronized (sessions) {
while (sessions.get(sessionId) != null){ // Guarantee uniqueness
sessionId = generateSessionId();
duplicates++;
// @todo Move appending of jvmRoute generateSessionId()???
if (jvmRoute != null) {
sessionId += '.' + jvmRoute;
}
}
}
session.setId(sessionId);
sessionCounter++;
return (session);
}
// todo 看源码
public Session createEmptySession() {
Session session = null;
synchronized (recycled) {
int size = recycled.size();
if (size > 0) {
session = (Session) recycled.get(size - 1);
recycled.remove(size - 1);
}
}
if (session != null)
session.setManager(this);
else
session = new StandardSession(this);
return(session);
}
// todo 扩展阅读吧
protected synchronized String generateSessionId() {
// Generate a byte array containing a session identifier
Random random = getRandom();
byte bytes[] = new byte[SESSION_ID_BYTES];
getRandom().nextBytes(bytes);
bytes = getDigest().digest(bytes);
// Render the result as a String of hexadecimal digits
StringBuffer result = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
byte b2 = (byte) (bytes[i] & 0x0f);
if (b1 < 10)
result.append((char) ('0' + b1));
else
result.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
result.append((char) ('0' + b2));
else
result.append((char) ('A' + (b2 - 10)));
}
return (result.toString());
}
8.1.2 PersistentManagerBase类
PersistentManagerBase是所有持久化Session管理器的父类,PersistentManagerBase使用store的私有引用。
// PersistentManagerBase使用专门的线程处理备份和换出Session对象的任务。
public void run() {
// Loop until the termination semaphore is set
while (!threadDone) {
threadSleep();
processExpires();
processPersistenceChecks();
}
}
// 1.1 过期session的处理
protected void processExpires() {
if (!started)
return;
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i++) {
StandardSession session = (StandardSession) sessions[i];
if (!session.isValid())
continue;
if (isSessionStale(session, timeNow))
session.expire();
}
}
// 1.2 注意此处用空闲时间>=活跃时间判断是否过期
// FIXME: Probably belongs in the Session class.
protected boolean isSessionStale(Session session, long timeNow) {
int maxInactiveInterval = session.getMaxInactiveInterval();
if (maxInactiveInterval >= 0) {
int timeIdle = // Truncate, do not round up
(int) ((timeNow - session.getLastAccessedTime()) / 1000L);
if (timeIdle >= maxInactiveInterval)
return true;
}
return false;
}
//
public void processPersistenceChecks() {
// 空闲的交换
processMaxIdleSwaps();
processMaxActiveSwaps();
// 备份
processMaxIdleBackups();
}
查找session findSession(String id):Session
//
public Session findSession(String id) throws IOException {
// 先从内存中查找
Session session = super.findSession(id);
if (session != null)
return (session);
// See if the Session is in the Store
// 从存储器中查找
session = swapIn(id);
return (session);
}
8.1.3 PersistentManager类
PersistentManager类继承PersistentManagerBase,多加了两个属性。
8.1.4 DistributedManager类
8.2 Store存储器
// save:用于将指定的session对象存储到持久性存储器中
// load:从存储器中将session对象载入内存
8.2.1 StoreBase类
public abstract class StoreBase
implements Lifecycle, Runnable, Store {}
StoreBase中并未实现save,load方法。
FileStore类会将Session对象存储到某个文件中,文件名后缀加.session
8.2.2 JDBCStore类
将Session对象通过JDBC存入数据库。
9.安全性
todo
10.StandardWrapper
- 连接器创建request/response对象
- 调用StandardContext invoke()方法
- StandardContext invoke()方法——>调用基阀的invoke()
- 基础阀invoke()调用Wrapper invoke()处理HTTP请求
- 调用管道的invoke,最后油基础阀调用invoke()方法,使用allocate()获取servlet实例
- load()调用servlet实例的init()方法
- StandardWrapperValve service()方法
10.1载入Servlet类
public synchronized void load() throws ServletException {
instance = loadServlet();
}
/**
1. 先判断是否已经加载过此类
2. 没加载过则设置类名
3. 获得加载器
4. 获得servlet
5. 返回servlet
*/
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
// 此前已经加载过,则直接返回
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet = null;
try {
// If this "servlet" is really a JSP file, get the right class.
// HOLD YOUR NOSE - this is a kludge that avoids having to do special
// case Catalina-specific code in Jasper - it also requires that the
// servlet path be replaced by the <jsp-file> element content in
// order to be completely effective
String actualClass = servletClass;
if ((actualClass == null) && (jspFile != null)) {
Wrapper jspWrapper = (Wrapper)
((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
if (jspWrapper != null)
actualClass = jspWrapper.getServletClass();
}
// Complain if no servlet class has been specified
if (actualClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
// 3.获得加载器
// Acquire an instance of the class loader to be used
Loader loader = getLoader();
if (loader == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.missingLoader", getName()));
}
// 4.获得类加载器
ClassLoader classLoader = loader.getClassLoader();
// Special case class loader for a container provided servlet
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) {
classClass = classLoader.loadClass(actualClass);
} else {
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
// 获得servlet
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);
}
// Check if loading the servlet in this web application should be
// allowed
if (!isServletAllowed(servlet)) {
throw new SecurityException
(sm.getString("standardWrapper.privilegedServlet",
actualClass));
}
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
isContainerProvidedServlet(actualClass)) {
((ContainerServlet) servlet).setWrapper(this);
}
// Call the initialization method of this servlet
try {
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
servlet);
servlet.init(facade);
// Invoke jspInit on JSP pages
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);
}
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);
}
// Register our newly initialized instance
singleThreadModel = servlet instanceof SingleThreadModel;
if (singleThreadModel) {
if (instancePool == null)
instancePool = new Stack();
}
fireContainerEvent("load", this);
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
10.2 ServletConfig对象
StandardWrapper获取ServletConfig对象
初始化参数
通过getInitParameter(String)方法,获取初始化参数,存储在一个HashMap中
10.3 servlet容器的父子关系
Wrapper的父类是Context,所以Wrapper setparent()方法只能传Context容器
10.4 StandWrapperFacade类
11 Server类
本节研究关闭/打开servlet容器
服务器组件:可以启动/关闭整个系统,不需要对连接器和容器分别启动/关闭。
服务器组件启动后会启动所有的组件,然后等待关闭命令。
shutdown属性保存关闭命令,port属性定义了端口号。
11.1 StandardServer类
可以通过addservice(),removeService()/findService()方法的实现。
11.1.1 三大方法 initialize() start() stop()
// initialized防止初始化两次
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();
}
}
// 启动所有的服务组件
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++) {
if (services[i] instanceof Lifecycle)
((Lifecycle) services[i]).start();
}
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
/**
1. 创建ServerSocket对象,监听8005端口
2. 获取socket
3. 将消息与关闭命令字符串比较,相同跳出while循环,关闭SocketServer
*/
public void await() {
// Set up a server socket to wait on
ServerSocket serverSocket = null;
try {
serverSocket =
new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
System.err.println("StandardServer.await: create[" + port
+ "]: " + e);
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a connection and a valid command
while (true) {
// Wait for the next connection
Socket socket = null;
InputStream stream = null;
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (AccessControlException ace) {
System.err.println("StandardServer.accept security exception: "
+ ace.getMessage());
continue;
} catch (IOException e) {
System.err.println("StandardServer.await: accept: " + e);
e.printStackTrace();
System.exit(1);
}
// Read a set of characters from the socket
StringBuffer command = new StringBuffer();
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random(System.currentTimeMillis());
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
System.err.println("StandardServer.await: read: " + e);
e.printStackTrace();
ch = -1;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
// Close the socket now that we are done with it
try {
socket.close();
} catch (IOException e) {
;
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
break;
} else
System.err.println("StandardServer.await: Invalid command '" +
command.toString() + "' received");
}
// Close the server socket and return
try {
serverSocket.close();
} catch (IOException e) {
;
}
}
11.2 Service接口
服务组件是Service接口的实例,一个服务组件可以有一个容器和多个连接器
11.2.1 StandardService类
initialize方法用于初始化添加到其中的所有连接器。
- connector/container
只会有一个container和多个connector,
// 多个连接器
private Connector connectors[] = new Connector[0];
// 一个容器
private Container container = null;
public void setContainer(Container container) {
Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (started && (this.container != null) &&
(this.container instanceof Lifecycle)) {
try {
((Lifecycle) this.container).start();
} catch (LifecycleException e) {
;
}
}
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++)
connectors[i].setContainer(this.container);
}
if (started && (oldContainer != null) &&
(oldContainer instanceof Lifecycle)) {
try {
((Lifecycle) oldContainer).stop();
} catch (LifecycleException e) {
;
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldContainer, this.container);
}
public void addConnector(Connector connector) {
synchronized (connectors) {
connector.setContainer(this.container);
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (initialized) {
try {
connector.initialize();
} catch (LifecycleException e) {
e.printStackTrace(System.err);
}
}
if (started && (connector instanceof Lifecycle)) {
try {
((Lifecycle) connector).start();
} catch (LifecycleException e) {
;
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
- 与生命周期有关的方法
initialize()用于初始化
start()负责启动被添加的容器和连接器
stop()用于关闭 - Stopper类
以优雅的方式关闭servlet容器。
12关闭钩子
关闭钩子是java提供的用于在jvm推出后执行代码清理工作的技术
主要实现如下
public class ShutdownHook {
public static void main(String[] args) {
//2. 在适合的地方开启关闭钩子
new ShutdownHook().start();
System.out.println("正常程序");
System.exit(0);
}
public void start() {
// 1.注册关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread((new Runnable() {
public void run() {
System.out.println("关闭钩子运行");
}
})));
}
}
关闭钩子使用场景:
- 程序正常退出
- 使用System.exit()
- 终端使用Ctrl+C触发的中断
- 系统关闭
- OutOfMemory宕机
- 使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的)
Tomcat中关闭钩子的使用
public class Catalina{
public void start(){
Thread shutdownHook = new CatalinaShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
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);
}
}
}
}
}
}
13 启动Tomcat
启动tomcat主要依赖Bootstrap和Catalina
Catalina负责启动/关闭Server对象,解析Tomcal的server.xml配置文件。
13.1 Catalina
Catalina定义如下,主要方法process(String[]):void
/**
* The instance main program.
*
* @param args Command line arguments
*/
public void process(String args[]) {
setCatalinaHome();//设置catalina.home的值 user.dir
setCatalinaBase();//设置catalina.base.catalina.home的值 user.dir
try {
if (arguments(args))
execute();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
/**
* Execute the processing that has been configured from the command line.
*/
protected void execute() throws Exception {
if (starting)
start();
else if (stopping)
stop();
}