spring boot debug using maven

初始化本地 git 仓库的脚本

git init --bare spring-boot-debug.gitgit clone spring-boot-debug.gitcd spring-boot-debugmkdir -p src/main/java/com/example/boot/controllermkdir -p src/main/resources### create pom.xml and java files ###touch pom.xmltouch src/main/java/com/example/boot/SpringBootDebugApplication.javatouch src/main/java/com/example/boot/controller/DebugController.javagit add -A :/git commit -am "init spring boot application"git push

spring boot 项目的 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>1.5.9.RELEASE</version>    </parent>    <groupId>com.example.spring</groupId>    <artifactId>spring-boot-debug</artifactId>    <packaging>jar</packaging>    <name>${project.artifactId}</name>    <version>0.0.1-SNAPSHOT</version>    <properties>        <java.version>1.8</java.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-actuator</artifactId>        </dependency>    </dependencies>    <build>        <finalName>${project.artifactId}</finalName>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                <version>${project.parent.version}</version>                <executions>                    <execution>                        <goals>                            <goal>build-info</goal>                            <goal>repackage</goal>                        </goals>                    </execution>                </executions>            </plugin>            <plugin>                <groupId>pl.project13.maven</groupId>                <artifactId>git-commit-id-plugin</artifactId>                <version>2.2.4</version>                <executions>                    <execution>                        <id>get-the-git-infos</id>                        <goals>                            <goal>revision</goal>                        </goals>                    </execution>                </executions>                <configuration>                    <generateGitPropertiesFile>true</generateGitPropertiesFile>                    <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>                </configuration>            </plugin>        </plugins>    </build></project>

spring boot 应用程序 SpringBootDebugApplication

package com.example.boot;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;@SpringBootApplicationpublic class SpringBootDebugApplication {    public static void main(String[] args) {        new SpringApplicationBuilder(SpringBootDebugApplication.class).run(args);    }}

测试的 DebugController

1234567891011121314151617181920212223
package com.example.boot.controller;import javax.servlet.http.HttpServletRequest;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class DebugController {    private static final Logger LOGGER = LoggerFactory.getLogger(DebugController.class);    @RequestMapping("index")    public String index(HttpServletRequest httpServletRequest) {        String host = httpServletRequest.getRemoteHost();        int port = httpServletRequest.getRemotePort();        LOGGER.info("add breakpoint at this line, host is {}, and port is {}", host, port);        return host;    }}

mvnDebug spring-boot:run

如下方式运行项目,虽然这里提示在8000端口监听客户端的连接,并且可以使用jdb连接调试,也可以用vscode连接调试,但使用IDEA开发工具Attach to Local Process...时,并没有发现待调试程序在8000端口上运行。

 mvnDebug spring-boot:runPreparing to execute Maven in debug modeListening for transport dt_socket at address: 8000

jdb attach

jdb是基于命令行的工具,设置好断点,再访问http://localhost:8080/index页面,就会进入断点。

 jdb -attach 8000Set uncaught java.lang.ThrowableSet deferred uncaught java.lang.ThrowableInitializing jdb ...>VM Started: No frames on the current call stack  main[1] run  > stop at com.example.boot.controller.DebugController:19Set breakpoint com.example.boot.controller.DebugController:19Breakpoint hit: "thread=http-nio-8080-exec-2", com.example.boot.controller.DebugController.index(), line=19 bci=14  http-nio-8080-exec-2[1] print port port = 60249

vscode attach

vscode中的launch.json内容如下,在DebugController第19行打上断点,按F5运行程序,访问http://localhost:8080/index即可进入断点。

{    "version": "0.2.0",    "configurations": [        {            "type": "java",            "name": "Debug (Attach) SpringBootDebugApplication",            "request": "attach",            "hostName": "localhost",            "port": 8000        }    ]}

mvnDebug exec:java -Dstart-class="..."

 mvnDebug exec:java -Dstart-class="com.example.boot.SpringBootDebugApplication"Preparing to execute Maven in debug modeListening for transport dt_socket at address: 8000

与前面一样,在jdbvscode中是可以连接调试的,在IDEA中不行。

mvn spring-boot:run -Drun.jvmArguments="-agentlib:jdwp=..."

