Zademy

Spring Boot 4 和 Spring Framework 7:Java 微服务快速安全的新时代

Java
SpringBoot; Java
words 字

Spring Boot 4.0(2025 年 11 月发布)和 Spring Framework 7.0 一起,算得上 Java 生态这几年最有分量的一次升级。不是小修小补,而是在模块化、空值安全、声明式 HTTP 客户端、弹性模式这些核心位置动了刀。

下面逐个拆。

最低要求:拥抱现代性

先说硬门槛。Java 最低要求还是 Java 17,但强烈建议用 Java 21 或更高(最好是 Java 25),因为 Virtual Threads 在这个版本已经足够成熟,在不少场景下可以直接替代传统的响应式编程(WebFlux)。Jakarta EE 11 对齐完成,Servlet 6.1、JPA 3.2、Bean Validation 3.1 都进来了。Kotlin 需要 2.2 或更高。GraalVM 24+ 完全支持,原生镜像生成优化了不少。

性能的巨大飞跃:模块化和 AOT

模块化

以前 spring-boot-autoconfigure 是一个巨大的工件,不管你用不用,几乎所有东西的配置逻辑都在里面,产生了大量 "classpath noise"。

Spring Boot 4 把这个模块拆成了更小、更专注的工件,自动配置分散在专门的模块里,按需加载。效果是启动更快(扫描的代码少了),内存占用更低(只加载真正需要的),IDE 建议更精确,GraalVM 原生镜像生成也更高效。

AOT 编译和 GraalVM

Spring Boot 4 完全对齐 GraalVM 24+。AOT(Ahead-of-Time)处理做了显著优化:编译时间更短,启动时内存占用更低,静态分析更强(能消掉更多死代码),reflection hints 兼容性改善。

新的 @ConfigurationPropertiesSource 注解允许更细粒度的模块化,只有相关的配置属性才会被处理。

空值安全:NPE 在编译时就该死

Java 里的 null 处理一直是生产事故的常客。Spring Framework 7 和 Spring Boot 4 在整个产品组合中采用了 JSpecify 作为空值安全标准。

JSpecify 是 Google、JetBrains、Meta、Oracle 等联合支持的协作标准,用注解明确标记值是否可以为 null。@Nullable 表示可能缺失,@NonNull 表示绝不为 null,@NullMarked 给整个包建立默认规则:除非另有说明,一切都是 NonNull。

@NullMarked 的实际效果

创建一个 package-info.java,把整个包标记为空值安全:

// src/main/java/com/tuempresa/servicio/package-info.java
@org.jspecify.annotations.NullMarked
package com.tuempresa.servicio;

之后你的 IDE(IntelliJ IDEA 2025.3+ 支持)会在你尝试在期望非空值的地方使用可能为 null 的值时,当场报错。开发时就拦住,而不是等到生产环境抛 NPE。

@NullMarked
package com.ejemplo.usuarios;

public class UsuarioService {

    // IDE 会在你尝试传递 null 时警告
    public Usuario crearUsuario(@NonNull String nombre, @Nullable String apellido) {
        // nombre 永不为 null - 可以直接安全使用
        String nombreCompleto = nombre.toUpperCase();

        // apellido 可能为 null - 你必须检查
        if (apellido != null) {
            nombreCompleto += " " + apellido.toUpperCase();
        }

        return new Usuario(nombreCompleto);
    }
}

声明式 HTTP 客户端:Feign 的"杀手"

Spring Boot 4 内置了声明式 HTTP 客户端,service-to-service 通信不再需要 Spring Cloud OpenFeign 这类外部依赖。

RestTemplate 在 Spring Framework 7 中正式弃用,未来版本会移除。代码冗长、难测试、不利用现代 Java 特性,是时候放手了。

接替它的是 RestClient(现代替代品,流畅的 builder 风格 API)和 @HttpExchange 声明式客户端。后者更值得关注:

package com.tuempresa.clients;

import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

@HttpExchange("/api/productos")
public interface ProductoClient {

    @GetExchange("/{id}")
    Producto obtenerProducto(@PathVariable("id") String id);

