tomcat8源码分析03-核心组件创建
从tomcat的架构图来看,tomcat的核心组件有Server、Service、Connector、Container。
这些组件是如何被创建出来的?
通过源码我们可以看到,tomcat在启动的时候实例化了Catalina类并执行了初始化、启动操作,而Catalina实例在执行load时会读取conf/server.xml,从解析完配置后调用getServer().setCatalina(this)我们可以猜到在配置解析的时候server会被实例化,也就是说tomcat实际是通过控制反转来实例化这些对象。
// BootStrap.java
daemon.setAwait(true);
daemon.load(args);
daemon.start();
// Catalina.java
public void load() {
...
// 读取server.xml配置流
file = configFile();
inputStream = new FileInputStream(file);
...
// 创建解析器
Digester digester = createStartDigester();
...
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
}
...
// getServer不再是null说明在配置解析后server已经被实例化并注入到catalina实例中
getServer().setCatalina(this);
...
}
那么tomcat是如何实现通过控制反转创建四大件的?从createStartDigester方法可以看出在创建解析器时代码上写了很多解析规则(Rule),比如Server的实现类是org.apache.catalina.core.StandardServer继承于LifecycleMBeanBase类,而LifecycleMBeanBase类继承了LifecycleBase并实现了JmxEnabled接口,这样我们只要查看对应的解析规则和父类就可以看到Server是如何被实例化的。
// Catalina.java
protected Digester createStartDigester() {
...
// 创建实例
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
// 注入字符类型成员变量
digester.addSetProperties("Server");
// 调用父节点方法为父节点注入特殊成员变量,比如Server的父节点是Catalina
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
...
return digester;
}
// Digester.java
public void addObjectCreate(String pattern, String className, String attributeName) {
// 添加一个实例化对象的解析规则
addRule(pattern, new ObjectCreateRule(className, attributeName));
}
public void addSetProperties(String pattern) {
// 添加注入字符类型成员变量规则
addRule(pattern, new SetPropertiesRule());
}
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
throws SAXException {
...
// Fire "begin" events for all relevant rules
// match初始是空字符串,实际上是我们xml标签的一种表现形式,比如读到server.xml的Server标签时match为Server,读到Listener标签时为Server/Listener
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("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
}
...
}
// ObjectCreateRule.java
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
...
// 根据解析规则创建实例
// Instantiate the new object and push it on the context stack
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.getConstructor().newInstance();
digester.push(instance);
}
// SetNextRule.java
public void end(String namespace, String name) throws Exception {
// Identify the objects to be used
Object child = digester.peek(0);
Object parent = digester.peek(1);
if (digester.log.isDebugEnabled()) {
if (parent == null) {
digester.log.debug("[SetNextRule]{" + digester.match +
"} Call [NULL PARENT]." +
methodName + "(" + child + ")");
} else {
digester.log.debug("[SetNextRule]{" + digester.match +
"} Call " + parent.getClass().getName() + "." +
methodName + "(" + child + ")");
}
}
// Call the specified method
// 这一步是和Catalina实例设置的规则相关的,当规则匹配时父节点的方法将被调用
// digester.addSetNext("Server",
// "setServer",
// "org.apache.catalina.Server");
IntrospectionUtils.callMethod1(parent, methodName,
child, paramType, digester.getClassLoader());
}
我们可以看到SetNextRule解析时需要依赖栈结构(栈顶作为child,栈顶下一个元素作为parent),那么Server作为最顶层标签而且又设置了SetNextRule规则,它要怎么找到它的父节点呢?其实这一步是在代码中完成的,代码在解析server.xml之前先把Catalina实例push进了栈顶,这样在执行Server的SetNextRule时就可以调用Catalina的setServer把Server对象注入到Catalina的server变量中了。
// Catalina.java
public void load() {
...
// 将当前实例设置到解析器栈顶
digester.push(this);
...
}
事实上tomcat用控制反转并不单单在加载四大组件上,通过源码我们可以发现在加载org.apache.catalina.mbeans.GlobalResourcesLifecycleListener时会再次创建一个disgester,这个disgester是配合mbeans一起使用的,从注释来看作用是为了支持JNDI,但原理和上面说的类似,预设好一些Rule然后配合classloader实例化出对象。
// MBeanUtils.java
public static synchronized Registry createRegistry() {
if (registry == null) {
registry = Registry.getRegistry(null, null);
ClassLoader cl = MBeanUtils.class.getClassLoader();
// 下面路径的mbeans-descriptors.xml都会被加载
registry.loadDescriptors("org.apache.catalina.mbeans", cl);
registry.loadDescriptors("org.apache.catalina.authenticator", cl);
registry.loadDescriptors("org.apache.catalina.core", cl);
registry.loadDescriptors("org.apache.catalina", cl);
registry.loadDescriptors("org.apache.catalina.deploy", cl);
registry.loadDescriptors("org.apache.catalina.loader", cl);
registry.loadDescriptors("org.apache.catalina.realm", cl);
registry.loadDescriptors("org.apache.catalina.session", cl);
registry.loadDescriptors("org.apache.catalina.startup", cl);
registry.loadDescriptors("org.apache.catalina.users", cl);
registry.loadDescriptors("org.apache.catalina.ha", cl);
registry.loadDescriptors("org.apache.catalina.connector", cl);
registry.loadDescriptors("org.apache.catalina.valves", cl);
registry.loadDescriptors("org.apache.catalina.storeconfig", cl);
registry.loadDescriptors("org.apache.tomcat.util.descriptor.web", cl);
}
return registry;
}
// Registry.java
public void loadDescriptors(String packageName, ClassLoader classLoader) {
String res = packageName.replace('.', '/');
if (log.isTraceEnabled()) {
log.trace("Finding descriptor " + res);
}
if (searchedPaths.get(packageName) != null) {
return;
}
// 规则描述文件mbeans-descriptors.xml
String descriptors = res + "/mbeans-descriptors.xml";
URL dURL = classLoader.getResource(descriptors);
if (dURL == null) {
return;
}
log.debug("Found " + dURL);
searchedPaths.put(packageName, dURL);
try {
load("MbeansDescriptorsDigesterSource", dURL, null);
} catch(Exception ex ) {
log.error("Error loading " + dURL);
}
}
总结:通过源码我们可以观察到StandardServer、StandardService、StandardConnector及ContainerBase都继承自LifecycleMBeanBase,所以他们实例化的方式其实都和Server大径相同。也就是四大件都是以控制反转的方式(通过解析server.xml)创建,server.xml在解析时层级结构会以栈的形式存储在Digester的stack上,这样遇到SetNextRule规则时就可以通过父子层级关系注入成员变量。