Java 部署:Jenkins Pipeline 构建 Java 项目(自动化)

👋 大家好,欢迎来到我的技术博客!

📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。

🎯 本文将围绕Java部署 这个话题展开,希望能为你带来一些启发或实用的参考。

🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

  • [Java 部署:Jenkins Pipeline 构建 Java 项目(自动化) 🚀](#Java 部署:Jenkins Pipeline 构建 Java 项目(自动化) 🚀)
    • [为什么选择 Jenkins Pipeline?🔧](#为什么选择 Jenkins Pipeline?🔧)
    • [环境准备:搭建 Jenkins 服务器 ⚙️](#环境准备:搭建 Jenkins 服务器 ⚙️)
      • [使用 Docker 快速启动 Jenkins](#使用 Docker 快速启动 Jenkins)
      • 安装必要插件
    • [示例 Java 项目:一个简单的 Spring Boot 应用 🌱](#示例 Java 项目:一个简单的 Spring Boot 应用 🌱)
    • [编写 Jenkins Pipeline:从构建到部署 🧪➡️📦➡️🚀](#编写 Jenkins Pipeline:从构建到部署 🧪➡️📦➡️🚀)
      • [基础 Pipeline 结构](#基础 Pipeline 结构)
    • [完整的声明式 Jenkinsfile 示例 📜](#完整的声明式 Jenkinsfile 示例 📜)
    • [Dockerfile 配置 🐳](#Dockerfile 配置 🐳)
    • [Pipeline 执行流程可视化 📊](#Pipeline 执行流程可视化 📊)
    • [高级技巧:参数化构建与环境隔离 🌐](#高级技巧:参数化构建与环境隔离 🌐)
      • [参数化 Jenkinsfile](#参数化 Jenkinsfile)
    • [错误处理与通知机制 🔔](#错误处理与通知机制 🔔)
      • 邮件通知示例
      • [Slack 通知(更现代的选择)](#Slack 通知(更现代的选择))
    • [性能优化:并行执行与缓存 🚄](#性能优化:并行执行与缓存 🚄)
    • [安全最佳实践 🔒](#安全最佳实践 🔒)
    • [调试与故障排查 🛠️](#调试与故障排查 🛠️)
    • [扩展:多分支 Pipeline 与 Pull Request 集成 🌿](#扩展:多分支 Pipeline 与 Pull Request 集成 🌿)
    • [总结:拥抱自动化,释放生产力 🌈](#总结:拥抱自动化,释放生产力 🌈)

Java 部署:Jenkins Pipeline 构建 Java 项目(自动化) 🚀

在现代软件开发中,持续集成(CI)与持续部署(CD)已成为提升开发效率、保障代码质量、加速产品交付的核心实践。对于 Java 开发者而言,如何高效地将代码从本地开发环境自动构建、测试并部署到目标服务器,是每个团队都必须面对的挑战。而 Jenkins,作为开源领域最流行、功能最强大的自动化服务器之一,凭借其灵活的插件生态和强大的 Pipeline 能力,成为实现 Java 项目自动化部署的首选工具。

本文将深入探讨如何使用 Jenkins Pipeline 来自动化构建、测试和部署一个典型的 Java 项目。我们将从零开始,逐步搭建 Jenkins 环境,编写声明式 Pipeline 脚本,集成单元测试、静态代码分析、Docker 打包,并最终实现自动化部署。文章包含大量可运行的 Java 示例代码、完整的 Jenkinsfile 配置、以及清晰的流程图(使用 Mermaid 渲染),帮助你真正掌握这一关键技能。


为什么选择 Jenkins Pipeline?🔧

在 Jenkins 的早期版本中,任务配置主要通过 Web UI 进行,这种方式虽然直观,但存在诸多问题:

  • 不可版本化:配置无法纳入 Git 管理,难以追踪变更历史。
  • 难以复用:不同项目的配置重复度高,维护成本大。
  • 缺乏灵活性:复杂逻辑难以通过 UI 实现。

为了解决这些问题,Jenkins 推出了 Pipeline as Code 的理念。通过编写 Jenkinsfile(一个文本文件),你可以将整个 CI/CD 流程以代码形式定义,并与源代码一同存储在版本控制系统中。这带来了以下显著优势:

版本控制 :所有构建逻辑可追溯、可回滚。

代码复用 :通过共享库(Shared Libraries)实现逻辑复用。

声明式语法 :结构清晰,易于阅读和维护。

强大扩展性:支持脚本化逻辑,满足复杂场景需求。

💡 提示:Jenkins Pipeline 支持两种语法:声明式(Declarative)脚本化(Scripted)。本文主要使用更易上手、结构更清晰的声明式语法。


环境准备:搭建 Jenkins 服务器 ⚙️

在开始编写 Pipeline 之前,我们需要一个运行中的 Jenkins 实例。以下是推荐的本地快速启动方式(适用于学习和测试):

使用 Docker 快速启动 Jenkins

bash 复制代码
# 创建 Jenkins 数据卷(持久化配置)
docker volume create jenkins-data

# 启动 Jenkins 容器
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins-data:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts-jdk11

🔗 官方 Jenkins Docker 镜像文档:https://www.jenkins.io/doc/book/installing/docker/

启动后,访问 http://localhost:8080,按照提示完成初始化设置(首次启动时需从容器日志中获取管理员密码)。

安装必要插件

进入 Jenkins 后台 → Manage Jenkins → Plugins → Available plugins,安装以下关键插件:

  • Pipeline
  • Git
  • Maven Integration
  • Docker Pipeline
  • Blue Ocean(提供现代化的 Pipeline 可视化界面)

安装完成后重启 Jenkins。


示例 Java 项目:一个简单的 Spring Boot 应用 🌱

为了演示 Pipeline 的完整流程,我们创建一个极简的 Spring Boot REST API 项目。该项目包含一个接口 /api/hello,返回当前时间戳和问候语。

项目结构

复制代码
java-pipeline-demo/
├── pom.xml
└── src/
    └── main/
        └── java/
            └── com/example/demo/
                ├── DemoApplication.java
                └── HelloController.java

pom.xml

xml 复制代码
<?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>

    <groupId>com.example</groupId>
    <artifactId>java-pipeline-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

DemoApplication.java

java 复制代码
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

HelloController.java

java 复制代码
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@RestController
public class HelloController {

    @GetMapping("/api/hello")
    public String sayHello() {
        String timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        return "Hello from Java Pipeline! Current time: " + timestamp;
    }
}

单元测试(可选但推荐)

java 复制代码
package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureWebMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testHelloEndpoint() throws Exception {
        mockMvc.perform(get("/api/hello"))
               .andExpect(status().isOk())
               .andExpect(content().string(org.hamcrest.Matchers.containsString("Hello from Java Pipeline!")));
    }
}

将此项目推送到你的 Git 仓库(如 GitLab、GitHub 或私有 Git 服务器),确保 Jenkins 能够访问。


编写 Jenkins Pipeline:从构建到部署 🧪➡️📦➡️🚀

现在,我们在项目根目录下创建 Jenkinsfile,定义完整的自动化流程。

基础 Pipeline 结构

groovy 复制代码
pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }

        stage('Deploy') {
            steps {
                echo 'Deploying...'
            }
        }
    }
}

但这只是一个骨架。我们需要增强它,加入错误处理、环境变量、Docker 构建等能力。


完整的声明式 Jenkinsfile 示例 📜

以下是一个生产级可用的 Jenkinsfile,包含了构建、测试、Docker 镜像构建与推送、以及部署到 Docker 容器的完整流程。

groovy 复制代码
pipeline {
    agent any

    environment {
        // 项目信息
        APP_NAME = 'java-pipeline-demo'
        DOCKER_REGISTRY = 'your-registry.com' // 替换为你的镜像仓库地址
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/${APP_NAME}"
        // 从 Git 提取版本号(简化版)
        VERSION = sh(script: 'echo ${GIT_COMMIT:0:8}', returnStdout: true).trim()
    }

    tools {
        maven 'Maven-3.8.6' // 需在 Jenkins 全局工具配置中预先定义
        jdk 'OpenJDK17'    // 同上
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
                echo "Checked out commit: ${env.GIT_COMMIT}"
            }
        }

        stage('Build with Maven') {
            steps {
                script {
                    echo "Building ${APP_NAME} version ${VERSION}..."
                    sh 'mvn clean compile'
                }
            }
        }

        stage('Run Unit Tests') {
            steps {
                script {
                    sh 'mvn test'
                }
            }
            post {
                always {
                    // 发布测试报告(需安装 JUnit 插件)
                    junit 'target/surefire-reports/*.xml'
                }
                failure {
                    echo "❌ Unit tests failed!"
                }
            }
        }

        stage('Static Code Analysis') {
            steps {
                script {
                    // 使用 Maven 插件进行静态分析(如 SpotBugs、Checkstyle)
                    sh 'mvn spotbugs:check checkstyle:checkstyle'
                }
            }
            post {
                always {
                    // 发布分析报告(需对应插件)
                    publishHTML(target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'target/site',
                        reportFiles: 'spotbugs.html,checkstyle.html',
                        reportName: 'Code Analysis Report'
                    ])
                }
            }
        }

        stage('Package & Build Docker Image') {
            steps {
                script {
                    echo "Building Docker image: ${DOCKER_IMAGE}:${VERSION}"
                    
                    // 构建 JAR 文件(跳过测试,因已执行)
                    sh 'mvn clean package -DskipTests'
                    
                    // 复制 JAR 到 docker 目录(或直接在根目录构建)
                    sh 'cp target/*.jar app.jar'
                    
                    // 构建 Docker 镜像
                    sh """
                        docker build -t ${DOCKER_IMAGE}:${VERSION} .
                        docker tag ${DOCKER_IMAGE}:${VERSION} ${DOCKER_IMAGE}:latest
                    """
                }
            }
        }

        stage('Push Docker Image') {
            steps {
                script {
                    // 登录到私有仓库(凭据需在 Jenkins 凭据管理中配置)
                    withCredentials([usernamePassword(
                        credentialsId: 'docker-registry-creds',
                        usernameVariable: 'DOCKER_USER',
                        passwordVariable: 'DOCKER_PASS'
                    )]) {
                        sh """
                            echo \$DOCKER_PASS | docker login ${DOCKER_REGISTRY} -u \$DOCKER_USER --password-stdin
                            docker push ${DOCKER_IMAGE}:${VERSION}
                            docker push ${DOCKER_IMAGE}:latest
                            docker logout ${DOCKER_REGISTRY}
                        """
                    }
                }
            }
        }

        stage('Deploy to Docker Host') {
            steps {
                script {
                    echo "Deploying ${DOCKER_IMAGE}:${VERSION} to Docker host..."

                    // 停止并删除旧容器
                    sh 'docker stop ${APP_NAME} || true'
                    sh 'docker rm ${APP_NAME} || true'

                    // 启动新容器
                    sh """
                        docker run -d \\
                            --name ${APP_NAME} \\
                            -p 8080:8080 \\
                            ${DOCKER_IMAGE}:${VERSION}
                    """

                    echo "✅ Deployment completed! App available at http://<host>:8080/api/hello"
                }
            }
        }
    }

    post {
        success {
            echo "🎉 Pipeline succeeded for ${APP_NAME} v${VERSION}!"
        }
        failure {
            echo "💥 Pipeline failed for ${APP_NAME} v${VERSION}!"
            // 可在此添加通知(邮件、Slack 等)
        }
    }
}

🔗 关于 Jenkins 凭据管理:https://www.jenkins.io/doc/book/using/using-credentials/


Dockerfile 配置 🐳

为了让上述 Pipeline 能正确构建镜像,我们需要在项目根目录添加 Dockerfile

dockerfile 复制代码
# 使用官方 OpenJDK 运行时作为父镜像
FROM openjdk:17-jre-slim

# 设置工作目录
WORKDIR /app

# 复制 JAR 文件(由 Pipeline 中的 cp 命令生成)
COPY app.jar app.jar

# 暴露端口
EXPOSE 8080

# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]

这个 Dockerfile 非常简洁,仅包含运行 Java 应用所需的最小依赖。


Pipeline 执行流程可视化 📊

为了更清晰地理解整个自动化流程,我们使用 Mermaid 绘制流程图:
成功
失败
通过
失败
开始 Pipeline
检出代码
编译项目
运行单元测试
静态代码分析
标记失败并通知
打包 JAR 并构建 Docker 镜像
推送镜像到仓库
停止旧容器
启动新容器
部署成功
结束
结束

该图展示了 Pipeline 的线性流程及关键决策点(如测试失败则终止)。实际中,你还可以加入并行阶段(如并行运行不同类型的测试)以加速流程。


高级技巧:参数化构建与环境隔离 🌐

在真实场景中,我们通常需要将应用部署到多个环境(如 dev、staging、prod)。这时可以使用 参数化 Pipeline

参数化 Jenkinsfile

groovy 复制代码
pipeline {
    agent any

    parameters {
        choice(
            name: 'ENVIRONMENT',
            choices: ['dev', 'staging', 'prod'],
            description: '选择部署环境'
        )
        booleanParam(
            name: 'SKIP_TESTS',
            defaultValue: false,
            description: '是否跳过测试(仅限紧急修复)'
        )
    }

    environment {
        APP_NAME = 'java-pipeline-demo'
        // 根据环境动态设置端口或配置
        PORT = "${params.ENVIRONMENT == 'prod' ? '80' : (params.ENVIRONMENT == 'staging' ? '8080' : '9090')}"
    }

    stages {
        stage('Build') {
            steps {
                script {
                    def mvnCmd = params.SKIP_TESTS ? 'mvn clean package -DskipTests' : 'mvn clean package'
                    sh mvnCmd
                }
            }
        }

        stage('Deploy to ${params.ENVIRONMENT}') {
            when {
                expression { params.ENVIRONMENT != 'prod' || isProdApproved() }
            }
            steps {
                script {
                    // 根据环境执行不同部署逻辑
                    if (params.ENVIRONMENT == 'prod') {
                        deployToProduction()
                    } else {
                        deployToNonProd()
                    }
                }
            }
        }
    }
}

// 自定义函数:生产环境需人工审批
def isProdApproved() {
    if (params.ENVIRONMENT == 'prod') {
        input message: '⚠️ 确认部署到生产环境?', ok: 'Deploy'
    }
    return true
}

def deployToProduction() {
    echo '🚀 Deploying to PRODUCTION...'
    // 生产部署逻辑
}

def deployToNonProd() {
    echo "🧪 Deploying to ${params.ENVIRONMENT}..."
    // 非生产部署逻辑
}

通过 parameters 块,用户在触发构建时可以选择环境和选项。when 条件确保生产部署需人工确认,避免误操作。


错误处理与通知机制 🔔

可靠的 Pipeline 必须具备完善的错误处理和通知能力。

邮件通知示例

groovy 复制代码
post {
    failure {
        emailext (
            subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
            body: """<p>JOB: ${env.JOB_NAME} [${env.BUILD_NUMBER}] failed.</p>
                     <p>Check: ${env.BUILD_URL}</p>""",
            recipientProviders: [[$class: 'DevelopersRecipientProvider']]
        )
    }
    success {
        // 可选:成功时也通知
    }
}

⚠️ 注意:需安装 Email Extension Plugin 并配置 SMTP。

Slack 通知(更现代的选择)

groovy 复制代码
post {
    always {
        slackSend(
            channel: '#ci-cd-alerts',
            color: currentBuild.result == 'SUCCESS' ? 'good' : 'danger',
            message: "*${currentBuild.result}* Job '${env.JOB_NAME}' (${env.BUILD_NUMBER})\n${env.BUILD_URL}"
        )
    }
}

🔗 Slack Notification Plugin 文档:https://plugins.jenkins.io/slack/


性能优化:并行执行与缓存 🚄

随着项目规模增长,Pipeline 执行时间可能变长。我们可以通过以下方式优化:

并行测试

groovy 复制代码
stage('Parallel Tests') {
    parallel {
        stage('Unit Tests') {
            steps {
                sh 'mvn test -Dtest=Unit*'
            }
        }
        stage('Integration Tests') {
            steps {
                sh 'mvn verify -Dtest=Integration*'
            }
        }
    }
}

Maven 依赖缓存

在 Jenkins 节点上缓存 .m2 目录,避免每次下载依赖:

groovy 复制代码
agent {
    docker {
        image 'maven:3.8.6-openjdk-17'
        args '-v $HOME/.m2:/root/.m2' // 挂载本地 Maven 仓库
    }
}

或者使用 Pipeline Maven Integration Plugin 自动缓存。


安全最佳实践 🔒

自动化部署涉及敏感操作,安全至关重要:

  1. 凭据管理 :永远不要在 Jenkinsfile 中硬编码密码。使用 Jenkins 的 Credentials Binding
  2. 最小权限原则:Jenkins 服务账户应仅拥有必要权限。
  3. 代码扫描:集成 OWASP Dependency-Check 等工具,检测依赖漏洞。
  4. 审计日志:启用 Jenkins 审计插件,记录所有关键操作。
groovy 复制代码
stage('Security Scan') {
    steps {
        sh 'mvn org.owasp:dependency-check-maven:check'
    }
    post {
        always {
            publishHTML(target: [
                reportDir: 'target',
                reportFiles: 'dependency-check-report.html',
                reportName: 'OWASP Dependency Check'
            ])
        }
    }
}

🔗 OWASP Dependency-Check:https://owasp.org/www-project-dependency-check/


调试与故障排查 🛠️

Pipeline 失败时,如何快速定位问题?

  • 查看 Blue Ocean 界面:图形化展示各阶段日志。
  • 使用 echo 调试:在关键步骤输出变量值。
  • 临时添加 sh 'env':查看当前环境变量。
  • 本地模拟 :在本地运行相同命令(如 mvn package)验证。

例如:

groovy 复制代码
steps {
    script {
        echo "Current directory: ${pwd()}"
        echo "Java version: ${sh(script: 'java -version', returnStdout: true)}"
        sh 'ls -la target/'
    }
}

扩展:多分支 Pipeline 与 Pull Request 集成 🌿

对于采用 Git Flow 或 GitHub Flow 的团队,Multibranch Pipeline 是理想选择。Jenkins 会自动为每个分支/PR 创建子任务。

  1. 在 Jenkins 中创建 Multibranch Pipeline 任务。
  2. 配置 Git 仓库地址。
  3. Jenkins 自动扫描分支,寻找 Jenkinsfile
  4. 对于 PR,可配置仅运行测试而不部署。
groovy 复制代码
// 在 Jenkinsfile 中区分分支类型
def isPullRequest = env.CHANGE_ID != null

stage('Conditional Deploy') {
    when {
        not { expression { isPullRequest } }
    }
    steps {
        // 仅非 PR 分支才部署
        sh 'deploy.sh'
    }
}

总结:拥抱自动化,释放生产力 🌈

通过本文,我们系统地学习了如何使用 Jenkins Pipeline 自动化构建、测试和部署 Java 项目。从环境搭建、项目示例、Pipeline 编写,到高级技巧与安全实践,每一步都旨在帮助你构建一个健壮、高效、可维护的 CI/CD 流程。

关键收获包括:

  • ✅ 使用 声明式 Pipeline 实现流程即代码。
  • ✅ 集成 Maven 构建、JUnit 测试、Docker 打包
  • ✅ 实现 多环境部署人工审批
  • ✅ 加入 通知、安全扫描、错误处理 等生产级特性。
  • ✅ 利用 并行、缓存 优化性能。

自动化不是一蹴而就的,而是一个持续改进的过程。建议从简单流程开始,逐步加入更多环节。当你看到代码提交后自动完成测试、构建、部署,并收到成功通知时,那种"魔法般"的体验,正是 DevOps 的魅力所在!✨

最后提醒:不要为了自动化而自动化。始终以提升软件质量与交付效率为目标,让 Jenkins 成为你可靠的"数字工人",而非负担。

Happy Coding, Happy Deploying! 💻🔧🚀


🙌 感谢你读到这里!

🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。

💡 如果本文对你有帮助,不妨 👍 点赞 、📌 收藏 、📤 分享 给更多需要的朋友!

💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿

🔔 关注我,不错过下一篇干货!我们下期再见!✨

相关推荐
Jinkxs1 小时前
Java 部署:滚动更新(K8s RollingUpdate 策略)
java·开发语言·kubernetes
a8a3021 小时前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
jfqqqqq2 小时前
win11下intelliJ idea的shift + F6无效
java·ide·intellij-idea
xu_ws2 小时前
Spring-ai项目-deepseek-7-Function Calling(智能客服)
java·人工智能·spring
逝水如流年轻往返染尘2 小时前
JAVA中的抽象类
java·开发语言
hx862272 小时前
Java MySQL 连接
java·mysql·adb
lpfasd1232 小时前
Kubernetes (K8s) 底层早已不再直接使用 Docker 引擎了
java·docker·kubernetes
aq55356002 小时前
SpringBoot有几种获取Request对象的方法
java·spring boot·后端
steel80883 小时前
Spring Boot 整合 log4j2 日志配置教程
spring boot·单元测试·log4j