    @GetExchange
    List<Producto> listarProductos();

    @PostExchange
    Producto crearProducto(@RequestBody Producto producto);
}

配置客户端:

@Configuration
public class ClientConfig {

    @Bean
    public ProductoClient productoClient(RestClient.Builder builder) {
        RestClient restClient = builder
            .baseUrl("https://api.ejemplo.com")
            .build();

        HttpServiceProxyFactory factory = HttpServiceProxyFactory
            .builderFor(RestClientAdapter.create(restClient))
            .build();

        return factory.createClient(ProductoClient.class);
    }
}

定义接口就够了,不用写样板代码。接口天然容易 mock,类型安全,不需要额外依赖。Spring Boot 4 还改进了自动配置,通过 clientType 属性可以在 RestClient(默认)和 WebClient(响应式场景)之间切换,客户端代码不用改。

弹性和 API 版本控制

原生弹性

以前要用 Spring Retry 这种外部项目才能做的事,现在内置到 Spring Framework 7 了:

@Service
@EnableResilientMethods
public class PagoService {

    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public PagoRespuesta procesarPago(PagoRequest request) {
        // 如果失败,最多重试 3 次,等待 1 秒
        return pagoGateway.procesar(request);
    }

    @ConcurrencyLimit(maxConcurrency = 10)
    public Report generarReporte() {
        // 最多 10 个并发调用
        return reportService.generar();
    }
}

@Retryable 做重试,@ConcurrencyLimit 限制并发,@EnableResilientMethods 激活这些注解。

原生 API 版本控制

REST API 版本控制以前各种第三方方案混战。Spring Framework 7 把 version 属性直接加到了 @RequestMapping 及其变体里:

@RestController
@RequestMapping("/api/pedidos")
public class PedidoController {

    // 版本 1:简单响应
    @GetMapping(version = "1", produces = MediaType.APPLICATION_JSON_VALUE)
    public List<PedidoV1> getPedidosV1() {
        return pedidoService.obtenerPedidosSimples();
    }

    // 版本 2:带有元数据的丰富响应
    @GetMapping(version = "2", produces = MediaType.APPLICATION_JSON_VALUE)
    public PedidoResponseV2 getPedidosV2() {
        return pedidoService.obtenerPedidosCompletos();
    }
}

版本控制策略集中配置,可以在不改 controller 的情况下切换策略(路径、header、查询参数):

@Configuration
public class ApiVersioningConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 基于 header 的策略
        configurer.apiVersioning(builder -> builder
            .strategy(VersionStrategy.HEADER)
            .headerName("API-Version")
        );

        // 或基于路径:/v1/pedidos, /v2/pedidos
        // 或基于查询参数:/pedidos?version=1
    }
}

可观测性

Spring Boot 4 在可观测性上投入不少。Micrometer 2.0 更新了,原生 OpenTelemetry starter 内置了,日志、指标和追踪自动关联。

@Observed 注解给任何方法自动生成指标和追踪:

@Service
public class InventarioService {

    @Observed(name = "inventario.verificar", contextualName = "verificarDisponibilidad")
    public boolean verificarDisponibilidad(String productoId) {
        // 自动生成以下指标:
        // - 执行时间
        // - 调用次数
        // - 错误率
        return inventarioRepository.existeStock(productoId);
    }
}

Actuator 现在对 SSL 证书做了自动监控。证书快过期的时候(默认 14-30 天内),/actuator/health 会明确报告:

{
  "status": "UP",
  "components": {
    "ssl": {
      "status": "WARNING",
      "details": {
        "certificate": "api.ejemplo.com",
        "expiresIn": "12 days",
        "expiryDate": "2025-12-05"
      }
    }
  }
}

生产环境证书意外过期这种事,提前两周就能收到预警。

Client 模式:Template 的接班人

如果你用过 Spring,一定用过各种 Template(JdbcTemplate、RestTemplate、JmsTemplate)。Spring Framework 7 正在从 Template Method 模式迁移到基于 builder 和函数式接口的 Client 模式。

RestTemplate 已弃用,由 RestClient 和 @HttpExchange 接替。JdbcTemplate 和 JdbcClient 共存。JmsClient 是新增的。

