今日洞见
文章作者来自ThoughtWorks:朱本威。
本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。已经本网协议授权的媒体、网站,在使用时必须注明"内容来源:ThoughtWorks洞见",并指定原文链接,违者本网将依法追究责任。
#百万奖金有奖问答#程序员的什么最值钱?
是他/她们的聪明才智,简洁代码,惊艳的颜值,还是无与伦比的手速,都不是,是宝贵的时间。
如果,你有机会尝试纯前端的web开发,比如:NodeJS FrontEnd。你一定会非常喜欢watch这个功能,可以自动检测文件变化,然后自动完成编译和部署。
#另个一个百万奖金问题# 如果在Java/Spring环境下,也能做到Watch,该有多好!!!
那么,本篇文章不是什么高大上的抽象理论,也不是什么改变行业观念的大道理,从实际出发,就是要给你这个答案,只是希望在这三个环境(Gradle、Spring、Intellij Idea)下开发时,来给你节省一点点的时间。
什么是热部署:
It is the ability to change ON-THE-FLY what's currently deployed without redeploying it. Hot deployment is VERY hot for development.The time savings realized when your developers can simply run their build and have the new code auto-deploy instead of build, shutdown, startup is massive.
解决方案:
针对Spring的版本不同,将实现热部署的解决办法分为两个不同的方案:Spring3下Spring MVC Jetty 和 Spring Boot,最后在给大家分享一个Gradle的Watch插件,来实现watch的功能。
Spring3下Spring MVC Jetty的实现方式
首先来回顾一下Gradle的Jetty插件,Jetty插件提供两个重要方法:jettyRun和jettyRunWar。jettyRun会将一个已暴露(解包的)的web应用部署到嵌入式Jetty Web容器中。它不需要将web应用打包成一个war文件,目的是为了节省部署时间。jettyRunWar正好相反,是将一个War包部署到Web容器中。
jettyRun的好处是,你可以改变静态文件和JSP文件,而不需要重新启动服务器。
但是即便如此,对于日常开发还是不方便,因为开发过程中改动非常多的还有Java文件和资源配置文件,所以真正需要的是热部署。
jettyRun的Gradle API文档中有这么一句话:
Once started, the web container can be configured to run continuously, scanning for changes in the project and automatically performing a hot redeploy when necessary. This allows the developer to concentrate on coding changes to the project using their IDE of choice and have those changes immediately and transparently reflected in the running web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying.
这句话简单总结就是Jetty提供实现热部署的特性,开发人员只需要专注于编写代码,减少重新构建,重新组装和重新部署所浪费的时间。那么如何配置来实现Jetty所提供的热部署呢?
Jetty插件提供了两个属性:
reload:The reload mode, which is either “automatic” or “manual”.
scanIntervalSeconds:The interval in seconds between scanning the web app for file changes. If file changes are detected, the web app is reloaded. Only relevant if reload is set to “automatic”. Defaults to 0, which disables automatic reloading.
读完上面两段,说明默认scanIntervalSeconds的配置是不支持自动重新载入变化文件的。所以,你需要修改Jetty插件的默认配置:
代码语言:javascript复制buildscript {
repositories { jcenter() }
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"
classpath 'org.springframework:springloaded:1.2.4.RELEASE'
}
}
apply plugin: 'idea'
idea {
module {
inheritOutputDirs = false
outputDir = file("$buildDir/classes/main/")
testOutputDir = file("$buildDir/classes/test/")
}
}
然后运行gradle jettyRun启动jetty容器,修改Java类/资源文件(resource下地文件),然后去页面验证变化,结果是没有变化。为什么?
原来,jetty监听的是build目录下的class和resource文件的变化,而不是源代码文件变化,也就说源代码内容改变了,但class文件没有变化,那么不会自动触发jetty重载变化文件,那么该怎么办?
另起一个窗口,手动运行一次gradlew compileJava或者gradlew processResources命令。就是这么简单,你已经实现了Spring 3下SpringMVC Jetty的热部署了。
Spring Boot的实现方式 - Spring Reloaded
现在,越来越多的Spring应用直接使用Spring Boot作为框架,我司也是如此,Spring官方也意识针对热部署问题,提供了解决方案:Spring Reloaded。项目地址:spring-projects/spring-loaded · GitHub
Spring官方也很有意识,专门有一章节来介绍Hot Swapping:78. Hot swapping
它告诉如何将Spring Loaded和Gradle以及IntelliJ结合起来:
默认情况下,IntelliJ将Java类和资源文件编译到一个跟Gradle不同的位置,这会导致Spring Loaded监控失败,所以使用idea模块修改编译输出位置和Gradle一样,而且IntelliJ必须配置跟命令行Gradle任务相同的Java版本,且springloaded必须作为一个buildscript依赖被包含进去。
代码语言:javascript复制buildscript {
repositories { jcenter() }
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"
classpath 'org.springframework:springloaded:1.2.4.RELEASE'
}
}
apply plugin: 'idea'
idea {
module {
inheritOutputDirs = false
outputDir = file("$buildDir/classes/main/")
testOutputDir = file("$buildDir/classes/test/")
}
}
依赖下载完成之后,正常启动Spring Boot Run。
官方文档中存在的问题:
官方文档的springloaded版本是1.2.0.RELEASE,这个版本有问题,会出现:org.springsource.loaded.jvm.JVM : Problems copying method. Incompatible JVM? 报错。而且,如果你自己有仔细阅读官方文档的代码,你会发现官方少了一行testOutputDir的配置。官方文档上没有设置testOutputDir,这就会导致,intellij编译代码时,输出test下面的class到了out目录的main中。从而改变了Gradle默认的输出测试路径(main和test分开的),所以最好还是手动配置一下。
此时,如果你在应用启动的时候修改了Java代码,只需要点击Intellij的编译按钮,重新编译代码即可。
导致集成测试的问题:
由于这样配置之后,导致Intellij上进行make project,输出到build/classes下。这样做会导致集成测试有一个问题,运行集成测试的时候,我们常常需要使用properties文件和xml文件。如果之前运行过gradle build,而build目录下没有被clean,则gradle默认会将properties文件和xml文件放在build/resources下,这与Intellij的行为不同。而运行测试的时候,本来Intellij会默认先跑make project,但是由于build/classes已经有文件了,所以就skip了,于是导致Intellij找不到resource文件,因为Intellij要求的resource路径和gradle构建时输出的路径不同。
解决办法:手动点击Project Rebuild 或者 配置Intellij的Junit
可以手动点击Project Rebuild,它会清理Intellij的输出目录(也就是当前Gradle的classes目录),然后在make。又或者改变Intellij中Junit的配置,Junit会在运行测试之前,先跑make,可以然它在make之前先跑Gradle的clean任务。
Gradle下Watch大法实现方式 - Watch插件
最后,也是最关键的一点,Watch插件,通过上面的方法,实际已经实现热部署,只不过,每次变化都需要手动的触发compile和processResource,已经比重启服务器快了很多。
但这还不够,程序员就应该学会偷懒,我们应该自动化一切可以自动化且应该自动化得东西。
在github上有一个关注度并不是那么多的Watch插件:bluepapa32/gradle-watch-plugin · GitHub
他可以帮助你实现,任何文件变化的监控,并且在监控到变化之后,自动执行相应的Gradle任务。我们以Spring3下SpringMVC Jetty方式为例:
代码语言:javascript复制buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bluepapa32:gradle-watch-plugin:0.1.3'
}
}
apply plugin: 'watch'
watch {
java {
files files('src/main/java')
tasks 'compileJava'
}
resources {
files fileTree(dir: 'src/main/resources', include: '**/*.xml')
tasks 'processResources'
}
}
自动在Java或者resources文件有变化时,执行compileJava和processResources。那么,在Spring Boot下也是类似的一个配置。
自此,你再也不用重复可恶的三套动作以及无限的等待服务器启动:ctrl c -> up key ->enter -> wait,专心实现Feature和修bug。
请让我安静地做一个只用写代码的程序员!