第9章 Spring Boot开发者工具

简介: 第9章 Spring Boot开发者工具Spring Boot为Maven和Gradle提供构建工具插件。9.1 Spring Boot maven pluginSpring Boot Maven Plugin,提供了使用Maven构建Spring Boot 工程的支持。

第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

相关文章
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
53 0
|
22天前
|
安全 Java 应用服务中间件
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
29 0
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
|
24天前
|
XML Java C++
【Spring系列】Sping VS Sping Boot区别与联系
【4月更文挑战第2天】Spring系列第一课:Spring Boot 能力介绍及简单实践
【Spring系列】Sping VS Sping Boot区别与联系
|
3月前
|
XML 监控 druid
【Java专题_02】springboot+mybatis+pagehelper分页插件+druid数据源详细教程
【Java专题_02】springboot+mybatis+pagehelper分页插件+druid数据源详细教程
|
4月前
|
开发框架 Java .NET
SpringBoot3中的属性绑定注解和YMAL配置文件、日志
SpringBoot3中的属性绑定注解和YMAL配置文件、日志
|
4月前
|
Java
springboot项目打包瘦身
springboot项目打包瘦身
|
6月前
|
Java 测试技术
Springboot集成JUnit5优雅进行单元测试
Springboot集成JUnit5优雅进行单元测试
|
安全 Java Maven
Spring Boot资源文件问题总结(Spring Boot的静态资源访问,配置文件外置)
Spring Boot资源文件问题总结(Spring Boot的静态资源访问,配置文件外置)
1331 1
|
10月前
|
Java Maven
【Springboot】创建boot工程spring-boot-maven-plugin报红、出错_解决方案
【Springboot】创建boot工程spring-boot-maven-plugin报红、出错_解决方案
322 0
|
10月前
|
SQL druid 前端开发
让SpringBoot不需要Controller、Service、DAO、Mapper,卧槽!这款工具绝了!
让SpringBoot不需要Controller、Service、DAO、Mapper,卧槽!这款工具绝了!