官方文档推荐使用插件spring-boot-maven-plugin的目标spring-boot:run运行spring-boot应用,如果有设置jvmArguments参数,就会自动派生新的 Java 进程来运行spring-boot中的main方法,所以可以用-Drun.jvmArguments把参数传给派生的 Java 进程,如下示例。

 mvn spring-boot:run -Drun.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000"[INFO] Scanning for projects...[INFO][INFO] ----------------< com.example.spring:spring-boot-debug >----------------[INFO] Building spring-boot-debug 0.0.1-SNAPSHOT[INFO] --------------------------------[ jar ]---------------------------------[INFO][INFO] >>> spring-boot-maven-plugin:1.5.9.RELEASE:run (default-cli) > test-compile @ spring-boot-debug >>>[INFO][INFO] --- spring-boot-maven-plugin:1.5.9.RELEASE:build-info (default) @ spring-boot-debug ---[INFO][INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ spring-boot-debug ---[INFO] Using 'UTF-8' encoding to copy filtered resources.[INFO] Copying 0 resource[INFO] Copying 0 resource[INFO][INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ spring-boot-debug ---[INFO] Nothing to compile - all classes are up to date[INFO][INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ spring-boot-debug ---[INFO] Using 'UTF-8' encoding to copy filtered resources.[INFO] skip non existing resourceDirectory /test/spring-boot-debug/src/test/resources[INFO][INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ spring-boot-debug ---[INFO] No sources to compile[INFO][INFO] <<< spring-boot-maven-plugin:1.5.9.RELEASE:run (default-cli) < test-compile @ spring-boot-debug <<<[INFO][INFO][INFO] --- spring-boot-maven-plugin:1.5.9.RELEASE:run (default-cli) @ spring-boot-debug ---[INFO] Attaching agents: []Listening for transport dt_socket at address: 8000

程序启动的前一部分[INFO]日志是由主进程输出的,派生的子进程启动spring-boot应用,因为调试参数suspend=y,因此在8000端口等待客户端连接,这时一共启动了2个 Java 进程,如下所示,其中84974这个进程就是84952这个进程fork出来的子进程:

 ps -ef | grep java  501 84952  1177   0 11:15PM ttys001    0:07.06 /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/bin/java -classpath /usr/local/Cellar/maven/3.5.3/libexec/boot/plexus-classworlds-2.5.2.jar -Dclassworlds.conf=/usr/local/Cellar/maven/3.5.3/libexec/bin/m2.conf -Dmaven.home=/usr/local/Cellar/maven/3.5.3/libexec -Dlibrary.jansi.path=/usr/local/Cellar/maven/3.5.3/libexec/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/github.com/test/spring-boot-debug org.codehaus.plexus.classworlds.launcher.Launcher spring-boot:run -Drun.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000  501 84974 84952   0 11:15PM ttys001    0:00.07 /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/bin/java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 -cp /test/spring-boot-debug/target/classes:...:~/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.8.10/jackson-databind-2.8.10.jar com.example.boot.SpringBootDebugApplication

jdb attach

 jdb -attach 8000Set uncaught java.lang.ThrowableSet deferred uncaught java.lang.ThrowableInitializing jdb ...>VM Started: No frames on the current call stack  main[1] stop at com.example.boot.controller.DebugController:19Deferring breakpoint com.example.boot.controller.DebugController:19.It will be set after the class is loaded.  main[1] run> Set deferred breakpoint com.example.boot.controller.DebugController:19Breakpoint hit: "thread=http-nio-8080-exec-2", com.example.boot.controller.DebugController.index(), line=19 bci=14  http-nio-8080-exec-2[1] print host host = "0:0:0:0:0:0:0:1"  http-nio-8080-exec-2[1] print port port = 58781  http-nio-8080-exec-2[1] print httpServletRequest httpServletRequest = "org.apache.catalina.connector.RequestFacade@7d5cdff"  http-nio-8080-exec-2[1] print httpServletRequest.getHeader("user-agent") httpServletRequest.getHeader("user-agent") = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"  http-nio-8080-exec-2[1] cont

使用 IDEA 客户端连接

jdb与 IDE 相比还是不方便的,可以使用 IntelliJ IDEAEclipse 之类的工具进行 debug,vscode的调试功能也很不错,以IDEA为例,IDEA - Run - Attach to Local Process...,可以看到运行中的调试程序,点击连接之后,客户端提示如下,并且主程序会继续启动spring-boot应用。

Connected to the target VM, address: '127.0.0.1:8000', transport: 'socket'

java -cp "..." -agentlib:jdwp="..."

下面用最基本的java命令来启动main方法所在的应用程序进行调试。

pom.xml文件所在目录下运行以下命令得到项目依赖的classpath,并将target/classes目录加入classpath中,然后运行main方法所在应用程序SpringBootDebugApplication

 mvn dependency:build-classpath | grep -v "INFO\|WARN"maven project classpath output here ...... java -cp "target/classes:$(mvn dependency:build-classpath | grep -v "INFO\|WARN")" -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 com.example.boot.SpringBootDebugApplicationListening for transport dt_socket at address: 8000

然后用IDEA连接8000端口后开始调试代码。

spring-boot 2.0 参数变化

spring-boot 2.x版本中,需要注意-Drun.jvmArguments被调整为-Dspring.boot.run.jvmArguments

References

  1. jdb - The Java Debugger
  2. Spring Boot Maven Plugin