侧边栏壁纸
博主头像
码农小札

行动起来,活在当下

  • 累计撰写 5 篇文章
  • 累计创建 3 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Spring Boot 4新特性详解

Spring Boot 4 是 Spring 生态的新一代版本,它内置了 Spring Framework 7.0,带来了多个实用的新特性,大幅提升了开发体验和系统的稳定性、可维护性。

一、API 版本控制原生支持

Spring Framework 7.0 为 Spring Boot 4 带来了 API 版本控制的原生支持,这是现代 Web 开发中非常实用的功能,能够帮助开发者在不破坏现有客户端的情况下管理 API 的演进。

1. 基础使用:RequestMapping version 属性

@RequestMapping及其变体注解中新增了version属性,同时可以通过配置来设置版本的解析策略。


@Configuration

public class WebConfiguration implements WebMvcConfigurer {

    @Override

    public void configureApiVersioning(ApiVersionConfigurer configurer) {

        // 方式 1:使用请求参数(默认参数名为 "version")

        configurer.useRequestParam("version");

        // 方式 2:使用请求头

        // configurer.useRequestHeader("API-Version");

        // 方式 3:使用路径变量

        // configurer.usePathVariable("version");

    }

}

2. 常见版本控制策略

  • 请求参数方式?version=1

  • 请求头方式API-Version: 1

  • 路径变量方式/api/v1/users

  • Accept 头方式Accept: application/vnd.api+json;version=1

3. 调用测试

使用请求参数进行版本控制的测试示例:


# 调用版本 1 的 API

curl "http://localhost:8080/?version=1"

# 输出:API Version 1.0.0

# 调用版本 2 的 API

curl "http://localhost:8080/?version=2"

# 输出:API Version 2.0.0

# 不指定版本(可能返回默认版本或错误)

curl "http://localhost:8080/"

4. 高级配置:自定义版本解析器

对于复杂的业务场景,可以实现自定义的版本解析逻辑:


@Configuration

public class WebConfiguration implements WebMvcConfigurer {

    @Override

    public void configureApiVersioning(ApiVersionConfigurer configurer) {

        configurer.useVersionResolver(new ApiVersionResolver() {

            @Override

            public @Nullable String resolveVersion(HttpServletRequest request) {

                // 示例 1:从用户代理字符串解析版本

                String userAgent = request.getHeader("User-Agent");

                if (userAgent != null && userAgent.contains("mobile")) {

                    return "mobile";

                }

                // 示例 2:基于客户端 IP 或其他业务规则

                String clientIp = getClientIp(request);

                if (isTestEnvironment(clientIp)) {

                    return "beta";

                }

                // 默认版本

                return "1";

            }

        });

    }

}

二、空安全特性:消灭空指针异常

Spring Boot 4 引入了 JSpecify 注解规范,替代了老旧的 JSR-305 注解体系,将空安全检查从运行时提升到编译期,从根源上减少空指针异常的出现。

1. JSpecify 核心理念

JSpecify 的核心是让类型系统携带空值信息,在编译期进行验证,通过@NullMarked@Nullable注解的组合,配合静态分析工具(如 NullAway),可以实现:

  • 编译期捕获潜在的 NPE

  • 显式化空值契约,方法签名明确告知调用者哪些值可能为 null

  • 减少防御性代码,无需过度的空值检查

  • 提升代码可维护性,团队成员可以通过注解理解 API 的空值语义

2. 默认非空规则

Spring Boot 4 引入了 “默认非空” 的规则,在包或类上使用@NullMarked注解后,除非用@Nullable明确标注,否则所有类型都是非空的。


// Spring Boot 4 之前 - 返回值是否可空?无从知晓!

@Service

public class PigUserService {

    public PigUser findUserByUsername(String username) {

        return pigUserRepository.findByUsername(username);  // 可能返回 null

    }

}

// Spring Boot 4 使用 JSpecify - 显式标注可空性

