Author: Shahu Wang
https://segmentfault.com/a/1190000021687878
When learning Spring Boot for the first time, according to the official documents, after setting up a project, execute mvn spring-boot:run We can run this project.
I'm curious about what this instruction does and why the class in the project that contains the main method needs to be added @SpringBootApplication What about the comments?
Why is this note added @SpringBootApplication After that, mvn spring-boot:run Instruction can find this class and execute its main method?
First of all, I noticed that maven New spring boot project, pom.xml There is a configuration:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
Looks like mvn spring-boot:run The instruction should be provided by this plug-in.
Because I don't understand maven I couldn't understand the development mechanism of the plug-in, so I went to look for it maven Plug in development documentation for:
http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
According to the official documents, a maven plug-in has many goals. Each goal is a Mojo class, such as mvn spring-boot:run This instruction, spring boot part is a maven plug-in, run part is a maven target, or instruction.
according to maven The development documents of plug-ins are located in the spring boot Maven plugin project RunMojo.java , mvn spring-boot:run The Java code that this instruction runs.
There are two key methods: runwithforked JVM and runwithmaven JVM. If pom.xml If it is configured as above, then it runs runwithforked JVM, if pom.xml If the configuration in is as follows, run runwithmaven JVM:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>false</fork> </configuration> </plugin> </plugins> </build>
The difference between runwithforked JVM and runwithmaven JVM is that the former is to start a process to run the current project, and the latter is to start a thread to run the current project.
The first thing I learned was runWithForkedJvm:
private int forkJvm(File workingDirectory, List<String\\> args, Map<String, String\\> environmentVariables) throws MojoExecutionException { try { RunProcess runProcess = new RunProcess(workingDirectory, new JavaExecutable().toString()); Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess))); return runProcess.run(true, args, environmentVariables); } catch (Exception ex) { throw new MojoExecutionException("Could not exec java", ex); } }
According to this code, RunProcess is provided by the spring boot loader tools project. The working directory to be provided is the directory where the *. Class file of the project is compiled, and the environment variables It is the resolved environment variable, args. For the spring boot sample projects, it is mainly the class name of the main method and the path of the referenced related class library.
Working directory can be quickly obtained by maven's ${project} variable, so the key here is how to find the class where the main method is located and how to obtain the path of the referenced related class library.
The implementation of the class where the main method is located AbstractRunMojo.java Inside:
mainClass = MainClassFinder.findSingleMainClass(this.classesDirectory, SPRING\_BOOT\_APPLICATION\_CLASS\_NAME);
MainClassFinder.java It is provided by spring boot loader tools. The main method is found in the following code:
static <T> T doWithMainClasses(File rootFolder, MainClassCallback<T> callback) throws IOException { if (!rootFolder.exists()) { return null; // nothing to do } if (!rootFolder.isDirectory()) { throw new IllegalArgumentException("Invalid root folder '" + rootFolder + "'"); } String prefix = rootFolder.getAbsolutePath() + "/"; Deque<File> stack = new ArrayDeque<>(); stack.push(rootFolder); while (!stack.isEmpty()) { File file = stack.pop(); if (file.isFile()) { try (InputStream inputStream = new FileInputStream(file)) { ClassDescriptor classDescriptor = createClassDescriptor(inputStream); if (classDescriptor != null && classDescriptor.isMainMethodFound()) { String className = convertToClassName(file.getAbsolutePath(), prefix); T result = callback.doWith(new MainClass(className, classDescriptor.getAnnotationNames())); if (result != null) { return result; } } } } if (file.isDirectory()) { pushAllSorted(stack, file.listFiles(PACKAGE_FOLDER_FILTER)); pushAllSorted(stack, file.listFiles(CLASS_FILE_FILTER)); } } return null; }
The core here is to use spring's asm framework to read the bytecode of the class file and analyze it, find the class containing the main method, and then judge whether the class has been used @SpringBootApplication Annotation, if any, belongs to the code file to be executed.
If there are multiple main methods in the project and @SpringBootApplication As for the annotated class, I think the code should be the first one found directly.
Reading the dependent library path, there is a lot of code in spring boot maven plugin, which is implemented by using the characteristics of maven itself.
According to the information I learned, I created a new common java project, bootexp, to run a spring boot project with a simple piece of code:
package com.shahuwang.bootexp; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.boot.loader.tools.RunProcess; public class Runner { public static void main( String[] args ) throws IOException { String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; File classesDirectory = new File("C:\\share\\bootsample\\target\\classes"); String mainClass = MainClassFinder.findSingleMainClass(classesDirectory, SPRING_BOOT_APPLICATION_CLASS_NAME); RunProcess runProcess = new RunProcess(classesDirectory, new JavaExecutable().toString()); Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess))); List<String> params = new ArrayList<>(); params.add("-cp"); params.add("Related library path") params.add(mainClass); Map<String, String> environmentVariables = new HashMap<>(); runProcess.run(true, params, environmentVariables); } private static final class RunProcessKiller implements Runnable { private final RunProcess runProcess; private RunProcessKiller(RunProcess runProcess) { this.runProcess = runProcess; } @Override public void run() { this.runProcess.kill(); } } }
The path acquisition of related libraries is the private method in the spring boot Maven plugin project, so I directly execute MVN spring under the spring boot project bootsample- boot:run -X , output classpath, and copy the classpath. Execute the bootexp project to run the bootsample spring boot project.
So why spring boot projects and classes where main methods are located should be annotated with @ SpringBootApplication has also been solved.
To sum up, mvn spring-boot:run Why this instruction can run a spring boot project is not so mysterious. Here are two main difficulties. One is maven One is the class loading mechanism and annotation analysis.
WeChat official account: Java technology stack, back in the background: boot, can get my N Spring Boot tutorial, all dry cargo.
Recommend to my blog to read more:
1.Java JVM, collection, multithreading, new features series
2.Spring MVC, Spring Boot, Spring Cloud series tutorials
3.Maven, Git, Eclipse, Intellij IDEA series tools tutorial
4.Latest interview questions of Java, backend, architecture, Alibaba and other large factories
Feel good, don't forget to like + forward!