spring boot quick start
An attractive feature of spring boot is that it can directly package the application into a jar/war, and then the jar/war can be started directly without configuring another Web Server.
Two questions about spring boot
When you first contact spring boot, you usually have these questions
How does spring boot start?
How does spring boot embedded Tomcat work? How are static files, JSPS, and web page templates loaded into?
Let's analyze how spring boot does it.
How spring boot starts when packaged as a single jar
After maven is packaged, two jar files will be generated:
demo-0.0.1-SNAPSHOT.jar demo-0.0.1-SNAPSHOT.jar.original
Where demo-0.0.1-snapshot jar. Original is the package generated by the default Maven jar plugin.
demo-0.0.1-SNAPSHOT.jar is a jar package generated by spring boot maven plug-in, which contains application dependencies and spring boot related classes. It is called fat jar below.
First, check the directory structure of the package packed by spring boot (omit the unimportant ones):
├── META-INF │ ├── MANIFEST.MF ├── application.properties ├── com │ └── example │ └── SpringBootDemoApplication.class ├── lib │ ├── aopalliance-1.0.jar │ ├── spring-beans-4.2.3.RELEASE.jar │ ├── ... └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── JavaAgentDetector.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── MainMethodRunner.class ├── ...
Take a look at these contents in turn.
MANIFEST.MF Manifest-Version: 1.0 Start-Class: com.example.SpringBootDemoApplication Implementation-Vendor-Id: com.example Spring-Boot-Version: 1.3.0.RELEASE Created-By: Apache Maven 3.3.3 Build-Jdk: 1.8.0_60 Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher
You can see that the Main class is org springframework. boot. loader. Jarlauncher, this is the Main function started by jar.
Another start class is com example. Springbootdemoapplication, which is our own Main function.
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
com/example directory
Below this is the application class file.
lib directory
What is stored here is the jar package file that Maven of the application depends on.
Such as spring beans, spring MVC and other jar s.
org/springframework/boot/loader directory
The Spring boot loader is stored below class file.
Concept of Archive
Archive is the archive file, which is a common concept under linux
It is usually a compressed package in tar/zip format
jar is in zip format
In spring boot, the concept of Archive is abstracted.
An archive can be a jar (jar file archive) or an expanded archive. It can be understood as the layer of unified access to resources abstracted from Spring boot.
Demo-0.0.1-snapshot. Above Jar is an Archive, and then demo-0.0.1-snapshot Each jar package under the / lib directory in jar is also an Archive.
public abstract class Archive { public abstract URL getUrl(); public String getMainClass(); public abstract Collection<Entry> getEntries(); public abstract List<Archive> getNestedArchives(EntryFilter filter);
You can see that Archive has its own URL, such as:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/
There is also a getNestedArchives function, which actually returns demo-0.0.1-snapshot The Archive list of jars under jar / lib. Their URL is:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/aopalliance-1.0.jar jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar
JarLauncher
From manifest MF can see that the Main function is JarLauncher. Let's analyze its workflow.
The inheritance structure of JarLauncher class is:
class JarLauncher extends ExecutableArchiveLauncher class ExecutableArchiveLauncher extends Launcher
Take demo-0.0.1-snapshot Jar to create an Archive
JarLauncher first finds its jar, demo-0.0.1-snapshot Jar, and then create an Archive.
The following code shows how to find the loading location of a class:
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)); }
Get the jar under lib / and create a LaunchedURLClassLoader
After JarLauncher has created the Archive, get demo-0.0.1-snapshot.xml through getNestedArchives function All jar files under jar / lib and create them as List.
Note that as mentioned above, Archive has its own URL.
After obtaining the URLs of these archives, you will also obtain a URL [] array, which is used to construct a custom ClassLoader: LaunchedURLClassLoader.
After creating ClassLoader, you can start from manifest Read start class in MF, i.e. com example. Spring bootdemoapplication, and then create a new thread to start the Main function of the application.
/** * Launch the application given the archive file and a fully configured classloader. */ protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Runnable runner = createMainMethodRunner(mainClass, args, classLoader); Thread runnerThread = new Thread(runner); runnerThread.setContextClassLoader(classLoader); runnerThread.setName(Thread.currentThread().getName()); runnerThread.start(); } /** * Create the {@code MainMethodRunner} used to launch the application. */ protected Runnable createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) throws Exception { Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS); Constructor<?> constructor = runnerClass.getConstructor(String.class, String[].class); return (Runnable) constructor.newInstance(mainClass, args); }
LaunchedURLClassLoader
The difference between launched URLClassLoader and ordinary URLClassLoader is that it provides loading from Archive class's ability.
Combined with the getEntries function provided by Archive, you can obtain the resources in Archive. Of course, there are still many details, which will be described below.
spring boot application startup process summary
Here, you can summarize the startup process of Spring Boot application:
1. After the spring boot application is packaged, a fat jar is generated, which contains the jar package that the application depends on and the classes related to Spring boot loader.
2. The start Main function of fat jar is JarLauncher, which is responsible for creating a LaunchedURLClassLoader to load the jar under / lib, and starting the Main function of the application with a new thread.
Details in spring boot loader
Code address: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-loader
Extension of JarFile URL
Spring boot can start with a fat jar. The most important thing is that it implements the loading mode of jar in jar.
For the definition of the original JarFile URL of JDK, please refer to here:
http://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html
The original JarFile URL looks like this:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/
URL of resources in jar package:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/com/example/SpringBootDemoApplication.class
You can see that for the resources in Jar, the definition is'/‘ To separate. The original JarFile URL only supports one '! /'.
Spring boot extends this protocol to support multiple '! /', You can represent the resources of jar in jar and jar in directory.
For example, the following URL represents demo-0.0.1-snapshot Jar the spring-beans-4.2.3.0 in the lib directory of the jar RELEASE. Manifest.jar MF:
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF
Customize URLStreamHandler, extend JarFile and JarURLConnection
When constructing a URL, you can pass a Handler, and the JDK comes with a default Handler class. Applications can register their own Handler to handle custom URLs.
public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException
reference resources:
https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-
Spring boot handles the logic of multiple jars in jars by registering a custom Handler class.
This Handler will internally use SoftReference to cache all opened jarfiles.
When processing URL s like the following, it will cycle '! /' Separator, starting from the top layer, construct demo-0.0.1-snapshot Jar this JarFile, and then construct spring-beans-4.2.3 RELEASE. Jar this JarFile, and then construct a file that points to manifest JarURLConnection of MF.
jar:file:/tmp/target/demo-0.0.1-SNAPSHOT.jar!/lib/spring-beans-4.2.3.RELEASE.jar!/META-INF/MANIFEST.MF
//org.springframework.boot.loader.jar.Handler public class Handler extends URLStreamHandler { private static final String SEPARATOR = "!/"; private static SoftReference<Map<File, JarFile>> rootFileCache; @Override protected URLConnection openConnection(URL url) throws IOException { if (this.jarFile != null) { return new JarURLConnection(url, this.jarFile); } try { return new JarURLConnection(url, getRootJarFileFromUrl(url)); } catch (Exception ex) { return openFallbackConnection(url, ex); } } public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); int separatorIndex = spec.indexOf(SEPARATOR); if (separatorIndex == -1) { throw new MalformedURLException("Jar URL does not contain !/ separator"); } String name = spec.substring(0, separatorIndex); return getRootJarFile(name); }
How does ClassLoader read resources
What capabilities does a ClassLoader need?
Find resources
Read resources
The corresponding API is:
public URL findResource(String name) public InputStream getResourceAsStream(String name)
As mentioned above, when Spring boot constructs LaunchedURLClassLoader, it passes a URL [] array. In the array is the URL of the jar under the lib directory.
How does the JDK or ClassLoader know how to read the contents of a URL?
In fact, the process is like this:
LaunchedURLClassLoader.loadClass
URL.getContent()
URL.openConnection()
Handler.openConnection(URL)
The final call is the getInputStream() function of 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(); }
The whole process from a URL to the final reading of the content in the URL is complex. To sum up:
spring boot registers a Handler to handle the URL of the "jar:" protocol
spring boot extends JarFile and JarURLConnection to handle jar in jar internally
When processing multiple jar in jar URL s, spring boot will cycle through the processing and cache the loaded JarFile
Multiple jars in jars are actually decompressed to a temporary directory for processing. You can refer to the code in JarFileArchive
When obtaining the InputStream of the URL, the JarEntryData in JarFile is finally obtained
There are many details here, only some important points are listed.
Then, how does URLClassLoader get resource?
During the construction of URLClassLoader, there is a URL [] array parameter, which will be used internally to construct a URLClassPath:
URLClassPath ucp = new URLClassPath(urls);
Inside the URLClassPath, a Loader will be constructed for these URLS, and then try to get them one by one when getResource.
If successful, it will be packaged as a Resource as follows.
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, the URL is actually called openConnection(). In this way, the complete chain can be connected.
Note that the code of the urlclasspath class is not included in the JDK. You can see it here http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/misc/URLClassPath.java#506
Launch Spring boot application in IDE / Open Directory
The above only mentioned the process of starting the Spring boot application in a fat jar. Next, we will analyze how the Spring boot is started in the IDE.
In the IDE, the Main function to run directly is to apply its own Main function:
@SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); } }
In fact, starting the Spring boot application in the IDE is the simplest case, because the dependent jars put the ide in the classpath, so the Spring boot is finished directly.
Another case is to start Spring boot in an open directory. The so-called open directory is to unzip the fat jar and start the application directly.
java org.springframework.boot.loader.JarLauncher
At this time, Spring boot will judge whether it is currently in a directory. If so, it will construct an ExplodedArchive (JarFileArchive when it is in jar), and the subsequent startup process is similar to that of fat jar.
Reprint: Analysis of springBoot startup principle