前言
最近在弄一个新的项目,涉及到LDAP服务器,并且前端页面也需要我自己一个人全权负责,之前一直在写服务器端,突然要写页面还是有很大压力的,不过既然要做,就好好做吧。刚好也想好好回顾下SpringMVC相关的知识。而在这之前突然想到之前自己一直都没有好好的去了解Servlet容器的具体实现,这一块基本是我的知识盲区。所以在写前端以及View层之前,我还是先看下我们常用的Servlet容器Tomcat是怎么工作的吧,源码版本是tomcat7.0.82,会在一些地方说明该版本和tomcat8版本之间的实现区别。
tomcat和我们之前接触的许多框架其实都一样,都需要解析相应的配置文件(随着Spring Boot的出现,现在已经可以实现零配置文件配置),而在tocmat中,我们需要解析的配置文件有server.xml和我们用户(程序员)自己编写的web.xml。
今天就先来看看server.xml的解析。
从tomcat的默认启动类Bootstrap说起
我们知道tomcat的默认启动类是位于org,apache.catalina.startup包下的Bootstrap,我们来看看他的main方法。
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
}
}
我们主要需要看这个方法的三个方法
bootstrap.init();
先初始化了真正的启动类Catalina。
demon.load();
然后调用Catalina的load方法
demon.start()
最后调用了Catalina的start方法
所以tomcat启动的过程主要就是调用了Catalina的load方法和start方法
我们先来看看load()方法
load()
/**
* Start a new server instance.
*/
public void load() {
long t1 = System.nanoTime();
// Create and execute our Digester
// 设置了解析server.xml的规则
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//加载server.xml
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
try {
//解析server.xml
//解析server.xml 使用的是digester,这是apache下一个解析xml的工具,可以了解一下,具体的在这就不过多介绍了
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
// Stream redirection
initStreams();
// Start the new server
try {
//调用解析server.xml得到的Server实例的init()方法
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("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
这里并没有吧load()方法的代码全部搬过来,只是把关键部分的代码拿过来分析。
先说下这个方法都干了些什么:load()方法主要做的就是将server.xml解析到对应的Java类中,最后调用Server实例的init()来进行初始化工作。
我们对这个方法里面的一些之前没接触的知识点讲解一下:
configFile()
protected String configFile = "conf/server.xml";
//Globals
public static final String CATALINA_BASE_PROP = "catalina.base";
protected File configFile() {
File file = new File(configFile);
//是否是绝对路径名,很明显不是,无论是在linux上还是在windows上都不是
if (!file.isAbsolute()) {
//调用的是File(String parent, String child)构造方法
//也就是System.getProperty是为了得到configFile的父目录
file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile);
}
return (file);
}
file.isAbsolute()判断是否为绝对路径
isAbsolute
public boolean isAbsolute()测试此抽象路径名是否为绝对路径名。绝对路径名的定义与系统有关。在 UNIX 系统上,如果路径名的前缀是 "/",那么该路径名是绝对路径名。在 Microsoft Windows 系统上,如果路径名的前缀是后跟 "\\" 的盘符,或者是 "\\\\",那么该路径名是绝对路径名。
返回:
System.getProperty()
getProperty
public static String getProperty(String key)获取指定键指示的系统属性。
首先,如果有安全管理器,则用该键作为其参数来调用 checkPropertyAccess 方法。结果可能导致 SecurityException。
如果没有当前系统属性的集合,则首先用与 getProperties 方法相同的方式创建并初始化系统属性的集合。
参数:
key - 系统属性的名称。
返回:
系统属性的字符串值,如果没有带有此键的属性,则返回 null。
抛出:
SecurityException - 如果安全管理器存在并且其 checkPropertyAccess 方法不允许访问指定的系统属性。
NullPointerException - 如果 key 为 null。
IllegalArgumentException - 如果 key 为空。
我们可以看到catalina.base并不是JVM中自带的系统属性,如果要想使用catalina.base,且并没有在程序中显式的调用System.setProperties来设置属性的话,我们需要手动来设置我们自定义的JVM系统属性
使用java -D 配置系统属性。
使用格式是:java -Dkey=value
当我们使用TomcatDebug的时候,我们发现我们的VM arguments(Run–>Run Confgurations“,然后在对话框的右边选择”Arguments”,即可看到)中已经配置了catalina.base,eclipse已经帮我们配置好了相关的JVM系统属性。
-Dcatalina.base="D:\hardworking\.metadata\.plugins\org.eclipse.wst.server.core\tmp0" -Dcatalina.home="D:\apache\apache-tomcat-7.0.56" -Dwtp.deploy="D:\hardworking\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps" -Djava.endorsed.dirs="D:\apache\apache-tomcat-7.0.56\endorsed"
我们通过configFile()成功找到了我们要加载的server.xml文件,接着就是解析这个配置文件了,解析这个配置文件用的是apache的开源组件Digester,关于这个组件的讲解会有专门的博客,这里先不详细说了。我们接着看getServer.init()这是关键。
getServer().init()
当解析完xml后,就要轮到getServer().init()发挥作用了。
首先,我们要找到Server对应的实例。
我们回头看看createStartDigester方法
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class<?>, List<String>> fakeAttributes =
new HashMap<Class<?>, List<String>>();
ArrayList<String> attrs = new ArrayList<String>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
我们看到这里给Server指定的是org.apache.catalina.core.StandardServer,那我们现在把目光集中到这个类的init()方法上。
StandardServer.init()
我们打开StandardServer的源码,你会发现,你并找不到init()方法的足迹,很明显,这个时候如果你不是一个初学者你就应该知道,肯定是StandardServer的父类实现了这个方法,那我们找到StandardServer的定义处,找到他的父类以及一整套继承链,看看到底在哪个祖先类实现了init()方法
public final class StandardServer extends LifecycleMBeanBase implements Server {
public abstract class LifecycleMBeanBase extends LifecycleBase
implements MBeanRegistration {
public abstract class LifecycleBase implements Lifecycle{
最后我们发现在LifecycleBase中实现了init()方法,而且你会发现init()方法在Server接口中并没有定义,而是在他的父接口Lifecycle接口中定义的
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
}
protected abstract void initInternal() throws LifecycleException;
主要就是调用了initInternal()方法,我们发现initInternal在LifecycleBase中是个抽象方法,很明显这里使用了框架设计中最喜欢使用的模板方法模式。那么我们就继续来看initInternal在StandardServer的具体实现。
StandardServer.initInternal()
@Override
protected void initInternal() throws LifecycleException {
//先调用了父类LifecycleMBeanBase的initInternal()
super.initInternal();
//...中间省略了一大坨代码,因为都不是重点呀,重点在下面
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
initInternal方法主要的工作就是调用Service对应的实例的init()方法
Service.init()
首先我们还是看createStartDigester方法,看看Service的实现类是什么
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
给出了实现类是org.apache.catalina.core.StandardService,我们观看源码发现他的继承链和StandardServer是一样的,init()方法也是在LifecycleBase中实现的,所以我们直接看StandardService的initInternal()方法
StandardServer.initInternal()
/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (container != null) {
container.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
我们可以看到该方法对其三个子元素调用了init()
分别是对容器初始化和对线程池初始化以及对链接的初始化。这里先不详细说。
我们直接看start()方法
start()
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
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;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// 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();
}
}
主要操作是:
getServer().start();
public Server getServer() {
return server;
}
我们发现Server接口没有直接定义start()方法,而是他的父接口Lifecycle定义了这一方法。
注意到我们操作的实现类org.apache.catalina.core.StandardServer(Digester解析XML时指定)也没有显示的实现这个方法,我们往他的祖先类中去寻找,终于,在LifecycleBase这个抽象类中找到了start()方法的实现。
public final class StandardServer extends LifecycleMBeanBase implements Server {
public abstract class LifecycleMBeanBase extends LifecycleBase
implements MBeanRegistration {
public abstract class LifecycleBase implements Lifecycle{
@Override
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
//初始化
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
//调用statInternal() start()
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
}
主要就是两个方法:
init();
startInternal();
daga
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
//中间有一大段代码,但是不是我们要关注的重点,感兴趣的同学可以都读一遍源码
...
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
services
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
所以调用的是org.apache.catalina.core.StandardService的init()方法
同样init()方法的实现是在
protected void initInternal() throws LifecycleException {
super.initInternal();
if (container != null) {
container.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
从代码可以看出该方法创建了初始化了线程池和Connector 。
startInternal()
在LifeCycleBase中,这是个抽象方法,具体的实现在StandardServer中
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
start()
这个方法的实现依旧是在LifeCycleBase中,其中还是调用了模板方法startInternal(),具体实现在StandardService中
@Override
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (container != null) {
synchronized (container) {
container.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
该方法主要是启动线程池和连接器。