先回顾一下Tomcat的基本知识
Tomcat
百度定义:
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。
开发者:
Tomcat最初是由Sun的软件构架师詹姆斯·邓肯·戴维森开发的
不同版本之间的差异 扩展知识不重要,可以大概了解下
==========================================================
Apache Tomcat 7.x
它在汲取了Tomcat 6.0.x优点的基础上,实现了对于Servlet 3.0、JSP 2.2和EL 2.2等特性的支持。
另外还有
Web应用内存溢出侦测和预防
增强了管理程序和服务器管理程序的安全性
一般 CSRF保护
支持web应用中的外部内容的直接引用
重构 (connectors, lifecycle)及很多核心代码的全面梳理
Apache Tomcat 6.x
在汲取 Tomcat 5.5.x优点的基础上,实现了Servlet 2.5和JSP 2.1等特性的支持。
其他的
· 内存使用优化
· 更大的IO容量
额外的
Apache Tomcat 5.0.x在Apache Tomcat 4.1的基础上做了很多改动,包括:
· 性能优化和减少垃圾回收动作
· 重构程序部署,通过一个可选的独立部署程序,允许在将一个web应用放进产品前验证和编译它
· 基于JMX的服务器全面监视及web程序管理
· 提高Taglibs的支撑能力,包括改进的数据池和tag插件
· 改进平台集成性,包括Windows和Unix
· 基于JMX的嵌入
· 增强的安全管理支撑
· 集成session集群
· 文档扩充
· 重构聚类
============================================================
Tomcat的配置不多讲了,以后有机会再整理下。
catalina.properties配置文件中参数功能
package.access:针对访问某个package内的代码,Java Security Manager机制提供了安全属性package.access,可以指定哪些包需要受到权限保护,会进行checkPackageAccess验证。
package.definition:同package.access,会进行checkPackageDefinition验证。
common.loader:tomcat自身和各web服务都会用到的类
server.loader:tomcat自身会用到的类
shared.loader:各web服务会用到的类
tomcat.util.buf.StringCache.*:字符缓存配置
重点是对Tomcat的分析
Tomcat中最顶层的容器叫Server,代表整个服务器
而最里面的Service包含Connector和Container.也是心脏组件。
Tomcat的结构分析
Tomcat的总体结构图
Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。
Connector用于处理连接相关的事情,并提供socket与request和reponse之间的转化。
Container用于封装和管理Servlet,以及具体的request请求
Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,
而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。例如同时提供http和https连接
Tomcat里的Server由Catalina来管理,它里面有3个方法load,start,stop方法分别管理生命周期,还有await
Tomcat的启动分析
Tomcat正常启动通过bin目录下startup间接调用catalina,并从catalina中首先执行Bootstrap的main方法,之后通过反射启动Catalina中的方法进行tomcat启动(无论是批处理文件和sh文件这部分处理过程都一样)。
Tomcat的入口main方法在BootStrap中,具体处理过程还是使用Catalina完成,目的把入口和管理分离,方便创建多种启动方式
正常情况下Tomacat的启动就是调用BootStrap中的main方法
Bootstrap类实质上是通过反射的方法去执行Catalina中的函数。Catalina的main方法似乎只是用于调试的,本人目前并没有发现有其他调用的地方。
Bootstrap.java启动代码如下:
public static void main(String args[]) {
//先创建了一个daemo
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();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
main方法的逻辑:
1.首先新建一个BootStrap
2.执行了init()方法初始化
3.处理main方法传入的参数,若为空,已默认start执行
在看init方法
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
在init方法中
1.初始化了ClassLoader
2.用ClassLoader创建了Catalina实例,赋给catalinaDaemo变量
3.对命令操作
从main方法中看出来对start命令调用了3个方法setAwait(true) load(args) start() 内部都调用了Catalina相应方法执行 只不过是反射来调用
看setAwait方法
/**
* Set flag.
*/
public void setAwait(boolean await)
throws Exception {
Class> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
通过反射调用了catalina的setAwait方法
再看BootStrap的load方法
/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
通过反射调用了Catalina的load方法
再看BootStrap的start方法
/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
做了对daemoCatiner是否初始化判断了一次,然后在使用Method进行反射调用Catalina的start方法
那肯定要分析Catalina这个类了它是Server的管理类
Catalina的启动分析
/*
* Load using arguments
*/
public void load(String args[]) {
try {
if (arguments(args)) {
load();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
load方法用于加载配置文件,创建并初始化Server;
start用于启动服务器
setAwait方法
Catalina的setAwait 用于设置Server启动后是否进入等待状态 如果为true进入等待
/**
* Use await.
*/
protected boolean await = false;
public void setAwait(boolean b) {
await = b;
}
load方法
/*
* Load using arguments
*/
public void load(String args[]) {
try {
if (arguments(args)) {
load();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
load的无参实现
初始化目录
初始化名称
初始化配置
public void load() {
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
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);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
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 {
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);
}
}
}
简化一下没用的代码片
public void load() {
long t1 = System.nanoTime();
//。。。。。。。。。。。。。。。。。。。。。。此处创建Server的代码,由Digester完成直接省略
// 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("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
//控制台可以看到的启动过程的一行
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
整理一下Catalina的load方法
根据conf/server.xml创建Server对象,并赋值给server属性(具体开源解析操作是开源项目Digester完成的)
然后调用了server的init方法
最后来看下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();
}
}
整理一下从中发现
1.它是调用了server的start启动并且对异常进行了了处理资源关闭和是否等待处理
2.中间还有一部分是注册和关闭钩子直接忽视
3.如果进入了等待状态要进行await和stop其实内部也是调用了Server的await和stop
实现就是个while循环 while结束调用stop
把我心态都搞崩了。。。
最后我们发现了Catalina的启动等待关闭是调用Service的启动关闭
然后我们来看一下
Server的启动过程
了解一下Server接口实现了Lifecycle接口启动关闭等
public interface Server extends Lifecycle {
// ------------------------------------------------------------- Properties
/**
* Return descriptive information about this Server implementation and
* the corresponding version number, in the format
* /
.
*/
public String getInfo();
/**
* Return the global naming resources.
*/
public NamingResources getGlobalNamingResources();
/**
* Set the global naming resources.
*
* @param globalNamingResources The new global naming resources
*/
public void setGlobalNamingResources
(NamingResources globalNamingResources);
/**
* Return the global naming resources context.
*/
public javax.naming.Context getGlobalNamingContext();
/**
* 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 address on which we listen to for shutdown commands.
*/
public String getAddress();
/**
* Set the address on which we listen to for shutdown commands.
*
* @param address The new address
*/
public void setAddress(String address);
/**
* 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);
/**
* Return the parent class loader for this component. If not set, return
* {@link #getCatalina()} {@link Catalina#getParentClassLoader()}. If
* catalina has not been set, return the system class loader.
*/
public ClassLoader getParentClassLoader();
/**
* Set the parent class loader for this server.
*
* @param parent The new parent class loader
*/
public void setParentClassLoader(ClassLoader parent);
/**
* Return the outer Catalina startup/shutdown component if present.
*/
public Catalina getCatalina();
/**
* Set the outer Catalina startup/shutdown component if present.
*/
public void setCatalina(Catalina catalina);
// --------------------------------------------------------- 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
* null
.
*
* @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);
}
其实说白了提供了addService removeService来添加和删除Service,和获取设置端口号地址,是否关闭,获取设置父类加载器
它的init和start方法分别循环调用了Service的start和init方法来启动所有Service
Server的默认实现是StandardServer
final class StandardServer extends LifecycleMBeanBase implements Server
StandardServer继承LifecycleMBeansBase
LifecycleMBeansBase 继承LifecycleBase中定义了start和init方法调用的是initInternal的startInternal方法,都是模板方法
所以就是LifecycleMBeansBase 调用start最后还是会变成调用startInternal 只不过在基类的start多了些状态异常判断,这个自己随便翻翻源码就能看到了。
这也就是Tomcat生命周期的管理方式
首先是
/**
* 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();
// 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
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
我们现在要看的是StandardServer的startInternal方法
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();
}
}
}
循环调用了每一个Service的start来启动
还有一个就是await方法的实现来看一下
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
慢慢分析 逻辑为 可以画张图
首先判断端口号
如果端口号为-2直接退出
如果端口号为-1进入循环,无法通过网络退出,stopAwait标志只有调用了stop方法才能设置为true 所以在-1时,只有外部调用stop方法才能退出循环
否则创建一个监听关闭的ServerSocket 如果收到关闭命令则break退出循环
此处的port和关闭命令是在conf/server.xml中配置Server时设置的 默认为8005 SHUNTDOWN命令 如果不想使用网络命令关闭服务器那么可将端口号设为-1;
分析完了Server的启动过程来分析Service的启动过程
Service的启动过程
Service接口其实也就是规定了获取和设置一些比如名字,服务的方法,而最重要的是它的实现
class StandardService extends LifecycleMBeanBase implements Service
是不是心里有了日了狗的感觉和StandardServer怎么那么相似呢?话不多说
那肯定start调用了startInternal init调用了initInternal 闭着眼睛回答的。
我们为了专注代码分析,现在开始忽略异常处理和日志打印。
直接从initInternal方法来看
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) {
//省略了这里的异常处理,日志打印
connector.init();
}
}
}
快点讲解发现initInternal初始化了Excutors 循环并调用了Connector的init方法
这个Excutor是什么??
看一下startInternal
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) {
//这里省略了日志异常
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
发现它调用了Container的start和循环调容器用了Excutor和Connector的start。其他啥也没干
不知道为啥在老版本里还有个mapperListener是Mapper的监听器,可以监听Connector容器的变化,在tomcat7里没有了
而executors是用在Connectors中管理线程的线程池,在server.xml里注释起来的参考用法
关于Tomcat线程池的使用后续补充。
Tomcat的整个启动流程图如下
分析了半天原来是这样一张图,累死了
今天先分析到这 后续还有。。。。
Tomcat的生命周期管理
Container分析
Pipeliner-Value管道分析
Connector分析
欢迎关注我的个人订阅号,我会推送更好的文章给大家