Components that a request passes through in Tomcat
Tomcat processes an HTTP request, and the flow process in each component is shown as the red arrow in the following figure:
How to manage the creation, initialization and calling relationship of components when a system assembles so many components to complete services at one time?
Lifecycle
The system design should find the invariable points and changing points. The invariable place here is that each component should be created, initialized, started and destroyed. These States and the transformation between states are invariable. The change is that each component initialization method is different.
Tomcat abstracts the unchanging places into a Lifecycle interface, and defines some unchanging methods: init, start, stop and destroy. Each component implements specific logic. In the init method of the parent component, the init method of the child component will be called. As long as the init and start methods of the top server component are called, the whole Tomcat component will be started. (composite mode Lifecycle interface)
The life cycle will correspond to one state, LiftcycleState. The state can be used as an event and can be monitored. The state change of a component will trigger the change of sub components. For example, the startup event of the Host container will trigger the scanning and loading (reflection) of Web Applications (observer mode). Finally, the Context container will be created in the Host container. There are two methods in the Lifecycle interface: adding a listener and deleting a listener.
The LifecycleBase abstract class implements the Lifecycle interface and puts some public logic into the base class, such as the transition and of life state, the triggering of life cycle events, etc. the subclass is responsible for its own initialization, start and stop methods (template mode), The implementation of subclasses will add Internal suffixes, such as InitInternal, startInternal, etc.
How to start Tomcat
1.Tomcat is essentially a Java program, so the startup.sh script will start a JVM to run Tomcat's startup class Bootstrap.
2. The main task of bootstrap is to initialize Tomcat's class loader and create Catalina. Why does Tomcat need its own class loader?
3.Catalina is a startup class. It parses server.xml, creates corresponding components, and calls the start method and init method of the Server.
Catalina, as a manager, also handles various exceptions through "hooks", such as how to release resources and brush memory data to disk when tomcat is closed
4. The responsibility of the server component is to manage the Service component, which is responsible for calling the start method of the Service.
5. The service component is responsible for managing the connector and the top-level container Engine, so it will call the start method of the connector and Engine.
1: Bootstrap class
Tomcat is started by calling the main method of Bootstra through startup.sh
1.1: main method:1 public static void main(String args[]) { 2 3 synchronized (daemonLock) { 4 if (daemon == null) { 5 // Don't set daemon until init() has completed 6 Bootstrap bootstrap = new Bootstrap(); 7 try { // 1: Initialization 8 bootstrap.init(); 9 } catch (Throwable t) { 13 } 14 daemon = bootstrap; 15 } 21 } 22 23 try { 24 String command = "start"; 25 if (args.length > 0) { 26 command = args[args.length - 1]; 27 } 28 // 2: Do different actions for the same command 29 if (command.equals("startd")) { 30 args[args.length - 1] = "start"; 31 daemon.load(args); 32 daemon.start(); 33 } else if (command.equals("stopd")) { 34 args[args.length - 1] = "stop"; 35 daemon.stop(); 36 } 63 } 64 }
It mainly initializes init and completes some action instructions load and start
1.2: init method:1 public void init() throws Exception { 2 //1: Class loader 3 initClassLoaders(); 4 5 Thread.currentThread().setContextClassLoader(catalinaLoader); 6 7 SecurityClassLoad.securityClassLoad(catalinaLoader); 8 9 // Load our startup class and call its process() method 10 if (log.isDebugEnabled()) 11 log.debug("Loading startup class"); 12 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 13 Object startupInstance = startupClass.getConstructor().newInstance(); 14 15 // Set the shared extensions class loader 16 if (log.isDebugEnabled()) 17 log.debug("Setting startup class properties"); 18 String methodName = "setParentClassLoader"; 19 Class<?> paramTypes[] = new Class[1]; 20 paramTypes[0] = Class.forName("java.lang.ClassLoader"); 21 Object paramValues[] = new Object[1]; 22 paramValues[0] = sharedLoader;
//2: Instantiate catalina 23 Method method = 24 startupInstance.getClass().getMethod(methodName, paramTypes); 25 method.invoke(startupInstance, paramValues); 26 27 catalinaDaemon = startupInstance; 28 }
1: Initialize the class loader, including common class loader, shared class loader and catalina class loader (Tomcat class loader and Jvm class loader?), in which common class loader is used as the parent class loader
2: Instantiate Catalina object and pass in catalinaClassLoader as a child component of parentClassLoader to isolate catalinaClassLoader from shareClassLoader
1.3: load method:1 private void load(String[] arguments) throws Exception { 2 3 // Call the load() method 4 String methodName = "load"; 5 Method method = 6 catalinaDaemon.getClass().getMethod(methodName, paramTypes); 7 method.invoke(catalinaDaemon, param); 8 }
Call the load method of Catalina class through reflection
1.4: start method:The start method also calls the start method of Catalina class through reflection
2: Catalina
Catalina, as a startup class, parses server.xml, creates corresponding components, and calls the Server start method and init method to complete the startup process of Tomcat
2.1: load method:1 public void load() {
2 // Set configuration source 3 ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile())); 4 File file = configFile(); 5 6 // Create and execute our Digester
// 1: Create components: server, service, ThreadPool, Listener, etc 7 Digester digester = createStartDigester(); 8 // 2: Parsing server.xml 9 try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) { 10 InputStream inputStream = resource.getInputStream(); 11 InputSource inputSource = new InputSource(resource.getURI().toURL().toString()); 12 inputSource.setByteStream(inputStream); 13 digester.push(this); 14 digester.parse(inputSource); 15 } catch (Exception e) { 16 log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e); 17 if (file.exists() && !file.canRead()) { 18 log.warn(sm.getString("catalina.incorrectPermissions")); 19 } 20 return; 21 } 22 23 getServer().setCatalina(this); 24 getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); 25 getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); 26 27 // Stream redirection 28 initStreams(); 29 30 // Start the new server 31 try {
// 3: Call the init method of the server to initialize the components of Tomcat 32 getServer().init(); 33 } catch (LifecycleException e) { 34 35 } 36 37 }
The core calls the init method of the server to complete the initialization of the server and the following components
2.2: start method:getServer().start();
The start method of server is called to start all components of Tomcat
3: Server class
The init and start methods of the server component finally call the init and start methods of Lifecycle. The implementation of Lifecycle is similar to the init method of LifecycleBase (template mode)
3.1: init method of lifecycle base:1 public final synchronized void init() throws LifecycleException { 2 if (!state.equals(LifecycleState.NEW)) { 3 invalidTransition(Lifecycle.BEFORE_INIT_EVENT); 4 } 5 6 try {
//1: Status change event 7 setStateInternal(LifecycleState.INITIALIZING, null, false);
// 2: Initialization method of server 8 initInternal(); 9 setStateInternal(LifecycleState.INITIALIZED, null, false); 10 } catch (Throwable t) { 11 handleSubClassException(t, "lifecycleBase.initFail", toString()); 12 } 13 }
1: Status change event (observer mode)
2: Call the initInternal method of StandardServer to initialize each component
Look at the second step first. Call the initInternal method of StandardServer to initialize all components. The implementation classes of the following sub containers start with Standard -; see the status change event later (how to register the event and how to notify?)
The whole initialization link is shown as follows:
3.2: initInternal method of StandardServer:
protected void initInternal() throws LifecycleException { super.initInternal(); // Initialize utility executor reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads)); register(utilityExecutor, "type=UtilityExecutor"); 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) { .... } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } }
In addition to basic initialization of the server itself, it mainly initializes the service components (one server can correspond to multiple services)
3.3: startInternal method of StandardServer:The startInternal method is mainly to call the startInternal method of the service, and the sub components will do some special actions
4: Service class
4.1: initInternal method:protected void initInternal() throws LifecycleException { super.initInternal();
//1:engine initialization if (engine != null) { engine.init(); } // 2: Thread pool initialization
for (Executor executor : findExecutors()) { if (executor instanceof JmxEnabled) { ((JmxEnabled) executor).setDomain(getDomain()); } executor.init(); } // 3: Initialize mapper listener mapperListener.init(); // 4: Initialize our defined Connectors synchronized (connectorsLock) { for (Connector connector : connectors) { connector.init(); } } }
The initialization of service is quite lively. It mainly completes four things, just corresponding to the function of service in the design of tomcat architecture
1) Initialization of subcomponents
2) Common thread pool
3) mapper listener request mapping
4) Connector
4.2: initInternal method:The start method is similar to the init method.
So far, the top-level common logic has been completed. The following is divided into independent initialization and startup processes of connector, processor, Mapper and common thread pool.
Starting from the above service initialization, go to the top-level Engine of the Container component initialization (the Container provides services as the public interface of the Container component).
5: Engine class
5.1: initInternal methodprotected void initInternal() throws LifecycleException { getRealm(); super.initInternal(); }
From the code level, the init method calls the init method of ContainerBase, but it doesn't seem to do much. Where is the initialization of the sub container completed? Continue to look
5.2: startInternal methodprotected synchronized void startInternal() throws LifecycleException { // Standard container startup super.startInternal(); }
The startInternal method of ContainerBase is called, which does a lot of things
protected synchronized void startInternal() throws LifecycleException { // 1: Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
Remove the previous part of the code. The core finds the sub components and initializes them with the thread pool (the init method did not initialize, but the thread pool is used here).
private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; }
The start method of this thread calls the start method of LifecycleBase, and finally initializes the init method (the default implementation of Container) and start method of the Host class
6: Host class
6.1: startInternal methodprotected synchronized void startInternal() throws LifecycleException { // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { } super.startInternal(); }
Add value to the Pipeline of the Host and call the startInternal method of the parent class ContainerBase to continuously initialize the child components.