Recently, I have time to optimize the company's open API platform. When I was typing out the jar package, I suddenly realized that I was very skilled in using spring boot before, but I never knew the starting principle of the jar produced by spring boot. Then I unzipped the jar this time and looked at it. It was really different from what I imagined. The following is a complete analysis of the unzipped jar.
The application of spring boot is not posted. The structure of a simpler demo is similar. In addition, the version of spring boot I used is 1.4.1. There is another article on the Internet to analyze the startup of spring boot jar. That should be below 1.4, and the startup method is also very different from the current version.
After mvn clean install, when we look in the target directory, we will find two jar packages, as follows:
xxxx.jarxxx.jar.original
This is attributed to the spring boot plug-in mechanism, which turns an ordinary jar into an executable jar package, and xxx.jar.original is a jar package produced by maven. These can be found in reference to the article on the official spring website, as follows:
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar
The following is part of the directory structure of the jar produced by the spring boot application, most of which are omitted, and only the important parts are displayed.
.├── BOOT-INF│ ├── classes│ ├── application-dev.properties│ │ ├── application-prod.properties│ │ ├── application.properties│ │ │ │ └── com│ │ │ │ └── weibangong│ │ │ └── open│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── open│ │ │ │ │ │ │ │ └── SwaggerConfig.class│ │ │ │ │ │ │ └── SwaggerConfig.class│ │ │ │ │ │ │ └── SwaggerConfig.class│ │ │ │ │ │ │ └── SwaggerConfig.class│ │ │ │ │ ├── oauth2│ │ │ │ ├── controller│ │ │ │ │ ├── AccessTokenController.class│ │ ├── logback-spring.xml│ │ └── static│ ├── css│ │ │ └── guru.css│ │ ├── images│ │ ├── FBcover1200x628.png│ │ └── NewBannerBOOTS_2.png│ └── lib│ ├── accessors-smart-1.1.jar├── META-INF│ ├── MANIFEST.MF│ └── maven│ └── com.weibangong.open│ └── open-server-openapi│ ├── pom.properties│ └── pom.xml└── org └── springframework └── boot └── loader ├── ExecutiveArchiveLauncher$1.class ├── ExecutiveArchiveLauncher.class ├── JarLauncher.class ├── LaunchedURLClassLoader$1.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── archive │ ├── Archive$Entry.class │ ├── Archive$EntryFilter.class │ ├── Archive.class │ ├── ExplodedArchive$1.class │ ├── ExplodedArchive$FileEntry.class │ ├── ExplodedArchive$FileEntryIterator$EntryComparator.class ├── ExplodedArchive$FileEntryIterator.class
In addition to the class we wrote in the application, this jar also has a separate org package. It should be that the spring boot application uses the spring boot plugin to enter this package, which is to enhance the package stage in the MVN life cycle. It is this package that plays a key role in the startup process. In addition, the various dependencies required by the application are entered in the jar and the additional package of spring boot is entered. This jar that can all-in-one is also called fat.jar. Here we will always use fat.jar to replace the name of the jar.
At this time, we will continue to look at the MANIFEST.MF file in META-INF, as follows:
Manifest-Version: 1.0Implementation-Title: open ::server ::openapiImplementation-Version: 1.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: xiaxuanImplementation-Vendor-Id: com.weibangong.openSpring-Boot-Version: 1.4.1.RELEASEImplementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.PropertiesLauncherStart-Class: com.weibangong.open.openapi.SpringBootWebApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Created-By: Apache Maven 3.3.9Build-Jdk: 1.8.0_20Implementation-URL: http://maven.apache.org/open-server-openapi
The main-class specified here is a class file in the package that is separately entered instead of our startup program, and then the MANIFEST.MF file has a separate start-class that specifies the startup program of our application.
First we find the class org.springframework.boot.loader.PropertiesLauncher, where the main method is:
public static void main(String[] args) throws Exception { PropertiesLauncher launcher = new PropertiesLauncher(); args = launcher.getArgs(args); launcher.launch(args);}Check the launch method. This method is found in the parent class Launcher and the parent class launch method is as follows:
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); this.createMainMethodRunner(mainClass, args, classLoader).run(); } protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args); }The launch method finally calls the createMainMethodRunner method, which instantiates the MainMethodRunner object and runs the run method. We go to the MainMethodRunner source code, as follows:
package org.springframework.boot.loader;import java.lang.reflect.Method;public class MainMethodRunner { private final String mainClassName; private final String[] args; public MainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = args == null?null:(String[])args.clone(); } public void run() throws Exception { Class mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", new Class[]{String[].class}); mainMethod.invoke((Object)null, new Object[]{this.args}); }}Checking the run method, it is very easy to run the spring boot jar, and the analysis is basically over.
5. The startup process of the main program
After talking about the start process of jar, let’s talk about the startup and loading process of the main program in the spring boot application. First, let’s look at the main method of the spring boot application.
package cn.com.devh;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.cloud.netflix.feign.EnableFeignClients;/** * Created by xiaxuan on 17/8/25. */@SpringBootApplication@EnableFeignClients@EnableEurekaClientpublic class A1ServiceApplication { public static void main(String[] args) { SpringApplication.run(A1ServiceApplication.class, args); }}Go to the run method in SpringApplication, as follows:
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified source using default settings. * @param source the source to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[] { source }, args); } /** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param sources the sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }Here the instantiation of SpringApplication is the key, we go to the SpringApplication constructor.
/** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param sources the bean sources * @see #run(Object, String[]) * @see #SpringApplication(ResourceLoader, Object...) */ public SpringApplication(Object... sources) { initialize(sources); } private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }DeduceWebEnvironment() in the initialize method here determines whether it is currently started with a web application or a normal jar, as follows:
private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }The WEB_ENVIRONMENT_CLASSES is:
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };As long as any of them does not exist, the current application is started in the form of a normal jar.
Then the setInitializers method initializes all ApplicationContextInitializers,
/** * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring * {@link ApplicationContext}. * @param initializers the initializers to set */ public void setInitializers( Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); this.initializers.addAll(initializers); }setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))**This step initializes all Listeners.
Let's go back to the previous SpringApplication(sources).run(args); and enter the run method, the code is as follows:
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.started(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); context = createAndRefreshContext(listeners, applicationArguments); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, ex); throw new IllegalStateException(ex); } }This step performs context creation createAndRefreshContext(listeners, applicationArguments),
private ConfigurableApplicationContext createAndRefreshContext( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ConfigurableApplicationContext context; // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); if (isWebEnvironment(environment) && !this.webEnvironment) { environment = convertToStandardEnvironment(environment); } if (this.bannerMode != Banner.Mode.OFF) { printBanner(environment); } // Create, load, refresh and run the ApplicationContext context = createApplicationContext(); context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); // Load the sources Set<Object> sources = getSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[sources.size()])); listeners.contextLoaded(context); // Refresh the context refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } return context; }// Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs());This step carries out the configuration and loading of the environment.
if (this.bannerMode != Banner.Mode.OFF) { printBanner(environment); }This step prints the spring boot logo. If you need to change it, add banner.txt and banner.txt to the resource file to change it to the pattern you need.
// Create, load, refresh and run the ApplicationContext context = createApplicationContext();return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)
The creation context really includes what container is created and instantiates the response class, including the creation of EmbeddedServletContainerFactory, whether to choose jetty or tomcat, there is a lot of content, so I will talk about it next time.
if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } }This step is to register the current context, and destroy the container when the kill command is received.
Basically, the start-up analysis is over, but there are still some details that are very time-consuming to tell. This will be discussed in the subsequent blog post, and that’s all for today.
In summary, the startup process of spring boot jar is basically the following steps:
1. When we package maven normally, the spring boot plug-in expands the maven life cycle and imparts the spring boot related package into the jar. This jar contains class files related to the spring boot startup program besides the jars produced by the application.
2. I have seen the startup process of a slightly lower version of spring boot jar before. At that time, I remember that the current thread has a new thread to run the main program, and now it has been changed to directly use reflection to start the main program.
Summarize
The above is the analysis of the principle of spring boot jar introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!