To start a SpringBoot project, we only need to execute Java - jar XXX The jar command is OK. At this time, we start a web server container, which can access the corresponding interface through the interface address on the browser, but the whole startup process is transparent to us, so it is necessary to understand.
Start front
- java -jar command: this command will find the manifest file in the jar, and then execute the main class configured therein;
- MANIFEST file:
The MANIFEST file in Spring Boot is / meta-inf / MANIFEST MF file, see its contents:
Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Implementation-Title: sdk-api-demo Implementation-Version: 1.0.0 Spring-Boot-Layers-Index: BOOT-INF/layers.idx Start-Class: com.xxx.xxx.XxxApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.6.1 Created-By: Maven Jar Plugin 3.2.0 Main-Class: org.springframework.boot.loader.JarLauncher
The main class we need is configured at the end, so when we execute the java -jar command, we actually execute org. Jar springframework. boot. loader. JarLauncher class. Looking up, there is also a start class parameter configuration. You can see that the value of this parameter configuration is the start class of our application project, so you can guess that there should be a call to XxxApplication class in JarLauncher class.
- JarLauncher class: This is the entry class for the startup of the whole application. It will load all dependent jar packages under / BOOT-INF/lib /, and create a new thread to execute the main method. Look at its source code. Oh, by the way, if you want to see the source code, add dependencies first:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <scope>provided</scope> </dependency>
Source code:
public class JarLauncher extends ExecutableArchiveLauncher { static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } @Override protected boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } return entry.getName().startsWith(BOOT_INF_LIB); } public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } }
In fact, this class has little content. We mainly look at the launch method. The launch method here calls the Launcher#launch method:
protected void launch(String[] args) throws Exception { JarFile.registerUrlProtocolHandler(); ClassLoader classLoader = createClassLoader(getClassPathArchives()); // Look at the getMainClass method here launch(args, getMainClass(), classLoader); }
The getMainClass() method here will read the class path configured by the start class parameter above. ExecutableArchiveLauncher#getMainClass:
@Override protected String getMainClass() throws Exception { // MANIFEST file Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { // Get the value of start class configuration mainClass = manifest.getMainAttributes().getValue("Start-Class"); } if (mainClass == null) { throw new IllegalStateException( "No 'Start-Class' manifest entry specified in " + this); } return mainClass; }
After obtaining the value of the start class configuration, the MainMethodRunner#run method will be accessed through a series of call stacks:
public void run() throws Exception { // mainClassName is the value of start class Class<?> mainClass = Thread.currentThread().getContextClassLoader() .loadClass(this.mainClassName); // Get main method Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); // Use reflection to call the main method of the configuration class mainMethod.invoke(null, new Object[] { this.args }); }
Here, the whole process comes to the familiar xxxapplication In the main (string [] args) method.
From xxxapplication The main (string [] args) method starts
Starting from the main method in the XxxApplication, it is the process of starting the spring container. Oh, it is also necessary to start the web container. From the main method, you can find the call of the SpringApplication#run method along the call stack.
- SpringApplication#run:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); // Look here refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
Ignore the others and look at the call of refreshContext(context), which is the method initialized by the spring container, and then go along the call stack to the SpringApplication#refresh method.
- SpringApplication#refresh method:
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
You see, the AbstractApplicationContext#refresh method is called. You see that the ioc container initialization process is too familiar with this method, and the rest of the process is the content of spring. Oh, there is also the container startup. In the refresh method, there is a call to onRefresh method. This method is a hook method, which is not implemented in spring. The container startup is implemented in this method.
- ReactiveWebServerApplicationContext#onRefresh
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start reactive web server", ex); } }
- ReactiveWebServerApplicationContext#createWebServer
private void createWebServer() { WebServer localServer = this.webServer; // This is mainly for compatibility with external web container startup // If you already have a webServer, you won't start another webServer if (localServer == null) { this.webServer = getWebServerFactory().getWebServer(getHttpHandler()); } initPropertySources(); }
Look at the comments, and then look at the getWebServer method. We generally use tomcat, so let's look at the implementation of Tomcat.
- TomcatReactiveWebServerFactory#getWebServer
@Override public WebServer getWebServer(HttpHandler httpHandler) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler); prepareContext(tomcat.getHost(), servlet); // Look here return new TomcatWebServer(tomcat, getPort() >= 0); }
This method mainly creates a Tomcat object and fills in some attributes. The main creation work is in the new TomcatWebServer. The new TomcatWebServer mainly calls the TomcatWebServer#initialize method. Look at this method directly.
- TomcatWebServer#initialize:
private void initialize() throws WebServerException { TomcatWebServer.logger .info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Look here, tomcat started this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { // Binding class loader ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Start a background thread to run. If the main method runs, the tomcat thread will disappear. startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
This method is the method to start tomcat. Here, tomcat is also started.