第9章 Spring Boot开发者工具
Spring Boot为Maven和Gradle提供构建工具插件。
9.1 Spring Boot maven plugin
Spring Boot Maven Plugin,提供了使用Maven构建Spring Boot 工程的支持。我们可以用这个插件完成打包功能。支持打可执行jar包, war包。该插件支持Maven 3.2 +版本。
使用方式如下:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!-- ... -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.3.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在我们的工程pom.xml文件里面配置上述代码即可。
Spring Boot Maven Plugin提供的goals 如下:
- repackage: 创建自动可执行的jar包或war包。
- run: 运行你的Spring Boot 应用,可以配置一些options,参数parameters.
- start: 管理Spring Boot应用的构建生命周期,默认绑定集成测试阶段的pre-integration-test
- stop : 管理Spring Boot应用的构建生命周期,默认绑定集成测试阶段的post-integration-test
- build-info: 生成Actuator的构建信息。
对应的命令如下:
mvn spring-boot:repackage
mvn spring-boot:run
mvn spring-boot:start
mvn spring-boot:stop
mvn spring-boot:build-info
9.2 Spring Boot gradle plugin
Spring Boot Gradle Plugin 提供了使用Gradl构建Spring Boot 应用的支持。同样支持打可执行 jar包或war包。运行 Spring Boot应用时,使用的是spring-boot-dependencies提供的依赖管理。
使用示例:
plugins {
id 'org.springframework.boot' version '1.5.3.RELEASE'
}
在你的build.gradle配置文件添加上述配置即可。这个看起来,比使用maven的plugins要简洁多了。这里也是groovy的DSL。
使用上面的配置,Spring Boot Gradle Plugin会完成使用spring-boot-starter-parent bom加载依赖的工作。
然后,我们在dependencies里面直接像下面这样使用
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.thymeleaf:thymeleaf-spring4")
compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
}
如果你想要打war,加上下面这句
apply plugin: 'war'
运行命令:
gradle bootRun
9.3 Spring Boot热部署:spring-boot-devtools
spring-boot-devtools 是一个为开发者服务的一个模块,其中最重要的功能就是热部署。
当我们修改了classpath下的文件(包括类文件、属性文件、页面等)时,会重新启动应用(由于其采用的双类加载器机制,这个启动会非常快,另外也可以选择使用jrebel)。
spring-boot-devtools使用了两个类加载器来实现重启(restart)机制:
base类加载器(base ClassLoader), restart类加载器(restart ClassLoader)。
- base ClassLoader:用于加载不会改变的jar(eg.第三方依赖的jar)
- restart ClassLoader:用于加载我们正在开发的jar(eg.整个项目里我们自己编写的类)。当应用重启后,原先的restart ClassLoader被丢掉、重新new一个restart ClassLoader来加载这些修改过的东西,而base ClassLoader却不需要动一下。这就是devtools重启速度快的原因。
使用devtools ,只需要添加其依赖即可 :
Maven
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Gradle.
dependencies {
compile("org.springframework.boot:spring-boot-devtools")
}
devtools的功能在命令行运行jar包
java -jar XXX.jar
或者,当应用运行在指定的 classloader的时候, 自动失效(考虑到,或许是在生产环境)。
DevTools通过检测classpath的资源文件 resources的变动来触发应用的重启。这个跟我们在 IntelliJ IDEA中, 使用Build -> Make Project,重新构建工程的效果是一样的。
默认情况下,
/META-INF/maven,/META-INF/resources,/resources,/static,/templates,/public
这些文件夹下的文件修改不会使应用重启,但是会重新加载(devtools内嵌了一个LiveReload server,当资源发生改变时,浏览器刷新)。
如果想改变默认的设置,可以自己设置不重启的目录:
spring.devtools.restart.exclude=static/**,public/**
这样的话,就只有这两个目录下的文件修改不会导致restart操作了。
如果要在保留默认设置的基础上还要添加其他的排除目录:
spring.devtools.restart.additional-exclude
如果想要使得当非classpath下的文件发生变化时应用得以重启,使用:
spring.devtools.restart.additional-paths
这样devtools就会将该目录列入了监听范围。
在application.properties文件中,关于DevTools的键值如下:
# ----------------------------------------
# DEVTOOLS PROPERTIES
# ----------------------------------------
# DEVTOOLS (DevToolsProperties)
spring.devtools.livereload.enabled=true # Enable a livereload.com compatible server.
spring.devtools.livereload.port=35729 # Server port.
spring.devtools.restart.additional-exclude= # Additional patterns that should be excluded from triggering a full restart.
spring.devtools.restart.additional-paths= # Additional paths to watch for changes.
spring.devtools.restart.enabled=true # Enable automatic restart.
spring.devtools.restart.exclude=META-INF/maven/**,META-INF/resources/**,resources/**,static/**,public/**,templates/**,**/*Test.class,**/*Tests.class,git.properties # Patterns that should be excluded from triggering a full restart.
spring.devtools.restart.poll-interval=1000 # Amount of time (in milliseconds) to wait between polling for classpath changes.
spring.devtools.restart.quiet-period=400 # Amount of quiet time (in milliseconds) required without any classpath changes before a restart is triggered.
spring.devtools.restart.trigger-file= # Name of a specific file that when changed will trigger the restart check. If not specified any classpath file change will trigger the restart.
# REMOTE DEVTOOLS (RemoteDevToolsProperties)
spring.devtools.remote.context-path=/.~~spring-boot!~ # Context path used to handle the remote connection.
spring.devtools.remote.debug.enabled=true # Enable remote debug support.
spring.devtools.remote.debug.local-port=8000 # Local remote debug server port.
spring.devtools.remote.proxy.host= # The host of the proxy to use to connect to the remote application.
spring.devtools.remote.proxy.port= # The port of the proxy to use to connect to the remote application.
spring.devtools.remote.restart.enabled=true # Enable remote restart.
spring.devtools.remote.secret= # A shared secret required to establish a connection (required to enable remote support).
spring.devtools.remote.secret-header-name=X-AUTH-TOKEN # HTTP header used to transfer the shared secret.
另外,使用Intellij的可能会遇到这个问题,即使项目使用了spring-boot-devtools,修改了类或者html、js等,idea还是不会自动重启,非要手动去make一下或者重启,就更没有使用热部署一样。出现这种情况,并不是你的配置问题,其根本原因是因为Intellij IEDA和Eclipse不同,Eclipse设置了自动编译之后,修改类它会自动编译,而IDEA在非RUN或DEBUG情况下才会自动编译(前提是你已经设置了Auto-Compile)。
首先,IDEA设置里面Build project automatically这里打勾
然后 Shift+Ctrl+Alt+/(Mac: Shift+Command+Alt+/),选择Registry
进去之后,找到如下图所示的选项,打勾
OK了,重启一下项目,然后改一下类里面的内容,IDEA就会自动去make了。
笔者在使用maven-scala-plugin + spring-boot-devtools过程中,有个问题这里提一下。在spring-boot-devtools跟maven-scala-plugin一起使用,使用命令行
#!/usr/bin/env bash
mvn clean scala:compile scala:run -Dlauncher=app
启动会报错(直接在IDEA中启动main入口类不报错,这是scala-maven-plugin跟spring-boot-devtools集成兼容性bug)。报错日志如下:
00:52:18.748 [restartedMain] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'delegatingApplicationListener'
00:52:18.748 [restartedMain] DEBUG o.s.boot.devtools.restart.Restarter - Creating new Restarter for thread Thread[main,5,main]
00:52:18.749 [restartedMain] DEBUG o.s.boot.devtools.restart.Restarter - Immediately restarting application
00:52:18.749 [restartedMain] DEBUG o.s.boot.devtools.restart.Restarter - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@1bfafd6
00:52:18.749 [restartedMain] DEBUG o.s.boot.devtools.restart.Restarter - Starting application com.springboot.in.action.LightSwordApplication with URLs [file:/Users/jack/book/lightsword/target/test-classes/, file:/Users/jack/book/lightsword/target/classes/]
00:52:18.751 [restartedMain] INFO scala.App - Started App in 30.547 seconds (JVM running for 31.682)
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org_scala_tools_maven_executions.MainHelper.runMain(MainHelper.java:161)
at org_scala_tools_maven_executions.MainWithArgsInFile.main(MainWithArgsInFile.java:26)
Caused by: org.springframework.boot.devtools.restart.SilentExitExceptionHandler$SilentExitException
at org.springframework.boot.devtools.restart.SilentExitExceptionHandler.exitCurrentThread(SilentExitExceptionHandler.java:90)
at org.springframework.boot.devtools.restart.Restarter.immediateRestart(Restarter.java:183)
at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:162)
at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:545)
at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationStartedEvent(RestartApplicationListener.java:68)
at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationEvent(RestartApplicationListener.java:45)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:122)
at org.springframework.boot.context.event.EventPublishingRunListener.started(EventPublishingRunListener.java:67)
at org.springframework.boot.SpringApplicationRunListeners.started(SpringApplicationRunListeners.java:48)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1187)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1176)
at com.springboot.in.action.LightSwordApplication$.delayedEndpoint$com$springboot$in$action$LightSwordApplication$1(LightSwordApplication.scala:6)
at com.springboot.in.action.LightSwordApplication$delayedInit$body.apply(LightSwordApplication.scala:5)
at scala.Function0.apply$mcV$sp(Function0.scala:34)
at scala.Function0.apply$mcV$sp$(Function0.scala:34)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App.$anonfun$main$1$adapted(App.scala:76)
at scala.App$$Lambda$5/1464642111.apply(Unknown Source)
at scala.collection.immutable.List.foreach(List.scala:389)
at scala.App.main(App.scala:76)
at scala.App.main$(App.scala:74)
at com.springboot.in.action.LightSwordApplication$.main(LightSwordApplication.scala:5)
at com.springboot.in.action.LightSwordApplication.main(LightSwordApplication.scala)
... 6 more
从日志内容,我们可以看出,系统在spring-boot-devtools的Restarter初始化的时候报错了。代码如下
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
// It's too early to use the Spring environment but we should still allow
// users to disable restart using a System property.
String enabled = System.getProperty(ENABLED_PROPERTY);
if (enabled == null || Boolean.parseBoolean(enabled)) {
String[] args = event.getArgs();
DefaultRestartInitializer initializer = new DefaultRestartInitializer();
boolean restartOnInitialize = !AgentReloader.isActive();
Restarter.initialize(args, false, initializer, restartOnInitialize);
}
else {
Restarter.disable();
}
}
报错的代码在if分支
Restarter.initialize(args, false, initializer, restartOnInitialize);
就是说,当我们没有配置spring.devtools.restart.enabled的值,或者值是true的时候,会进来这行代码。
绕过这个错误的解决办法,是配置spring.devtools.restart.enabled的值是false。这样就用不了自动重启应用的功能。
package com.springboot.in.action
import org.springframework.boot.SpringApplication
object LightSwordApplication extends App {
System.setProperty("spring.devtools.restart.enabled", "false")
SpringApplication.run(classOf[AppConfig])
}
由于spring-boot-devtools的实现原理是,在发现代码有更改之后,重新启动应用。它使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为 Restart ClassLoader
, 这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。正是这样的实现机制,导致我们使用scala语言集成SpringBoot开发的时候,一起使用scala-maven-plugin插件跟spring-boot-devtools的时候会报错。
由于对应的各自的语言的maven插件实现原理,比如说scala-maven-plugin:
在应用启动的时候,执行一次如下逻辑C:
先用其编译api scalac, 把scala代码编译成.class文件,然后调用ClassLoader对.class文件进行读写操作, 寻找classpath下面的类, 加载到jvm中。最后在jvm中执行.class字节码。
而后续的scala代码的变动,便没有实时调用到插件的逻辑C,动态编译成.class文件。所以,spring-boot-devtools的在监测动态更新ClassLoader的时候,无法监测到scala代码的更改,也就无法实现自动重启热部署了。要想实现对应的scala集成SpringBoot热部署,需要特殊定制spring-boot-devtools-scala,监测scala代码变更,动态编译scala代码到类路径。这样spring-boot-devtools就能监测到类的动态变更了。
9.4 Spring Boot远程调试
有时会遇到一些问题:开发环境是正常的,而线上环境是有问题,而此时就需要远程调试来定位问题。
使用Spring Boot开发应用程序,支持远程调试。 启动远程调试,按照如下配置即可:
Maven
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<jvmArguments>
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005
</jvmArguments>
</configuration>
</plugin>
Gradle
在build.gradle的bootRun任务里添加jvmArgs属性,如下配置
bootRun {
jvmArgs "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
}
然后,在IDEA中开启远程debug即可。在IDEA中的示例如下图
更多关于spring-boot-devtools的功能与特性,可以参考[4]。
参考资料:
1.http://docs.spring.io/spring-boot/docs/1.5.3.RELEASE/maven-plugin/
2.http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html
3.https://segmentfault.com/a/1190000005369936
4.http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html
5.http://www.cnblogs.com/java-zhao/p/5502398.html
6.http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html#using-boot-devtools-restart-exclude