前言
对最前沿技术痴迷的朋友们一定有听过Spring Boot和Docker,当Spring Boot的易配置、易部署,与Docker的虚拟化容器相结合时,一定会让欣赏DevOps的开发伙伴如沐春风。
今天的博客会带领刚刚接触这两门技术的朋友们一步一步地搭建第一个Spring Boot应用,并将其运行在你的Docker容器中。
准备阶段
给macOS同伴们的Tips:
1. 便捷的安装最新版的Gradle(2017.7.7发布的4.0.1版本):
借助Homebrew安装时使用$ brew update
会一直等待无响应,原因在于git镜像在倒车的高速上不能访问,可通过切换至中科大的镜像来解决
2. macOS 10.10以上的版本可以直接通过Docker for Mac
来安装,相比于以前的Toolbox方式有了极大的便利
(以下路径创建指令适用于包括macOS在内的*nix系统,Windows下可直接手动创建文件夹,其余核心指令均共同适用)
从零开始创建第一个Spring Boot项目
Gradle环境准备
如果想省去此小节的配置步骤,可到我的GitHub上clone初始版本环境直接进入下一节
编写Spring Boot应用
:
在你选定的目录创建项目文件夹:
$ mkdir SpringBoot_Docker
$ cd SpringBoot_Docker/
构造Java类路径:
$ mkdir -p src/main/java/hello
在项目根目录下(即与src/平级)路径中用文本编辑器创建并保存命名为build.gradle
的构建文件,其内容如下:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-spring-boot-docker'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
Spring Boot中的gradle插件提供了:
1. 自动收集classpath上的所有jar包,并且构建一个可运行的über-jar
2. 搜索public static void main()
主方法并标记为可运行类
3. 提供内置的依赖解析器,其可以将版本号设置为与Spring Boot依赖关系相比配
在项目根目录下创建编译脚本文件:
- 对于*nix用户:
$ touch gradlew
$ chmod u+x gradlew
- 对于Windows用户:
$ touch gradlew.bat
将gradlew(.bat)文件通过文本编辑器修改至内容如下:(为项目的编译配置,可不做过多了解)(因此请尽量保持与我的路径及命名创建方式相同)
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
在项目根路径下创建gradle/wrapper文件夹,并下载gradle-wrapper.jar放在gradle/wrapper/下,同时,在gradle/wrapper/使用文本编辑器创建gradle-wrapper.properties文件,其内容如下:
#Mon Aug 29 13:09:43 CDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip
至此,项目环境配置完毕。
编写Spring Boot应用
使用文本编辑器创建入口类并命名为Application.java,保存至(projectpath)/src/main/java/hello/下:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class Application {
@RequestMapping("/")
public String home() {
return "Hello SpringBoot from Docker!";
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
仔细观察类前的标记:
- @SpringBootApplication与@RestChontroller共用,是Spring MVC准备好处理Web请求
- @RequestMapping将/映射到发送’Hello SpringBoot from Docker!’应答的home()方法
main()方法实用Spring Boot中的SpringApplication.run()来发布这个应用。
在本机运行
打包并运行我们的项目:
$ ./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar
等待控制台成功输出:
打开浏览器访问localhost:8080
,可以看到有输出Hello SpringBoot from Docker!
,证明项目一切顺利喽!
虚拟化至Docker
有成就感的来喽!
第一步,也是最关键的,当然是创建Dockerfile了!
在src/main/下(即与java/平级)新建文件夹命名为”docker”,并使用文本编辑器创建名为Dockerfile(无后缀)的文件保存至src/main/docker/下,修改其内容如下:
FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ADD gs-spring-boot-docker-0.1.0.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
在这其中,我们将项目刚刚打好的jar包作为”app.jar”ADD
到Docker容器中,并执行ENTRYPOINT
。
在创建镜像之前,我们还需要修改一下build.gradle至如下:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath('org.springframework.boot:spring-boot-gradle-plugin:1.5.3.RELEASE')
// tag::build[]
classpath('se.transmode.gradle:gradle-docker:1.2')
// end::build[]
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
// tag::plugin[]
apply plugin: 'docker'
// end::plugin[]
// This is used as the docker image prefix (org)
group = 'xxxxxx' // 请修改此处成你的Docker账户ID
jar {
baseName = 'gs-spring-boot-docker'
version = '0.1.0'
}
// tag::task[]
task buildDocker(type: Docker, dependsOn: build) {
push = true
applicationName = jar.baseName
dockerfile = file('src/main/docker/Dockerfile')
doFirst {
copy {
from jar
into stageDir
}
}
}
// end::task[]
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
一切准备就绪,开始创建Docker的镜像:
$ ./gradlew build buildDocker
等待生成成功:
这里重要的是找到如图第一行的镜像编号,在我这里是610e64655e33
。
创建Docker容器:
docker run -p 8080:8080 -t 610e64655e33
成功启动后,再次用浏览器访问localhost:8080
,同样看到我们希望的输出,而这次,它来自Docker!我们成功了!
这篇博客改编自Spring Boot官网文档,附上链接供朋友们参考:https://spring.io/guides/gs/spring-boot-docker/