@Service

@NullMarked// 默认所有类型为非空

public class PigUserService {

    @Nullable

    public PigUser findUserByUsername(String username) {

        return pigUserRepository.findByUsername(username);  // 明确表示可能返回 null

    }

}

3. 集合类型的空安全处理

JSpecify 还可以处理集合中的可空元素,清晰表达集合的空值语义:


@Service

public class PigReviewService {

    // 列表本身非空,但可以包含空元素

    public List<@Nullable String> getProductReviews() {

        List<@Nullable String> reviews = new ArrayList<>();

        reviews.add("商品质量很好");           // 评价 1:已填写

        reviews.add(null);                    // 评价 2:留空

        reviews.add("lengleng 的服务态度非常棒");  // 评价 3:已填写

        return reviews;

    }



    public int calculateReviewRate(List<@Nullable String> reviews) {

        long completed = reviews.stream()

                .filter(Objects::nonNull)

                .count();

        return (int) ((completed * 100) / reviews.size());

    }

}

4. 编译期安全检查:NullAway 集成

集成 NullAway 后,可以在编译期就捕获空指针问题,将潜在的运行时错误转化为构建失败:


<build>

    <plugins>

        <plugin>

            <groupId>org.apache.maven.plugins</groupId>

            <artifactId>maven-compiler-plugin</artifactId>

            <version>3.14.0</version>

            <configuration>

                <release>17</release>

                <encoding>UTF-8</encoding>

                <fork>true</fork>

                <compilerArgs>

                    <arg>-XDcompilePolicy=simple</arg>

                    <arg>--should-stop=ifError=FLOW</arg>

                    <arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>

                    <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>

                    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>

                    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>

                </compilerArgs>

                <annotationProcessorPaths>

                    <path>

                        <groupId>com.google.errorprone</groupId>

                        <artifactId>error_prone_core</artifactId>

                        <version>2.38.0</version>

                    </path>

                    <path>

                        <groupId>com.uber.nullaway</groupId>

                        <artifactId>nullaway</artifactId>

                        <version>0.12.7</version>

                    </path>

                </annotationProcessorPaths>

            </configuration>

        </plugin>

    </plugins>

</build>

5. @Nullable 与 Optional 的对比

相比于 Optional,@Nullable注解有以下优势:

  • API 兼容性:为现有方法添加@Nullable不会破坏现有调用者,而改为返回 Optional 会破坏兼容性

  • 运行时开销@Nullable没有运行时成本,而 Optional 会产生额外的对象分配开销

  • 使用场景更灵活:Optional 主要设计为返回类型,@Nullable可以用于参数、字段等场景

  • 代码更简洁:简单的空值检查场景下,@Nullable的代码更简洁

三、BeanRegistrar:简化动态 Bean 注册

Spring Boot 4 引入了新的BeanRegistrar接口,替代了过去繁琐的ImportBeanDefinitionRegistrar,大幅简化了动态 Bean 注册的代码。

1. BeanRegistrar 接口定义

BeanRegistrar的接口非常简洁,只包含一个方法:


public interface BeanRegistrar {

    void register(BeanRegistry registry, Environment environment);

}

其中BeanRegistry用于注册 Bean,Environment可以直接获取配置信息,无需额外实现接口。

2. 新旧方式对比

基础注册

新方式(BeanRegistrar)


registry.registerBean("pigUserService", PigUserService.class);

旧方式(ImportBeanDefinitionRegistrar)


BeanDefinitionBuilder builder = BeanDefinitionBuilder

        .genericBeanDefinition("com.pig4cloud.pigx.admin.service.impl.PigUserService");

registry.registerBeanDefinition("pigUserService", builder.getBeanDefinition());

带配置的注册

新方式


registry.registerBean("pigOrderService", PigOrderService.class, spec -> spec

        .prototype()           // 原型作用域,每次获取都是新实例

        .lazyInit()           // 延迟初始化,用到才创建

        .description("PIG 订单服务")  // 自定义描述

);

