「实战」Spring Boot 4.1.0-M3 新特性速览:gRPC、OpenTelemetry全面升级

6 阅读3分钟

Spring Boot 4.1.0-M3 于2026年3月20日发布,这是4.1系列的第三个里程碑版本,包含127项改进。本文将带你快速了解核心新特性,并掌握实战应用。


一、版本概览

Spring Boot 4.1.0-M3 带来了一系列令人兴奋的新特性:

特性说明重要程度
Spring gRPC 支持原生支持gRPC服务⭐⭐⭐⭐⭐
Log4j 日志轮转原生日志文件轮转⭐⭐⭐⭐
OpenTelemetry 增强可观测性升级⭐⭐⭐⭐
MongoDB Batch支持Spring Batch集成⭐⭐⭐
RabbitMQ Streams SSL安全增强⭐⭐⭐
AMQP 1.0 支持消息协议扩展⭐⭐⭐

本文将重点介绍 gRPC日志轮转OpenTelemetry 三大核心特性。


二、Spring gRPC 原生支持

2.1 背景

在 Spring Boot 4.1 之前,我们需要:

  1. 手动引入 grpc-spring-boot-starter 第三方库
  2. 复杂的配置和依赖管理
  3. 与Spring生态集成不够优雅

Spring Boot 4.1 带来了原生的 gRPC 支持,开箱即用!

2.2 快速开始

项目结构

grpc-demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/xalgocapital/
│   │   │   ├── GrpcDemoApplication.java
│   │   │   ├── grpc/
│   │   │   │   └── HelloGrpcService.java
│   │   │   └── controller/
│   │   │       └── HelloController.java
│   │   ├── proto/
│   │   │   └── hello.proto
│   │   └── resources/
│   │       └── application.yml
│   └── test/
│       └── java/com/xalgocapital/
│           └── GrpcDemoApplicationTests.java
└── README.md

Maven 配置

<?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 
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.0-M3</version>
        <relativePath/>
    </parent>
    
    <groupId>com.xalgocapital</groupId>
    <artifactId>grpc-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>grpc-demo</name>
    <description>Spring Boot 4.1 gRPC Demo</description>
    
    <properties>
        <java.version>21</java.version>
        <grpc.version>1.60.0</grpc.version>
        <protobuf.version>3.25.1</protobuf.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-grpc</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- gRPC -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- Observability -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-otel</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-exporter-otlp</artifactId>
        </dependency>
        
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.1</version>
            </extension>
        </extensions>
        
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                    <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                    <outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

Protobuf 定义

// src/main/proto/hello.proto
syntax = "proto3";

package com.xalgocapital;

option java_package = "com.xalgocapital.grpc";
option java_multiple_files = true;

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

应用主类

// src/main/java/com/xalgocapital/GrpcDemoApplication.java
package com.xalgocapital;

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

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

gRPC 服务实现

// src/main/java/com/xalgocapital/grpc/HelloGrpcService.java
package com.xalgocapital.grpc;

