Tomcat8 source parsing
Overall Tomcat Architecture
Connector: Opens the Socket, listens to client requests, and returns response data. Container: handles specific requests;
A Service maintains multiple Connectors and a Container. Requests from the Connector can only be handled by the Container maintained by the Service to which the Service belongs.
Engine: represents the entire servlet Engine. Host: represents a virtual Host. Context: represents an application
Tomcat source code construction
- Tomcat software and source code file download link: tomcat.apache.org/download-80…
- Create a tomcat directory folder to store the downloaded files, and then create a pom.xml file. Then use idea to open the Tomcat directory.
- The pom. XML file is as follows
<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0. 0</modelVersion>
<groupId>com.apache.tomcat</groupId>
<artifactId>tomcat</artifactId>
<version>1.0-SNAPSHOT</version>
<name>tomcat</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.101.</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.62.</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.13. 0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.51.</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.61.</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Copy the code
- The idea Settings are as shown in the figure below:
- Finally start. The access path is http://localhost:8080/
Tomcat source code Analysis
1. The Tomcat initialization phase
# 1.Bootstrap starts the main method of the classpublic static void main(String args[]) {
if (daemon == null) {
// Instantiate BootStrap
Bootstrap bootstrap = new Bootstrap();
try {
// Initialize BootStrap
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")) {
// If the command is start, perform the following operations
daemon.setAwait(true); // In order for Tomcat to block listening to the shutdown command on the shutdown port
daemon.load(args); // Actually call the catalina.load() method to initialize the server, service, engine, executor, connector
daemon.start(); // Actually call catalina.start() to start the server, service, engine, executor, connector; Host,Context,Wrapper
if (null == daemon.getServer()) {
System.exit(1); }}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 instanceofInvocationTargetException && t.getCause() ! =null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1); }} #2.Initialize theBootStrap
public void init(a) throws Exception {
// Initialize the class loader
initClassLoaders();
// The current thread sets the context classloader
Thread.currentThread().setContextClassLoader(catalinaLoader);
// Set the class loader for the security mechanism
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// Load Catalina classClass<? > startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
// Create an instance of Catalina using the constructor of class Catalina
Object startupInstance = startupClass.getConstructor().newInstance();
// Run Catalina's setParentClassLoader method to set the parent 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);
/ / set catalinaDaemon
catalinaDaemon = startupInstance;
}
# 3.Initialize the class loaderprivate void initClassLoaders(a) {
try {
// The parent class loader of commonLoader is set to NULL, breaking the parent delegation mechanism.
commonLoader = createClassLoader("common".null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
// Set the parent class loader for catalinaLoader and sharedLoader to commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1); }} #4.daemon.load(args); Initialization operationprivate 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;
}
// Execute catalina's load() method.
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
Copy the code
The main method of the Bootstrap class is used to start tomcat. 1. Initialize Bootstrap to create catalina objects. Initialize the Tomcat class loader. 2. Call the load method of Bootstrap to initialize tomcat components.
- The catalina.load() method is executed, followed by source trace analysis
/** * will initialize some resources and load conf/server.xml first. * In addition, the load method initializes the Server */
public void load(a) {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
// Initialize the directory
initDirs();
// Initialize the namespace
initNaming();
// Parser to parse the server.xml file
Digester digester = createStartDigester();
// Read the server.xml file
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); }}try {
inputSource.setByteStream(inputStream);
digester.push(this);
// Start parsing the server.xml file (emphasis)digester.parse(inputSource); }}// Set the server Catalina information
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
// Initialize the Server. This init method is the method of the superclass LifecycleBase (emphasis).
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"); }}Copy the code
The catalina.load method does two important things: 1. Parse the server.xml file. 2. Initialize the Server.
- The execution of the getServer().init() method initializes the Server, source analysis
# 1.LifecycleBase init() method@Override
public final synchronized void init(a) throws LifecycleException {
if(! state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); }try {
setStateInternal(LifecycleState.INITIALIZING, null.false);
// Call the initInternal method of the subclass (important)
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); }} #2.StandardServer's initInternal() method@Override
protected void initInternal(a) 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 JMX
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Get the class loader
// 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++) {
// Initialize services (emphasis)services[i].init(); }}Copy the code
Server initializes init() by calling the superclass LifecycleBase init() method and then the subclass Server initInternal() method. Init –> initInternal; services, Connector, engine are initialized in the same way.
- Services [I].init()
# 1.StandardService's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
super.initInternal();
// Initialize engine (emphasis)
if(engine ! =null) {
engine.init();
}
// Initialize the thread pool
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize the Mapper mapping listener
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// Initialize the connector (key)
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 newLifecycleException(message); }}}}Copy the code
From the initialization analysis of the services, you can see that the initialization of the services will initialize the Engine and connector. (executor, mapperListener)
- Engine. Init (), initialize the engine, source analysis
# 1.StandardEngine's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
# 2.ContainerBase initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
# 3.LifecycleMBeanBase's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
if (oname == null) {
mserver = Registry.getRegistry(null.null).getMBeanServer();
oname = register(this, getObjectNameKeyProperties()); }}Copy the code
- Connector.init (), initializing the connector, source analysis
# 1.The connector's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
super.initInternal();
// Initialize the adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Set parseBodyMethodsSet to a default value
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if(protocolHandler.isAprRequired() && ! AprLifecycleListener.isAprAvailable()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceofAbstractHttp11JsseProtocol) { AbstractHttp11JsseProtocol<? > jsseProtocolHandler = (AbstractHttp11JsseProtocol<? >) protocolHandler;if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is availablejsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); }}try {
// initialize a protocolHandler (emphasis)
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); }}Copy the code
From the initialization analysis of the Connector, you can get the initialization of the Connector, which initializes the protocolHandler
- Protocolhandler.init (), initialize the protocolHandler, source analysis
# 1.AbstractProtocol init() method@Override
public void init(a) throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if(oname ! =null) {
Registry.getRegistry(null.null).registerComponent(this, oname, null); }}if (this.domain ! =null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null.null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// Initialize endpoint (key)
endpoint.init();
}
Copy the code
From the initialization analysis of protocolHandler, we can get the initialization of protocolHandler, which will initialize the endpoint
- Endpoint.init (), initialize endpoint, source analysis
# 1.AbstractEndpoint init() methodpublic void init(a) throws Exception {
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain ! =null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\" ");
Registry.getRegistry(null.null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null.null).registerComponent(socketProperties, socketPropertiesOname, null);
for(SSLHostConfig sslHostConfig : findSslHostConfigs()) { registerJmx(sslHostConfig); }}}Copy the code
At this point, the Initialization phase of Tomcat is complete. Using the chain of responsibility pattern, step by step initialization. Component initialization sequence: Server–>Service–>Engine–>Connector–>ProtocolHandler–>Endpoint These are initialized during tomcat’s Start startup phase.