第9章 Spring Boot开发者工具

作者: 光剑书架上的书 | 来源:发表于2017-04-25 23:41 被阅读3310次

    第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

    相关文章

      网友评论

        本文标题:第9章 Spring Boot开发者工具

        本文链接:https://www.haomeiwen.com/subject/zxvbzttx.html