import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class HelloGrpcService extends HelloServiceGrpc.HelloServiceImplBase {
    
    @Override
    public void sayHello(HelloRequest request,
                         StreamObserver<HelloReply> responseObserver) {
        String message = "Hello, " + request.getName() + "!";
        
        HelloReply reply = HelloReply.newBuilder()
            .setMessage(message)
            .build();
        
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

REST 控制器(调用 gRPC)

// src/main/java/com/xalgocapital/controller/HelloController.java
package com.xalgocapital.controller;

import com.xalgocapital.grpc.HelloReply;
import com.xalgocapital.grpc.HelloRequest;
import com.xalgocapital.grpc.HelloServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {
    
    @GrpcClient("hello-service")
    private HelloServiceGrpc.HelloServiceBlockingStub helloStub;
    
    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name) {
        HelloRequest request = HelloRequest.newBuilder()
            .setName(name)
            .build();
        
        HelloReply reply = helloStub.sayHello(request);
        return reply.getMessage();
    }
}

应用配置

# src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: grpc-demo
  grpc:
    server:
      port: 9090
    client:
      hello-service:
        address: static://localhost:9090
        negotiation-type: plaintext

management:
  endpoints:
    web:
      exposure:
        include: health,info

logging:
  level:
    com.xalgocapital: DEBUG

2.3 运行测试

# 编译项目
mvn clean compile

# 启动服务
mvn spring-boot:run

# 测试 gRPC(使用 grpcurl)
grpcurl -plaintext -d '{"name": "Tony"}' localhost:9090 HelloService/SayHello

# 输出:
# {
#   "message": "Hello, Tony!"
# }

# 测试 REST API
curl http://localhost:8080/api/hello/Tony

# 输出:
# Hello, Tony!

三、Log4j 日志轮转

3.1 配置示例

<!-- pom.xml 添加 Log4j2 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
# application.yml
logging:
  log4j2:
    roll:
      enabled: true
      max-size: 10MB
      max-history: 7
      total-size-cap: 1GB
      pattern: "app-%d{yyyy-MM-dd}-%i.log.gz"

或者使用 XML 配置:

<?xml version="1.0" encoding="UTF-8"?>
<!-- src/main/resources/log4j2.xml -->
<Configuration status="WARN">
    <Properties>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>
    
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        
        <RollingFile name="RollingFile" 
                     fileName="logs/grpc-demo.log"
                     filePattern="logs/grpc-demo-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="10MB"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="7">
                <Delete basePath="logs" maxDepth="1">
                    <IfFileName glob="grpc-demo-*.log.gz"/>
                    <IfLastModified age="7d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <Logger name="com.xalgocapital" level="DEBUG" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Logger>
        
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

四、OpenTelemetry 增强

4.1 配置示例

# application.yml 添加 OpenTelemetry 配置
management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0
    propagation:
      type: b3
    baggage:
      correlation:
        enabled: true
      remote-fields:
        - userId
        - tenantId
        
  otlp:
    tracing:
      export:
        enabled: true
        url: http://localhost:4318/v1/traces
        
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

4.2 自定义追踪

// src/main/java/com/xalgocapital/grpc/TracedHelloGrpcService.java
package com.xalgocapital.grpc;

import io.grpc.stub.StreamObserver;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import net.devh.boot.grpc.server.service.GrpcService;
import org.springframework.beans.factory.annotation.Autowired;

@GrpcService
public class TracedHelloGrpcService extends HelloServiceGrpc.HelloServiceImplBase {
    
    @Autowired
    private Tracer tracer;
    
    @Override
    public void sayHello(HelloRequest request,
                         StreamObserver<HelloReply> responseObserver) {
        Span span = tracer.spanBuilder("sayHello")
            .setAttribute("request.name", request.getName())
            .startSpan();
        
        try (Scope scope = span.makeCurrent()) {
            String message = "Hello, " + request.getName() + "!";
            
            HelloReply reply = HelloReply.newBuilder()
                .setMessage(message)
                .build();
            
            span.setStatus(StatusCode.OK);
            span.setAttribute("response.message", message);
            
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

五、完整可运行示例

5.1 单元测试

// src/test/java/com/xalgocapital/GrpcDemoApplicationTests.java
package com.xalgocapital;

import com.xalgocapital.grpc.HelloReply;
import com.xalgocapital.grpc.HelloRequest;
import com.xalgocapital.grpc.HelloServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test")
class GrpcDemoApplicationTests {
    
    @GrpcClient("hello-service")
    private HelloServiceGrpc.HelloServiceBlockingStub helloStub;
    
    @Test
    void testSayHello() {
        HelloRequest request = HelloRequest.newBuilder()
            .setName("Test")
            .build();
        
        HelloReply reply = helloStub.sayHello(request);
        
        assertNotNull(reply);
        assertEquals("Hello, Test!", reply.getMessage());
    }
}
# src/test/resources/application-test.yml
server:
  port: 0

spring:
  grpc:
    server:
      port: 0
    client:
      hello-service:
        address: static://localhost:9090
        negotiation-type: plaintext

5.2 启动并验证

# 1. 编译
mvn clean package -DskipTests

# 2. 启动 Jaeger(可选,用于追踪可视化)
docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4318:4318 \
  jaegertracing/all-in-one:latest

# 3. 启动应用
java -jar target/grpc-demo-1.0.0-SNAPSHOT.jar

# 4. 测试 gRPC
grpcurl -plaintext -d '{"name": "World"}' localhost:9090 HelloService/SayHello

# 5. 测试 REST
curl http://localhost:8080/api/hello/World

# 6. 查看健康状态
curl http://localhost:8080/actuator/health

# 7. 查看追踪(如果启动了 Jaeger)
# 访问 http://localhost:16686

六、升级指南

6.1 版本要求

组件最低版本推荐版本
Java1721
Spring Framework7.07.0
Maven3.6+3.9+

6.2 兼容性检查清单

  • Java 17+ 环境
  • 检查第三方依赖兼容性
  • 更新废弃的 API 调用
  • 测试 gRPC/消息队列集成
  • 验证日志配置迁移
  • 可观测性组件测试

七、总结

核心收获

  1. gRPC 原生支持 - 微服务通信更简单,告别第三方库
  2. 日志轮转 - 运维更方便,开箱即用
  3. OpenTelemetry 增强 - 可观测性更完善,分布式追踪更轻松

注意事项

  • M3 是里程碑版本,不建议直接用于生产
  • 提前在测试环境验证兼容性
  • 关注后续 RC 版本和正式版发布

参考资源


本文首发于掘金,作者:Tony Zhang

关注我,持续分享 Java + AI 开发实战经验