概述
在前面的章节中已经看到了如何初始化连接器和容器并将它们关联起来作为Servlet 容器。前面只有一个连接器,通过端口 8080 对 HTTP 请求进行服务。我们不能添加另一个连接器来处理例如 HTTPS 的请求。
另外,前面所有的配套Demo程序都缺乏一件事:很好地启动和停止 Servlet 容器的机制。在本章中,我们将提供了该机制以及其它一些附属组件:服务器(server)和服务(service)。
14.1 Server接口
org.apache.catalina.Server 接口表示整个 Catalina Servlet 容器以及囊括其它组件。一个服务器相当有用,因为它提供了一种优雅的机制来启动和停止整个系统。不必再单独地启动连接器和容器了。
下面阐述说明启动和停止机制是如何工作的。当服务器启动时,它会启动它内部的所有组件,然后无限期的等待关闭命令。如果想要关闭系统,我们发送一个关闭命令到指定端口即可。当服务器收到正确的关闭指令时,它会停止所有组件的服务。
服务器还使用了另外一个组件,服务(service),它用来持有组件,例如容器或者一个多个
的连接器。服务将在本章的 service 小节中介绍。
Server 接口如 Listing14.1 所示。
Listing 14.1: The Server interface
package org.apache.catalina;
import org.apache.catalina.deploy.NamingResources;
public interface Server {
// ------------------------------------------------------------- Properties
/**
* Return descriptive information about this Server implementation and
* the corresponding version number, in the format
* <code><description>/<version></code>.
*/
public String getInfo();
/**
* Return the global naming resources.
*/
public NamingResources getGlobalNamingResources();
/**
* Set the global naming resources.
*
* @param namingResources The new global naming resources
*/
public void setGlobalNamingResources
(NamingResources globalNamingResources);
/**
* Return the port number we listen to for shutdown commands.
*/
public int getPort();
/**
* Set the port number we listen to for shutdown commands.
*
* @param port The new port number
*/
public void setPort(int port);
/**
* Return the shutdown command string we are waiting for.
*/
public String getShutdown();
/**
* Set the shutdown command we are waiting for.
*
* @param shutdown The new shutdown command
*/
public void setShutdown(String shutdown);
// --------------------------------------------------------- Public Methods
/**
* Add a new Service to the set of defined Services.
*
* @param service The Service to be added
*/
public void addService(Service service);
/**
* Wait until a proper shutdown command is received, then return.
*/
public void await();
/**
* Return the specified Service (if it exists); otherwise return
* <code>null</code>.
*
* @param name Name of the Service to be returned
*/
public Service findService(String name);
/**
* Return the set of Services defined within this Server.
*/
public Service[] findServices();
/**
* Remove the specified Service from the set associated from this
* Server.
*
* @param service The Service to be removed
*/
public void removeService(Service service);
/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*
* @exception LifecycleException If this server was already initialized.
*/
public void initialize()
throws LifecycleException;
}
属性 shutdown 用来持有一个停止服务的指令字符串。属性 port 则是服务器等待接收关闭命令的端口。可以调用服务器的 addService() 方法将服务添加到服务器。使用removeService ()方法将服务删除。findServices()返回服务器中所有的服务。initialize()方法中包含需要在启动之前执行的代码。
14.2 StandardServer类
org.apache.catalina.core.StandardServer 类是服务器(Server)的标准实现。我们会特别关注这个类中最重要的特性:关闭机制。而有关通过 server.xml 配置服务器的相关方法,这里不予讨论。感兴趣的话可以自己阅读,并不难理解。
一个服务器可以有零个或多个服务,StandardServer 类提供了 addService()、removeService()、findServices()方法的实现。
另外还有4个跟生命周期相关的方法:initialize() 、start() 、stop() 以及 await()。跟其它组件相似,初始化和启动一个服务器。然后可以使用 await() 方法和 stop()方法。await()方法在收到8085端口(或其他端口)关闭指令之前会一直等待。当 await()方法返回时,stop()方法会执行停止所有子组件。在本章配套的Demo中,我们可以看到如何实现关闭机制。
关于 initialize(), start(), stop() 和 await()方法的内容将在如下子节中讨论。
14.3.1 initialize()方法
initialize()方法用于初始化添加到服务器实例中的服务,StandardServer 的initialize()方法如 Listing14.2:
Listing 14.2: The initialize method
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();
}
}
注意该方法使用了一个名为 initialized 的变量来避免多次启动该服务器。在Tomcat5 中,initialize() 方法于此相似,但是包括了 JMX 的相关代码。stop()方法不会重置 initialized 的值,所以当服务器被停止后再次启动时,不会再次调用 initialized()方法。
14.3.2 start()方法
可以使用 start()方法来启动一个服务器,StandardServer的 start()方法的实现将会启动所有服务及其相关组件,例如连接器和容器。Listing14.3 展示了 start()方法:
Listing 14.3: The start method
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);
}
该方法使用了一个 started 布尔变量来避免一个服务器被启动两次。stop()方法中会重置该变量的值。
14.3.3 stop()方法
stop()方法用于停止一个服务器,该方法如 Listing14.4 所示:
Listing 14.4: The stop method
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 our defined Services
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);
}
调用 stop()方法可以停止所有的服务并重置 started 变量的值,以便于可以再次启动服务器。
14.3.4 await()方法
await()负责整个 Tomcat 部署的停止机制,它的代码如 Listing14.5 所示:
Listing 14.5: The await method
/**
* Wait until a proper shutdown command is received, then return.
*/
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) {
;
}
}
方法 await()在 8085 端口创建一个 ServerSocket 对象,在 while 循环中调用它的accept()方法。accept()方法仅仅接受 8085 端口的信息。它将接受到的信息跟shutdown 命令进行匹配,如果匹配的话跳出循环关闭 SocketServer,如果不匹配继续 while 循环等待另一个命令。
14.3 Service接口
org.apache.catalina.Service 接口用于表示服务。一个服务可以有一个容器和多个连接器。我们可以添加多个连接器 ,并将它们跟容器相关联。Service接口如 Listing14.6 所示:
Listing 14.6: The Service interface
public interface Service {
// ------------------------------------------------------------- Properties
/**
* Return the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*/
public Container getContainer();
/**
* Set the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*
* @param container The new Container
*/
public void setContainer(Container container);
/**
* Return descriptive information about this Service implementation and
* the corresponding version number, in the format
* <code><description>/<version></code>.
*/
public String getInfo();
/**
* Return the name of this Service.
*/
public String getName();
/**
* Set the name of this Service.
*
* @param name The new service name
*/
public void setName(String name);
/**
* Return the <code>Server</code> with which we are associated (if any).
*/
public Server getServer();
/**
* Set the <code>Server</code> with which we are associated (if any).
*
* @param server The server that owns this Service
*/
public void setServer(Server server);
// --------------------------------------------------------- Public Methods
/**
* Add a new Connector to the set of defined Connectors, and associate it
* with this Service's Container.
*
* @param connector The Connector to be added
*/
public void addConnector(Connector connector);
/**
* Find and return the set of Connectors associated with this Service.
*/
public Connector[] findConnectors();
/**
* Remove the specified Connector from the set associated from this
* Service. The removed Connector will also be disassociated from our
* Container.
*
* @param connector The Connector to be removed
*/
public void removeConnector(Connector connector);
/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*
* @exception LifecycleException If this server was already initialized.
*/
public void initialize()
throws LifecycleException;
}
14.4 StandardService类
org.apache.catalina.core.StandardService 类是 Service 接口的标准实现。StandardService 类的 initialize()方法初始化添加到该服务中的所有连接器。该类还实现了 org.apache.catalina.Lifecycle 接口,因此它的 start()方法能启动所有的连接器和容器。
14.4.1容器和连接器
一个 StandardService 实例中包含两种组件:一个容器和多个连接器。多个连接器可以使得 Tomcat 能服务于多种协议。一种处理 HTTP协议请求,另一个用于处理 HTTPS协议请求。
StandardService 类用 container 变量来持有容器实例,用connectors数组来持有所有连接器:
private Container container = null;
private Connector connectors[] = new Connector[0];
要将一个容器跟一个服务相关联,可以使用如下Listing14.7 所示的setContainer()方法:
Listing 14.7: The setContainer method
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);
}
与此服务相关联的容器将通过setContainer()方法传递到此服务中每个可用的连接器对象上,使容器和每个连接器个体之间相关联。
可以使用 addConnector() 方法给服务添加连接器。要删除一个连接器,可以使用 removeConnector()方法。它们分别如 Listing14.8 和 Listing14.9所示:
Listing 14.8: The addConnector method
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);
}
}
Listing 14.9: The removeConnector method
public void removeConnector(Connector connector) {
synchronized (connectors) {
int j = -1;
for (int i = 0; i < connectors.length; i++) {
if (connector == connectors[i]) {
j = i;
break;
}
}
if (j < 0)
return;
if (started && (connectors[j] instanceof Lifecycle)) {
try {
((Lifecycle) connectors[j]).stop();
} catch (LifecycleException e) {
;
}
}
connectors[j].setContainer(null);
connector.setService(null);
int k = 0;
Connector results[] = new Connector[connectors.length - 1];
for (int i = 0; i < connectors.length; i++) {
if (i != j)
results[k++] = connectors[i];
}
connectors = results;
// Report this property change to interested listeners
support.firePropertyChange("connector", connector, null);
}
}
addConnector()方法初始化并启动所有添加的连接器。
14.4.2 生命周期方法
lifecycle()、start()、stop()和initialize()方法都是从 Lifecycle 接口继承而来。initialize()方法中调用此服务中每个连接器的 initialize()方法,该方法如 Listing14.10 所示:
Listing 14.10: The initialize method of StandardService
public void initialize() throws LifecycleException {
if (initialized)
throw new LifecycleException (sm.getString("StandardService.initialize.initialized"));
initialized = true;
// Initialize our defined Connectors
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++) {
connectors[i].initialize();
}
}
start()方法用于启动相关联的连接器和容器,如 Listing14.11 所示:
Listing 14.11: The start method
public void start() throws LifecycleException {
// Validate and update our current component state
if (started) {
throw new LifecycleException
(sm.getString("standardService.start.started"));
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
System.out.println
(sm.getString("standardService.start.name", this.name));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Start our defined Container first
if (container != null) {
synchronized (container) {
if (container instanceof Lifecycle) {
((Lifecycle) container).start();
}
}
}
// Start our defined Connectors second
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++) {
if (connectors[i] instanceof Lifecycle)
((Lifecycle) connectors[i]).start();
}
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
stop()方法关闭所有相关联的连接器和容器。stop()方法如 Listing14.12 所示:
Listing 14.12: The stop method
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started) {
throw new LifecycleException
(sm.getString("standardService.stop.notStarted"));
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
System.out.println
(sm.getString("standardService.stop.name", this.name));
started = false;
// Stop our defined Connectors first
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++) {
if (connectors[i] instanceof Lifecycle)
((Lifecycle) connectors[i]).stop();
}
}
// Stop our defined Container second
if (container != null) {
synchronized (container) {
if (container instanceof Lifecycle) {
((Lifecycle) container).stop();
}
}
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
14.5 应用Demo
这个应用Demo展示了如何使用服务器和服务,特别是说明了StandardServer 类中如何使用启动和停止机制。本Demo由3个类组成,SimpleContextConfig 跟第 13 章中的相同,另外Bootstrap类用来启动服务, Stopper 类用来停止服务。
14.5.1 Bootstrap类
Bootstrap类如Listing 14.13:
Listing 14.13: The Bootstrap Class
package ex14.pyrmont.startup;
import ex14.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;
public final class Bootstrap {
public static void main(String[] args) {
System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();
Wrapper wrapper1 = new StandardWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new StandardWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/app1");
context.setDocBase("app1");
context.addChild(wrapper1);
context.addChild(wrapper2);
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
Host host = new StandardHost();
host.addChild(context);
host.setName("localhost");
host.setAppBase("webapps");
Loader loader = new WebappLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
Engine engine = new StandardEngine();
engine.addChild(host);
engine.setDefaultHost("localhost");
Service service = new StandardService();
service.setName("Stand-alone Service");
Server server = new StandardServer();
server.addService(service);
service.addConnector(connector);
//StandardService class's setContainer will call all its connector's setContainer method
service.setContainer(engine);
// Start the new server
if (server instanceof Lifecycle) {
try {
server.initialize();
((Lifecycle) server).start();
server.await();
// the program waits until the await method returns,
// i.e. until a shutdown command is received.
}
catch (LifecycleException e) {
e.printStackTrace(System.out);
}
}
// Shut down the server
if (server instanceof Lifecycle) {
try {
((Lifecycle) server).stop();
}
catch (LifecycleException e) {
e.printStackTrace(System.out);
}
}
}
}
Bootstrap main()方法的开始部分跟第 13 章相似。它创建了一个连接器(Connector)、两个包装器(Wrapper)、一个上下文(Context)、一个主机(Host)以及一个引擎(Engine)。然后将包装器添加到上下文,将上下文添加到主机,主机添加到引擎。它并没有将连接器和引擎相关联,而是创建了一个服务对象,设置它的名字;创建一个服务器对象,并给该服务器添加服务:
Service service = new StandardService();
service.setName("Stand-alone Service");
Server server = new StandardServer();
server.addService(service);
然后,主将连接器和引擎添加到服务上:
service.addConnector(connector);
service.setContainer(engine);
这样把连接器添加到服务上,连接器在服务上跟容器相关联。
然后调用服务器的 initialize() 和 start() 方法。初始化连接器并启动它以及容器:
if (server instanceof Lifecycle) {
try {
server.initialize();
((Lifecycle) server).start();
接下来,调用服务器的 await()方法让服务器在8085端口上等待一个关闭命令。注意此时连接器已经启动了,在 默认8080 端口上提供HTTP服务:
server.await() ;
await()方法会一直等待,直到接收正确的关闭指令,然后调用服务器的stop()方法,关闭所有组件。
接下来学习用于停止服务器的 Stopper 类。
14.5.2 Stopper类
在前面章节应用Demo中,都是通过按键这一粗暴方式关闭容器。在本章中,Stopper类提供了一种优雅的方式来关闭 Catalina 服务器。它还保证了所有生命周期组件的 stop()方法会被调用。Stopper 类如 Listing14.14 所示:
Listing 14.14: The Stopper class
package ex14.pyrmont.startup;
import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;
public class Stopper {
public static void main(String[] args) {
// the following code is taken from the Stop method of
// the org.apache.catalina.startup.Catalina class
int port = 8005;
try {
Socket socket = new Socket("127.0.0.1", port);
OutputStream stream = socket.getOutputStream();
String shutdown = "SHUTDOWN";
for (int i = 0; i < shutdown.length(); i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
System.out.println("The server was successfully shut down.");
}catch (IOException e) {
System.out.println("Error. The server has not been started.");
}
}
}
main()方法创建一个 Socket 对象然后将关闭命令” SHUTDOWN “发送到 8085 端口。如果 Catalina服务器正在运行,它将会被关闭。
14.5.3 运行Demo
在 Windows 下面可以在工作目录下输入如下命令运行该程序:
java -classpath./lib/servlet.jar;./lib/commons-
collections.jar;./lib/commons-digester.jar;./lib/naming-
factory.jar;./lib/naming-common.jar;./ ex14.pyrmont.startup.Bootstrap
在 Linux 下面需要使用分号来分隔开两个库:
java -classpath ./lib/servlet.jar:./lib/commons-
collections.jar:./lib/commons-digester.jar:./lib/naming-
factory.jar:./lib/naming-common.jar:./ ex14.pyrmont.startup.Bootstrap
使用如下 URL 可以调用 PrimitiveServlet和ModernServlet:
http://localhost:8080/app1/Primitive
http://localhost:8080/app1/Modern
可以在工作目录下面运行 Stopper 来停止该应用程序:
java ex14.pyrmont.startup.Stopper
注意:在一个真正的 Catalina 部署中,提供停止服务的 Stopper 类的功能会被包装在 Bootstrap 类中。
14.6 小结
本章介绍了Catalina的2个重要组件:服务器(Server)和服务(Service)。 服务器是特别有用的,因为它提供了一优雅的机制启动和停止Catalina部署。 服务组件封装了容器和一个或多个连接器。 本章附带的Demo演示了如何使用服务器和服务组件。 也说明了在StandardServer中如何使用停止机制。