spring-boot-run 指令是怎么运行 Spring Boot 项目的?

简介: 初学 Spring Boot 的时候,按照官方文档,都是建立了一个项目之后,然后执行 mvn spring-boot:run 就能把这个项目运行起来。

初学 Spring Boot 的时候,按照官方文档,都是建立了一个项目之后,然后执行 mvn spring-boot:run 就能把这个项目运行起来。


我就很好奇这个指令到底做了什么,以及为什么项目里包含了 main 方法的那个class,要加一个 @SpringBootApplication  的注解呢?


为什么加了这个注解 @SpringBootApplication 之后,mvn spring-boot:run 指令就能找到这个class并执行它的main方法呢?


首先我注意到,用maven新建的spring boot项目,pom.xml 里面有这么一条配置:

<build>  
    <plugins>  
        <plugin>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-maven-plugin</artifactId>  
        </plugin>  
    </plugins>  
</build>      

看来mvn spring-boot:run 指令应该就是这个插件提供的。


由于不懂maven插件的开发机制,看不太懂,于是去找了下 maven 的插件开发文档:


http://maven.apache.org/guides/plugin/guide-java-plugin-development.html


根据官方的文档,一个 maven 插件会有很多个目标,每个目标就是一个 Mojo 类,比如 mvn spring-boot:run 这个指令,spring-boot这部分是一个maven插件,run这部分是一个maven的目标,或者指令。


根据maven插件的开发文档,定位到 spring-boot-maven-plugin 项目里的RunMojo.java,就是mvn spring-boot:run 这个指令所运行的java代码。


关键方法有两个,一个是 runWithForkedJvm,一个是runWithMavenJvm,如果pom.xml是如上述配置,则运行的是 runWithForkedJvm,如果pom.xml里的配置如下,则运行runWithMavenJvm:


<build>  
    <plugins>  
        <plugin>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-maven-plugin</artifactId>  
            <configuration>  
                <fork>false</fork>  
            </configuration>  
        </plugin>  
    </plugins>  
</build>      

runWithForkedJvmrunWithMavenJvm 的区别,在于前者是起一个进程来运行当前项目,后者是起一个线程来运行当前项目。

我首先了解的是 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);    
  }    
}  

根据这段代码,RunProcess是由spring-boot-loader-tools 这个项目提供的,需要提供的workingDirectory 就是项目编译后的 *.class 文件所在的目录,environmentVariables 就是解析到的环境变量,args里,对于spring-boot的那些sample项目,主要是main方法所在的类名,以及引用的相关类库的路径。


workingDirectory 可以由maven的 ${project} 变量快速获得,因此这里的关键就是main方法所在的类是怎么找到的,以及引用的相关类库的路径是如何获得的。


找main方法所在的类的实现是在 AbstractRunMojo.java 里面:

mainClass = MainClassFinder.findSingleMainClass(this.classesDirectory, SPRING\_BOOT\_APPLICATION\_CLASS\_NAME);  

MainClassFinder.java 是由spring-boot-loader-tools提供的,找到main方法所在的类主要是如下的代码:

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;  
}  

这里的核心就是利用spring的asm框架,读取class文件的字节码并分析,找到含有main方法的类,然后再判断这个类有没有使用了 @SpringBootApplication 注解,有的话,就属于要执行的代码文件了。


如果项目里面有多个含有main方法且被 @SpringBootApplication 注解的类的话,我看代码应该是直接选择找到的第一个开运行。


读取依赖的库路径,在spring-boot-maven-plugin里有大量的代码来实现,还是利用maven本身的特性实现的。


根据了解到的这些信息,我新建了一个普通的java项目bootexp,用一段简单的代码来运行起一个spring boot项目:

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("相关库路径")  
        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();  
        }  
    }  
}  

相关库的路径获取,都是spring-boot-maven-plugin这个项目里面的私有方法,所以我这里直接在 bootsample 这个spring boot项目下执行 mvn spring-boot:run -X, 输出classpath,把classpath复制过来即可。执行bootexp这个项目,即可运行起 bootsample 这个spring boot项目了。


所以为什么spring boot的项目,main方法所在的类都要加上注解 @SpringBootApplication 这个疑问也得到了解决。


综上,mvn spring-boot:run 这个指令为什么能运行起一个spring boot项目就没有那么神秘了,这里主要的难点就两个,一个是maven插件的开发,获得项目的配置信息,执行起指令;一个是类加载机制,以及注解分析。


相关文章
|
20天前
|
XML Java 应用服务中间件
SpringBoot项目打war包流程
本文介绍了将Spring Boot项目改造为WAR包并部署到外部Tomcat服务器的步骤。主要内容包括:1) 修改pom.xml中的打包方式为WAR;2) 排除Spring Boot内置的Tomcat依赖;3) 添加Servlet API依赖;4) 改造启动类以支持WAR部署;5) 打包和部署。通过这些步骤,可以轻松地将Spring Boot应用转换为适合外部Tomcat服务器的WAR包。
112 64
SpringBoot项目打war包流程
|
23天前
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
106 36
|
15天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
35 6
|
15天前
|
自然语言处理 IDE Java
SpringBoot start.aliyun.com创建项目,解决properties乱码的问题
通过确保文件和开发环境的编码一致,配置 Maven 编码,设置 Spring Boot 应用和嵌入式服务器的编码,可以有效解决 properties 文件的乱码问题。以上步骤可以帮助开发者确保在 Spring Boot 项目中正确处理和显示多语言字符,避免因编码问题导致的乱码现象。
30 5
|
28天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
261 12
|
27天前
|
负载均衡 IDE Java
SpringBoot整合XXL-JOB【04】- 以GLUE模式运行与执行器负载均衡策略
在本节中,我们将介绍XXL-JOB的GLUE模式和集群模式下的路由策略。GLUE模式允许直接在线上改造方法为定时任务,无需重新部署。通过一个测试方法,展示了如何在调度中心配置并使用GLUE模式执行定时任务。接着,我们探讨了多实例环境下的负载均衡策略,确保任务不会重复执行,并可通过修改路由策略(如轮训)实现任务在多个实例间的均衡分配。最后,总结了GLUE模式和负载均衡策略的应用,帮助读者更深入理解XXL-JOB的使用。
34 9
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
76 8
|
2月前
|
缓存 前端开发 Java
【Spring】——SpringBoot项目创建
SpringBoot项目创建,SpringBootApplication启动类,target文件,web服务器,tomcat,访问服务器
|
3月前
|
监控 Java 数据库连接
详解Spring Batch:在Spring Boot中实现高效批处理
详解Spring Batch:在Spring Boot中实现高效批处理
441 12
|
3月前
|
安全 Java 测试技术
详解Spring Profiles:在Spring Boot中实现环境配置管理
详解Spring Profiles:在Spring Boot中实现环境配置管理
144 10