Startup principle
1. How SpringBoot was started through the jar package
What did java-jar do
Let's first figure out what the java-jar command does, in oracle Official Web A description of the command was found:
If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.
Translated as
If the -jar option is specified, its parameter is the name of the JAR file that contains the application class and resource files. The startup class must be indicated by the Main-Class manifest header in its source code.
When using the -jar parameter, the following parameter is the jar file name;
The jar file contains class es and resource files;
Main-Class is defined in the manifest file;
The Main-Class source specifies the startup class for the entire application. (in its source code)
Java-jar goes to the manifest file in jar, where it finds the real startup class;
There is this line in the MANIFEST.MF file:
Start-Class: com.xxxx.Application
In the previous official java documentation, only Main-Class was mentioned, not Start-Class.
The Start-Class value is com.xxxx.Application, which is the only class in our java code and only really applies the startup class.
So here's the question: In theory, when you execute a java-jar command, the JarLauncher class is executed, but actually the com.xxxx.Application is executed. What happens? Why?
Java does not provide any standard way to load nested jar files (that is, jar files themselves contained in jars).
Packaging Plugins and Core Methods for Jar Packages
The Spring Boot project's pom.xml file is packaged using the following plug-ins by default:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
After executing the maven clean package, two files are generated:
spring-learn-0.0.1-SNAPSHOT.jar
spring-learn-0.0.1-SNAPSHOT.jar.original
The spring-boot-maven-plugin project exists in the spring-boot-tools directory. Spring-boot-maven-plugin has five goals by default: repackage, run, start, stop, build-info. Repackage is used by default when packaging.
The repackage of spring-boot-maven-plugin is able to package packages generated by MVN packages again as executable packages, and rename packages generated by MVN packages to *.original.
The repackage of spring-boot-maven-plugin calls the execute method of RepackageMojo at the code level, whereas the repackage method is called in the method. The repackage method code and operations are parsed as follows:
private void repackage() throws MojoExecutionException { // jar generated by maven, the final name will be appended with the.original suffix Artifact source = getSourceArtifact(); // The final executable jar is fat jar File target = getTargetFile(); // Get the repackager and repackage the jar generated by maven into an executable jar Repackager repackager = getRepackager(source.getFile()); // Find and filter jar s that project runtime depends on Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // Converting artifacts to libraries Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // Get the Spring Boot startup script LaunchScript launchScript = getLaunchScript(); // Execute repackaging to generate fat jar repackager.repackage(target, libraries, launchScript); }catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } // Update the jar generated by maven to a.original file updateArtifact(source, target, repackager.getBackupFile()); }
After executing the above commands, two files corresponding to the packaged results are generated. The following explores the content and structure of the file.
jar package directory structure
First, let's look at jar's directory structure, which directories and files it contains. Unzip the jar package to see the following structure:
spring-boot-learn-0.0.1-SNAPSHOT ├── META-INF │ └── MANIFEST.MF ├── BOOT-INF │ ├── classes │ │ └── Application Class │ └── lib │ └── Third-party dependency jar └── org └── springframework └── boot └── loader └── springboot Start Program
META-INF Content
In the above directory structure, META-INF records basic information about jar packages, including entry programs, and so on.
Manifest-Version: 1.0
Implementation-Title: spring-learn
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.xxxx.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.5.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
You can see that Main-Class is org.springframework.boot.loader.JarLauncher, which is a jar-started Maine function.
Another Start-Class is com.xxxx.Application, which is the Main function that we apply ourselves to.
Archive concept
Before continuing to understand the underlying concepts and principles, let's look at Archive concepts:
- Archive is the archive file, which is a common concept under linux.
- Usually it's a tar/zip compressed package.
- jar is in zip format.
SpringBoot abstracts the concept of Archive, an Archive that can be jar (JarFileArchive), an ExplodedArchive, and a logical layer for uniform access to resources. The source code for Archive in Spring Boot is as follows:
public interface Archive extends Iterable<Archive.Entry> { // Get the url of the archive URL getUrl() throws MalformedURLException; // Get jar!/META-INF/MANIFEST.MF or [ArchiveDir]/META-INF/MANIFEST.MF Manifest getManifest() throws IOException; // Get jar!/BOOT-INF/lib/*.jar or [ArchiveDir]/BOOT-INF/lib/*.jar List<Archive> getNestedArchives(EntryFilter filter) throws IOException; }
SpringBoot defines an interface for describing resources, which is org.springframework.boot.loader.archive.Archive. The interface has two implementations, org.springframework.boot.loader.archive.ExplodedArchive and org.springframework.boot.loader.archive.JarFileArchive. The former is used to find resources in the folder directory, and the latter is used to find resources in the jar package environment. In SpringBoot packaged fatJar, the latter is used.
JarFile: Encapsulation of a jar package in which each JarFileArchive corresponds to a JarFile. When JarFile is constructed, it parses the internal structure to get the individual files or folders in the jar package that are encapsulated in Entry and stored in JarFileArchive. If Entry is a jar, it resolves to JarFileArchive.
For example, the URL for a JarFileArchive is:
jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/
Its corresponding JarFile is:
/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar
This JarFile has many Entries, such as:
META-INF/
META-INF/MANIFEST.MF
spring/
spring/study/
...
spring/study/executablejar/ExecutableJarApplication.class
lib/spring-boot-starter-1.3.5.RELEASE.jar
lib/spring-boot-1.3.5.RELEASE.jar
...
Some of the URLs within JarFileArchive that depend on jar (SpringBoot uses the org.springframework.boot.loader.jar.Handler processor to process these URLs):
jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/
jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
If a jar package contains jar or a jar package contains a class file from the jar package, it will be used!/ Separated, which can only be handled by org.springframework.boot.loader.jar.Handler, is a URL protocol that extends from within SpringBoot.
JarLauncher
From MANIFEST.MF, you can see that the Main function is JarLauncher. Here's how it works. The inheritance structure of the JarLauncher class is:
class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher
Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.
According to the definition of_, JarLauncher can load jar under internal/BOOT-INF/lib and application class under/BOOT-INF/classes. In fact, JarLauncher implementation is very simple:
public class JarLauncher extends ExecutableArchiveLauncher { public JarLauncher() {} public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } }
Its main entry creates a new JarLauncher and calls the launch method in the parent Launcher to start the program. When creating JarLauncher, the parent Executable ArchiveLauncher finds its jar and creates an archive.
JarLauncher inherits from org.springframework.boot.loader.Executable ArchiveLauncher. The main function of this class's parameterless construction method is to construct the JarFileArchive object of the FatJar where the current main method resides. Let's look at the launch method. This method mainly does two things:
- Construct a JarFileArchive object with FatJar as the file. Get all the resource targets, get their Urls, take these URLs as parameters, and build a URLClassLoader.
- The lassLoader built in the first step loads the business class pointed to by Start-Class in the MANIFEST.MF file and executes the static method main. Then start the whole program.
public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; public ExecutableArchiveLauncher() { try { // Find your jar and create Archive this.archive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); } } } public abstract class Launcher { protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException( "Unable to determine code source archive from " + root); } return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } }
In Launcher's launch method, the archives corresponding to all jar s and/BOOT-INF/classes directories under/BOOT-INF/lib are found through the getNestedArchives method of the archives above. LaunchedURLClassLoader is generated from the URLs of these archives and set as a thread context class loader to start the application.
This executes the main method of our application's main entry class, all application class files can be loaded through / BOOT-INF/classes, and all dependent third-party jar s can be loaded through / BOOT-INF/lib.
URLStreamHandler
URLs are often used to describe resources in java. URLs have a way to open the link java.net.URL#openConnection(). Since URLs are used to represent a wide variety of resources, specific actions to open resources are performed by a subclass of the class java.net.URLStreamHandler. There are different handler implementations depending on the protocol. JDK has a considerable number of handler implementations built in to handle different protocols. Such as jar, file, http, etc. Inside the URL is a static HashTable property that holds the mapping of discovered protocols and handler instances.
There are three ways to get URLStreamHandler:
- Implement the URLStreamHandlerFactory interface by setting the method URL.setURLStreamHandlerFactory. This property is a static property and can only be set once.
- Provide a subclass of URLStreamHandler directly as an input to the construction method of URLs. However, there are fixed specification requirements in the JVM:
---------- The class name of the subclass must be Handler, and the package name of the last level must be the name of the protocol. For example, if you customize the protocol implementation of Http, the class name must be xx.http.Handler; - When the JVM starts, the java.protocol.handler.pkgs system property needs to be set, and if there are multiple implementation classes, they are separated by |. Because when the JVM tries to find the Handler, it gets the package name prefix from this property and eventually uses the package name prefix.protocol name.Handler, uses the Class.forName method to attempt to initialize the class, and if initialization is successful, uses its implementation as the protocol implementation.
To achieve this goal, SpringBoot first supports custom content reading from jar in jar, that is, supports multiple!/ The url path of the separator. SpringBoot customizes the following two aspects:
-
A subclass of java.net.URLStreamHandler, org.springframework.boot.loader.jar.Handler, is implemented. This handler supports identifying multiple!/ Separator and open URLConnection correctly. The open Connection is a SpringBoot customized implementation of org.springframework.boot.loader.jar.JarURLConnection.
-
A subclass of java.net.JarURLConnection, org.springframework.boot.loader.jar.JarURLConnection, is implemented. This link supports multiple!/ Separator, and I have implemented my own method of getting InputStream in this case. SpringBoot customizes a set of tool classes and methods for reading ZipFile in order to get the input stream correctly from org.springframework.boot.loader.jar.JarURLConnection. This part is closely related to the ZIP compression algorithm specification and will not be expanded.
Summary of Spring Boot's Jar Application Startup Process
Summarize the startup process for Spring Boot applications:
-
After the Spring Boot application is packaged, a Fat jar is generated that contains the application-dependent jar packages and the classes associated with the Spring Boot loader.
-
Fat jar's Start Main function is Jar Launcher, which creates a LaunchedURLClassLoader to load the jar under / lib and start the Main function of the application with a new thread.
So how does ClassLoader read resources and what capabilities does it need? The ability to find and read resources. Corresponding API s:
public URL findResource(String name)
public InputStream getResourceAsStream(String name)
When SpringBoot constructed LaunchedURLClassLoader, it passed an array of URLs []. The array contains the URL of the jar under the lib directory.
How does JDK or ClassLoader know how to read into a URL? The process is as follows:
- LaunchedURLClassLoader.loadClass
- URL.getContent()
- URL.openConnection()
- Handler.openConnection(URL)
The final call is the getInputStream() function of the JarURLConnection.
//org.springframework.boot.loader.jar.JarURLConnection @Override public InputStream getInputStream() throws IOException { connect(); if (this.jarEntryName.isEmpty()) { throw new IOException("no entry name specified"); } return this.jarEntryData.getInputStream(); }
From a URL to the content that is ultimately read into the URL, the whole process is more complex. To summarize:
- Spring boot registered a Handler to handle the URL of the protocol "jar:".
- Spring boot extends JarFile and JarURLConnection to handle jar in jar internally.
- When processing URL s for multiple jar in jar s, Spring Boot loops through the process and caches the JarFile that has been loaded.
- For multiple jar in jar s, it is actually unpacked into a temporary directory for processing, referring to the code in JarFileArchive.
- When you get the InputStream of the URL, you end up with JarEntryData in the JarFile.
There are many details, but only the more important steps are listed above. Finally, how does URLClassLoader get Resource? URLClassLoader constructs with a URL[] array parameter, which is used internally to construct a URLClassPath:
URLClassPath ucp = new URLClassPath(urls);
Inside URLClassPath, a Loader is constructed for each of these URLS, and then when getResource, one of these Loaders tries to get it. If successful, wrap it as a Resource as below.
Resource getResource(final String name, boolean check) { final URL url; try { url = new URL(base, ParseUtil.encodePath(name, false)); } catch (MalformedURLException e) { throw new IllegalArgumentException("name"); } final URLConnection uc; try { if (check) { URLClassPath.check(url); } uc = url.openConnection(); InputStream in = uc.getInputStream(); if (uc instanceof JarURLConnection) { /* Need to remember the jar file so it can be closed * in a hurry. */ JarURLConnection juc = (JarURLConnection)uc; jarfile = JarLoader.checkJar(juc.getJarFile()); } } catch (Exception e) { return null; } return new Resource() { public String getName() { return name; } public URL getURL() { return url; } public URL getCodeSourceURL() { return base; } public InputStream getInputStream() throws IOException { return uc.getInputStream(); } public int getContentLength() throws IOException { return uc.getContentLength(); } }; }
As you can see from the code, url.openConnection() is actually called. This allows the entire chain to be connected.
Start Spring boot application in IDE/Open Directory
The process of launching SpringBoot apps in one fat jar mentioned above, so how does Spring boot start in IDE?
In IDE, the Main function that runs directly is the applied Main function:
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
In fact, the easiest way to start a SpringBoot application is in the IDE, because dependent Jar s put the IDE in the classpath, so the Spring boot startup is complete.
Another scenario is to start SpringBoot startup in an open directory. The so-called open directory is to unzip the fat jar and start the application directly.
In this case, Spring boot will determine if it is currently in a directory and if so, construct an ExplodedArchive (JarFileArchive in the jar before), followed by a startup process similar to that of fat jar.
summary
JarLauncher starts fat jar by loading jar files in the BOOT-INF/classes directory and the BOOT-INF/lib directory.
SpringBoot implements the loading of resources in jar in jar by extending JarFile, JarURLConnection, and URLStreamHandler.
SpringBoot implements the loading of class files in jar in jar by extending URLClassLoader - LauncherURLClassLoader.
WarLauncher implements direct startup of war files and startup in web container by loading jar files in WEB-INF/classes directory and WEB-INF/lib and WEB-INF/lib-provider directories.
2. How SpringBoot starts the Spring container
SpringBoot False Monitor Publishing Order:
1.ApplicationStartingEvent is sent at the beginning of the run, but before any processing (except for the registration of listeners and initializers).
2. The Application nEnvironmentPreparedEvent will be sent before the context is created.
3. After preparing the ApplicationContext and calling the ApplicationContextInitializers, the ApplicationContextInitializedEvent is sent.
4. Send ApplicationPreparedEvent after reading the configuration class.
5. The ApplicationStartedEvent will be sent after the context has been refreshed but before any applications and command line runners are called.
6. Subsequently, AvailabilityChangeEvent with LivenessState.CORRECT is sent to indicate that the application is considered active.
7. ApplicationReadyEvent will be sent after any application and command line runs are called.
8. Send ReadabilityState.ACCEPTING_shortly thereafter AvailabilityChangeEvent of TRAFFIC to indicate that the application is ready to process requests.
If an exception occurs at startup, an ApplicationFailedEvent is sent.
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
1. Call SpringApplication.run to start the springboot application
SpringApplication.run(Application.class, args);
2. Start with a custom SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
Create SpringApplication
- new SpringApplication(primarySources)
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // Put the startup class into primarySources this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // Calculate the current web application type (webFlux, servlet) based on the classpath this.webApplicationType = WebApplicationType.deduceFromClasspath(); // Just go to spring.factories to get all the keys: org.springframework.context.ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //Just go to spring.factories to get all the keys: org.springframework.context.ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // Estimate mainApplicationClass from main method this.mainApplicationClass = deduceMainApplicationClass(); }
- org.springframework.context.ApplicationContextInitializer
- org.springframework.context.ApplicationListener
Summary:
-
Get the startup class: Load the ioc container based on the startup class
-
Get web application type
-
spring.factories reads ApplicationContextInitializer for External Extensions, ApplicationListener Extensions for External Extensions, and Class Decoupling (such as Global Profiles, Hot Deployment Plugins)
-
Deduce the class from main
start-up
- run
public ConfigurableApplicationContext run(String... args) { // Used to record current springboot startup time StopWatch stopWatch = new StopWatch(); // Just record the start time stopWatch.start(); // It is an interface to any spring context, so you can receive any ApplicationContext implementation ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // Turn on Headless mode: configureHeadlessProperty(); // Go to spring.factroies to read the components of SpringApplicationRunListener, which are used to publish events or run listeners SpringApplicationRunListeners listeners = getRunListeners(args); // Publish 1.ApplicationStartingEvent event, sent at the beginning of the run listeners.starting(); try { // Instantiate an ApplicationArguments based on command line parameters ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // Pre-initialization environment: read environment variables, read configuration file information (listener-based) ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // Ignore beaninfo's beans configureIgnoreBeanInfo(environment); // Print Banner Banner Banner printedBanner = printBanner(environment); // Create Spring context from webApplicationType context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //Pre-initialize spring context prepareContext(context, environment, listeners, applicationArguments, printedBanner); // Loading the spring ioc container** is important because it is a spring container launched using the AnnotationConfigServletWebServerApplicationContext, so springboot extends it: // Load the automatic configuration class: invokeBeanFactoryPostProcessors, create the servlet container onRefresh 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; }
- prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Creating an Environment from a webApplicationType reads: java environment variables and system environment variables ConfigurableEnvironment environment = getOrCreateEnvironment(); // Read command line parameters into environment variables configureEnvironment(environment, applicationArguments.getSourceArgs()); // Put the configuration information of @PropertieSource first because reading the configuration file @PropertieSource has the lowest priority ConfigurationPropertySources.attach(environment); // The listener that published the Application EnvironmentPreparedEvent read the global configuration file listeners.environmentPrepared(environment); // Bind all configuration information starting with spring.main to SpringApplication bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } //Update PropertySources ConfigurationPropertySources.attach(environment); return environment; }
- prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); // Get the component that read all the ApplicationContextInitializer before you get it, and loop through the initialize method applyInitializers(context); // Published ApplicationContextInitializedEvent listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Get the current spring context beanFactory (responsible for creating beans) ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } // If there are two bean s with duplicate names under Spring, the later reads will overwrite the previous one // Overrides are not allowed here in SpringBoot, which throws an exception when two bean s with duplicate names appear if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Set whether the current spring container will set all bean s to lazy load if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // Read the main boot class and register it as BD, just like our previous register; One meaning (because all configured beans are subsequently resolved based on the configuration class) load(context, sources.toArray(new Object[0])); //4. Send ApplicationPreparedEvent after reading the configuration class. listeners.contextLoaded(context); }
Summary:
-
Initialize SpringApplication to read listener ApplicationContextInitializer from spring.factories.
-
run Run Method
-
Read environment variable configuration information...
-
Create a springApplication context: ServletWebServerApplicationContext
-
Pre-initialization context: Read boot class
-
Call refresh to load ioc container
- Load all auto-configuration classes
- Create a servlet container
ps: In this process springboot calls many listeners to extend
3. Use an external Servlet container
External servlet container
- Server, native installation of tomcat environment variables...
- Deployment: war - Operations - > Tomcat webapp startup.sh startup
- Development: Bind development to local tomcat
- Development, Operations and Maintenance Server Configuration war
Embedded servlet container:
- Deployment: Jar - >operations - java -jar startup
Use:
- Download tomcat service
- Set how the current maven project is packaged
<!--Packaging method defaults to jar--> <packaging>war</packaging>
- Keep tomcat-related dependencies out of the packaging deployment because the external Tomcat server already has these jar packages
<!--Make it not part of a packaged deployment--> <dependency> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> <scope>provided</scope> </dependency>
- In order for it to support springboot, you need to add: to start the springboot application
public class TomcatStartSpringBoot extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } }
- Run in idea
4. External Servlet Container Start SpringBoot Application Principle
tomcat does not actively start springboot applications, so tomcat must start by calling SpringBoot ServletInitializer's SpringApplication Builder.
public class TomcatStartSpringBoot extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder (SpringApplicationBuilder builder) { return builder.sources(Application.class); } }
Official documentation for the servlet 3.0 specification: 8.2.4
What is SPI?
SPI, also known as Service Provider Interface (Service Provider Interface), is a service discovery mechanism that automatically loads the classes defined in the file by locating the file in the META-INF/services folder under the ClassPath path.
Probably: when the servlet container starts, it will find javax.servlet.ServletContainerInitializer in the META-INF/services folder. This file must have a ServletContainerInitializer bound to it. When the servlet container starts, it will find the implementation class of ServletContainerInitializer in the file, which will create an instance of it to call onstartUp
-
@HandlesTypes(WebApplicationInitializer.class).
-
The @HandlesTypes class passed in is of interest to ServletContainerInitializer
-
The container automatically finds the WebApplicationInitializer in the classpath and passes it into the webAppInitializerClasses of the onStartup method
-
Set> webAppInitializerClasses This also includes the TomcatStartSpringBoot defined earlier
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // If the interface is not abstract and has a relationship with WebApplicationInitializer, it will be instantiated if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); // sort AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } }
@Override public void onStartup(ServletContext servletContext) throws ServletException { // Logger initialization is deferred in case an ordered // LogServletContextInitializer is being used this.logger = LogFactory.getLog(getClass()); WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext); if (rootApplicationContext != null) { servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext)); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not " + "return an application context"); } }
- SpringBootServletInitializer
- The TomcatStartSpringBoot previously defined inherits it
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers(new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); // Call configure builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) { application.addPrimarySources(Collections.singleton(getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } application.setRegisterShutdownHook(false); return run(application); }
- When configure is called, you will come to TomcatStartSpringBoot.configure
- Pass the Springboot startup class into builder.source
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); }
//Call SpringApplication application = builder.build(); A SpringApplication will be built based on the incoming Springboot startup class
public SpringApplication build(String... args) { configureAsChildIfNecessary(args); this.application.addPrimarySources(this.sources); return this.application; } // Call return run(application); Will help me start the springboot application
//Call return run(application); Will help me start the springboot application
protected WebApplicationContext run(SpringApplication application) { return (WebApplicationContext) application.run(); }
It's equivalent to our
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
These two implementation classes actually help me create ContextLoaderListener and Dispatcher Servlet
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--Global parameters: spring configuration file--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-core.xml</param-value> </context-param> <!--Front End Scheduler servlet--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--Set the path to the configuration file--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!--Set load on startup--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>