旧方式


BeanDefinitionBuilder builder = BeanDefinitionBuilder

        .genericBeanDefinition("com.pig4cloud.pigx.mall.service.impl.PigOrderService");

builder.setScope("prototype");           // 要记住是"prototype"字符串

builder.setLazyInit(true);               // setter 方式

builder.setDescription("PIG 订单服务");    // 又是 setter

registry.registerBeanDefinition("pigOrderService", builder.getBeanDefinition());

自定义创建逻辑(带依赖注入)

新方式


registry.registerBean("pigGoodsService", PigGoodsService.class, spec -> spec

        .supplier(context -> {

            // context 可以获取其他已注册的 Bean

            PigUserService userService = context.bean(PigUserService.class);

            return new PigGoodsService(userService);

        })

);

旧方式


BeanDefinitionBuilder builder = BeanDefinitionBuilder

        .genericBeanDefinition("com.pig4cloud.pigx.mall.service.impl.PigGoodsService");

// 需要手动设置构造函数参数或属性引用

builder.addConstructorArgReference("pigUserService");  // 还得知道 Bean 的名字

// 或者用更复杂的方式

builder.setFactoryMethod("createInstance");

builder.addPropertyReference("userService", "pigUserService");

registry.registerBeanDefinition("pigGoodsService", builder.getBeanDefinition());

条件注册

新方式


// Environment 直接就在参数里

if (environment.matchesProfiles("production")) {

    registry.registerBean(PigCacheService.class);

}

// 支持任意复杂的逻辑

if (environment.getProperty("pig.cache.enabled", Boolean.class, false)) {

    String cacheType = environment.getProperty("pig.cache.type", "redis");

    if ("redis".equals(cacheType)) {

        registry.registerBean(PigRedisCacheService.class);

    } else {

        registry.registerBean(PigLocalCacheService.class);

    }

}

旧方式


// 先得实现 EnvironmentAware 接口

private Environment environment;

@Override

public void setEnvironment(Environment environment) {

    this.environment = environment;

}

// 然后在 registerBeanDefinitions 方法里

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

    if (environment.matchesProfiles("production")) {

        BeanDefinitionBuilder builder = BeanDefinitionBuilder

                .genericBeanDefinition("com.pig4cloud.pigx.common.cache.PigCacheService");

        registry.registerBeanDefinition("pigCacheService", builder.getBeanDefinition());

    }

}

3. 实战案例:动态消息服务注册

在多租户 SaaS 项目中,需要根据租户的配置动态注册消息服务,使用 BeanRegistrar 可以大幅简化代码:


/**

* 消息服务注册器 - 新方式

*

* @author lengleng

*/

public class PigMessageServiceRegistrar implements BeanRegistrar {

    @Override

    public void register(BeanRegistry registry, Environment environment) {

        String messageType = environment.getProperty("pig.message.type", "email");

        switch (messageType.toLowerCase()) {

            case "email" -> registry.registerBean(

                    "pigMessageService",

                    PigEmailMessageService.class,

                    spec -> spec.description("PIG 邮件消息服务")

            );

            case "sms" -> registry.registerBean(

                    "pigMessageService",

                    PigSmsMessageService.class,

                    spec -> spec.description("PIG 短信消息服务")

            );

            default -> throw new IllegalArgumentException("未知的消息类型:" + messageType);

        }

    }

}

只需要 27 行代码就可以完成过去 54 行代码的功能,并且代码的可读性和可维护性大幅提升。

四、总结

Spring Boot 4 的这些新特性从不同维度提升了开发体验和系统质量:API 版本控制让 API 演进更优雅,空安全特性从根源减少空指针异常,BeanRegistrar 简化了动态 Bean 注册的代码。这些特性都是 Spring 框架对现代开发场景的适配,能够帮助开发者更高效地构建稳定、可维护的应用。

0

评论区