org.apache.catalina.startup.Bootstrap是tomcat的启动类,即入口类。而实际上Tomcat的启动是通过org.apache.catalina.startup.Catalina来执行的,Bootstrap类只是一层代理,通过反射的方式来调用Catalina的start()方法。
接下来让我们看看看Catalina类start()方法具体做了哪些操作。
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
这个方法主要做了如下几个操作:
查看server是否已经被加载了。如果没有则加载server(load方法)。
加载过后如果server还是为空,则记录错误日志后直接返回。
调用server的start方法。
向当前虚拟机置入一个hook,当虚拟机退出后会执行该hook里的方法。
设置logMannager。
最后等待,直到接受到退出的命令。
其中最核心的方法其实是在load()和server.start()方法。
/**
* Start a new server instance.
*/
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Parse main server.xml
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
load()方法结构比较清晰,做了一些初始化配置之后就开始解析我们部署web项目经常要用到的server.xml配置文件(parseServerXml(true)方法),并且在解析过程中创建了server实例。
接下来让我们来看下tomcat是如何解析server.xml和创建tocmat中的各个组件(server、service等)。
protected void parseServerXml(boolean start) {
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// Init source location
File serverXmlLocation = null;
if (generateCode) {
//
if (generatedCodeLocationParameter != null) {
generatedCodeLocation = new File(generatedCodeLocationParameter);
if (!generatedCodeLocation.isAbsolute()) {
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter);
}
} else {
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), "work");
}
serverXmlLocation = new File(generatedCodeLocation, "catalina");
if (!serverXmlLocation.isDirectory() && !serverXmlLocation.mkdirs()) {
log.warn(sm.getString("catalina.generatedCodeLocationError", generatedCodeLocation.getAbsolutePath()));
// Disable code generation
generateCode = false;
}
}
ServerXml serverXml = null;
if (useGeneratedCode) {
// 内置tomcat使用ServerXml接口来配置
String xmlClassName = start ? "catalina.ServerXml" : "catalina.ServerXmlStop";
try {
serverXml = (ServerXml) Catalina.class.getClassLoader().loadClass(xmlClassName).newInstance();
} catch (Exception e) {
// Ignore, no generated code found
}
}
if (serverXml != null) {
serverXml.load(this);
} else {
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// Create and execute our Digester
Digester digester = start ? createStartDigester() : createStopDigester();
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
if (generateCode) {
digester.startGeneratingCode();
generateClassHeader(digester, start);
}
digester.parse(inputSource);
if (generateCode) {
generateClassFooter(digester);
try (FileWriter writer = new FileWriter(new File(serverXmlLocation,
start ? "ServerXml.java" : "ServerXmlStop.java"))) {
writer.write(digester.getGeneratedCode().toString());
}
digester.endGeneratingCode();
}
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
}
}
}
前面大部分代码都是内置tomcat中配置用到的,我们暂时先不管,直到serverXml != null这一段,else分支中才是真正解析server.xml的逻辑。tomcat通过构建了一个Digester来解析,将server.xml构建成一个InputSource对象,通过digester对象的parse方法来具体解析server.xml。我们跟随该方法来看看,解析过程具体做了哪些 操作。
public class Digester extends DefaultHandler2 {
// 省略其他代码
/**
* Parse the content of the specified input source using this Digester.
* Returns the root element from the object stack (if any).
*
* @param input Input source containing the XML data to be parsed
* @return the root object
* @exception IOException if an input/output error occurs
* @exception SAXException if a parsing exception occurs
*/
public Object parse(InputSource input) throws IOException, SAXException {
configure();
getXMLReader().parse(input);
return root;
}
/**
* Return the XMLReader to be used for parsing the input document.
*
* FIX ME: there is a bug in JAXP/XERCES that prevent the use of a
* parser that contains a schema with a DTD.
* @return the XML reader
* @exception SAXException if no XMLReader can be instantiated
*/
public XMLReader getXMLReader() throws SAXException {
if (reader == null) {
reader = getParser().getXMLReader();
}
reader.setDTDHandler(this);
reader.setContentHandler(this);
EntityResolver entityResolver = getEntityResolver();
if (entityResolver == null) {
entityResolver = this;
}
// Wrap the resolver so we can perform ${...} property replacement
if (entityResolver instanceof EntityResolver2) {
entityResolver = new EntityResolver2Wrapper((EntityResolver2) entityResolver, source, classLoader);
} else {
entityResolver = new EntityResolverWrapper(entityResolver, source, classLoader);
}
reader.setEntityResolver(entityResolver);
reader.setProperty("http://xml.org/sax/properties/lexical-handler", this);
reader.setErrorHandler(this);
return reader;
}
}
代码抽丝剥茧到这一步我们可以看到,tomcat使用的是SAX来解析server.xml文件的,在getXMLReder()方法中通过将自身的引用作为DTDHandler、ContentHandler、EntityResolver、ErrorHandler传入到XMLReader的实例对象reader中去。而从Digester的继承关系中我们能看到Digester是继承了DefaultHandler2类,相信熟悉SAX解析的同学对这个类应该都不陌生,这里就不多做解释了。
解析来思路就比较清晰了,让我们来看下SAX解析handler中几个关键的方法来看看tomcat具体都做了些什么。
/**
* Process notification of the start of an XML element being reached.
*
* @param namespaceURI The Namespace URI, or the empty string if the element
* has no Namespace URI or if Namespace processing is not being performed.
* @param localName The local name (without prefix), or the empty
* string if Namespace processing is not being performed.
* @param qName The qualified name (with prefix), or the empty
* string if qualified names are not available.\
* @param list The attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @exception SAXException if a parsing error is to be reported
*/
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled();
if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + qName + ")");
}
// Parse system properties
// 对${}变量进行替换
list = updateAttributes(list);
// Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
bodyText = new StringBuilder();
// the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
// Compute the current matching rule
StringBuilder sb = new StringBuilder(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
}
// Fire "begin" events for all relevant rules
List<Rule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (Rule value : rules) {
try {
Rule rule = value;
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
log.error(sm.getString("digester.error.begin"), e);
throw createSAXException(e);
} catch (Error e) {
log.error(sm.getString("digester.error.begin"), e);
throw e;
}
}
} else {
if (debug) {
log.debug(sm.getString("digester.noRulesFound", match));
}
}
}