开发可运行于Docker上的Spring Boot应用

本文详细介绍如何结合SpringBoot和Docker技术,从零开始创建并部署一个简单的Web应用。文章涵盖环境搭建、代码编写、项目打包及Docker容器化等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

对最前沿技术痴迷的朋友们一定有听过Spring Boot和Docker,当Spring Boot的易配置、易部署,与Docker的虚拟化容器相结合时,一定会让欣赏DevOps的开发伙伴如沐春风。

今天的博客会带领刚刚接触这两门技术的朋友们一步一步地搭建第一个Spring Boot应用,并将其运行在你的Docker容器中。

准备阶段

  1. 安装Gradle
  2. 安装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应用

https://github.com/JiayangLiu/SpringBoot_Docker.git

在你选定的目录创建项目文件夹:

$ 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依赖关系相比配

在项目根目录下创建编译脚本文件:

  1. 对于*nix用户:
$ touch gradlew
$ chmod u+x gradlew
  1. 对于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

等待控制台成功输出:
output

打开浏览器访问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

等待生成成功:
output

这里重要的是找到如图第一行的镜像编号,在我这里是610e64655e33

创建Docker容器:

docker run -p 8080:8080 -t 610e64655e33

成功启动后,再次用浏览器访问localhost:8080,同样看到我们希望的输出,而这次,它来自Docker!我们成功了!

这篇博客改编自Spring Boot官网文档,附上链接供朋友们参考:https://spring.io/guides/gs/spring-boot-docker/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值