JdbcClient

JdbcClient 用 builder 风格消掉了 JdbcTemplate 的冗长。以前写一个查询:

public List<Persona> findAll() {
    return jdbcTemplate.query(
        "SELECT id, nombre, edad FROM persona",
        new RowMapper<Persona>() {
            @Override
            public Persona mapRow(ResultSet rs, int rowNum) throws SQLException {
                Persona p = new Persona();
                p.setId(rs.getLong("id"));
                p.setNombre(rs.getString("nombre"));
                p.setEdad(rs.getInt("edad"));
                return p;
            }
        }
    );
}

现在:

public List<Persona> findAll() {
    return jdbcClient.sql("SELECT id, nombre, edad FROM persona")
        .query((rs, rowNum) -> new Persona(
            rs.getLong("id"),
            rs.getString("nombre"),
            rs.getInt("edad")
        ))
        .list();
}

// 或者使用自动映射更简单
public List<Persona> findAll() {
    return jdbcClient.sql("SELECT * FROM persona")
        .query(Persona.class)
        .list();
}

带参数的查询:

public Optional<Persona> findById(Long id) {
    return jdbcClient.sql("SELECT * FROM persona WHERE id = :id")
        .param("id", id)
        .query(Persona.class)
        .optional();
}

写操作:

public int actualizarEdad(Long id, int nuevaEdad) {
    return jdbcClient.sql("UPDATE persona SET edad = :edad WHERE id = :id")
        .param("edad", nuevaEdad)
        .param("id", id)
        .update();
}

更简洁,充分利用 lambda 和方法引用,和 Optional 集成得好,对 AOT 和 GraalVM 的支持也更好。

测试改进

新的测试切片 @HttpServiceClientTest 只加载 HTTP 客户端需要的配置,不用起整个应用上下文:

@HttpServiceClientTest
class ProductoClientTest {

    @Autowired
    private ProductoClient productoClient;

    @Test
    void deberiaObtenerProducto() {
        // 只加载 HTTP 客户端所需的配置
        Producto producto = productoClient.obtenerProducto("123");
        assertThat(producto).isNotNull();
    }
}

TestRestTemplate 也改进了,和 RestClient 集成更好,支持类型化响应:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class IntegrationTest {

    @Autowired
    private TestRestClient restClient;

    @Test
    void deberiaCrearUsuario() {
        Usuario nuevoUsuario = new Usuario("Juan", "Pérez");

        Usuario creado = restClient.post()
            .uri("/api/usuarios")
            .body(nuevoUsuario)
            .exchange()
            .expectStatus().isCreated()
            .expectBody(Usuario.class)
            .returnResult();

        assertThat(creado.getId()).isNotNull();
    }
}

从 Spring Boot 3 迁移

几个必须处理的点:Jackson 从 2.x 升到 3.x,javax.* 完整迁移到 jakarta.*,Java 17 最低要求(建议 21+),Kotlin 需要 2.2+。RestTemplate 要换成 RestClient 或声明式客户端,Spring Security 里 lambda 配置变成强制的,一些配置属性改了名字。

Spring 官方推荐用 OpenRewrite 自动化大部分迁移:

<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>5.42.0</version>
    <configuration>
        <activeRecipes>
            <recipe>org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0</recipe>
        </activeRecipes>
    </configuration>
</plugin>

这个插件可以自动处理依赖更新、javax 到 jakarta 的命名空间变更、已弃用 API 的迁移和配置文件更新。

结论

Spring Boot 4 和 Spring Framework 7 做的事情可以总结为:让 Java 应用更轻、更可预测、更快、更安全。

模块化和 AOT 改善了启动时间和内存消耗。JSpecify 在开发时就拦住 NPE。声明式 API 砍掉样板代码。弹性模式内置进框架。可观测性一流。GraalVM 原生镜像为容器和云环境做了优化。

迁移不是没有成本的,要更新依赖、调整配置。但投入产出比很值。如果你在为未来几年的项目选平台,Spring Boot 4 是现在 Java 生态里最扎实的基础。