坤刀小试 SpringBoot 与 JDK21 能擦出怎样的火花?

JDK21 已于2023年9月发布,其新特性中,「虚拟线程」备受瞩目自不必说,后或将改写并发编程现有之格局,目前观望中,除此,引入了新的内存 api 以访问 JVM 以外的内存地址,以前则是需要使用 JNI 技术调 c/c++ 函数来实现,这为 Java 写歪瓜提供了新的可能,JVM 或将纳入检测行列。前有放弃指针,后有操作内存,前有一处编译到处运行,后有 GraalVM 构建原生镜像,颇有「前人栽树,后人砍树」之意,暂且不论。其余,择 Record 记录类,字符串模板,或可于项目中小试一番,以观其效。


Record 特性

传统类型实体 VS Record 类型实体

于当下,一个传统的实体类(JavaBean),通常拥有如下定义,默认的无参构造、属性私有、通过 getter、setter 访问其私有属性、序列化(可选)、重写 toString、equals(可选)。演示如下:

// 借助 lombok 生成 getter/setter
@Data
public class User { private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private String phone;
}

record 类似 enum,只是语法糖层面的新东西,对类做了隐式处理,反编译可知,即:为该类生成一个全参数构造,以及相应的 getter 方法,没有 setter 方法,成员属性只能定义在()中,且赋值只能通过构造赋值,其 getter 亦不同于传统类型 getXxx 的命名方式,字段名即方法名。演示如下:

public record User(Integer id, String name, Integer age, String gender, String phone) {}
使用普通构造注入 VS 使用 Record 类型进行依赖注入

Spring 注入方式有字段注入、setter 注入、构造注入三种。因构造注入方式更易把控,且不可变性,故而更受官方推崇。偷懒,省去其中 service 部分,使用构造生成 controller 演示代码如下:

@RestController
@RequiredArgsConstructor
public class UserController { // 构造注入,借助 lombok 生成必要的参数构造
    private final UserDao userDao;
    ......
}

使用 Record 生成 controller 并进行依赖注入,亦属于构造注入,演示如下

@RestController
public record UserController(UserDao userDao) {......
}

注:如果明确知道一个框架底层使用了反射获取无参构造以创建对象,或通过 get+字段名取值、set+字段名赋值方式时,就不要用 Record了,盲猜一波,或许某些涉及 JSON 转化的框架中可能会存在这样的代码,此时不要用 Record,Record 目前局限性还是挺大的,用处是真不多,也不能取代 lombok。


字符串模板特性

删繁就简(“三……三秋树”——来自画外音,语气弱弱),引入了符号""",帮我们省去繁杂的字符串拼接过程。

MyBatis,我第一个想到的受益者就是它。原先使用注解方式生成 mapper,因其使用动态 sql 时存在大量标签,需要在注解上大量拼接字符串方可完成,且可读性极差,费力不讨好,故该方式没有流行开来,今有字符串模板补其缺漏,岂非妙哉?又闻「(天下 SQL)分久必合,合久必分」,亦合此理!sql 语句从方法体中移至 xml,又移至方法签名上,方便阅读亦不失灵活,该代理依旧代理,该解耦依旧解耦,无 xml 之乱花迷眼,无跳入跳出之劳吾形、乏吾身,不可谓不称心也。

或言:“何必理会?吾有一法,曰「MyBatis plus」,一把梭哈之,岂非更妙?”,对曰:“岂不闻,大道至简,万法归宗,及君悟,必歌之以「当~~~」”,其实,这种三方辅助框架,用多了,方不方便,也就那样,另外,我为何要写sql?因为我享受掌控全局的快感 因为我对这SQL,爱得深沉 因为我傻呗。我也是好久没写 sql 了,感觉偶尔写一写 sql 也是蛮不错的。(来自bed言bed语)字符串模板结合 MyBatis,演示代码如下:

public interface UserDao { // as 别名映射
    @Select("""
            SELECT
                u_id AS id,
                u_name AS name,
                u_age AS age,
                u_gender AS gender,
                u_phone AS phone
            FROM t_user
            """)
    List findAll1();
    // 字段一一映射 -- 适用于常规类型实体
    @Results(id = "user",
            value = { @Result(column = "u_id", property = "id", id = true),
                    @Result(column = "u_age", property = "age"),
                    @Result(column = "u_gender", property = "gender"),
                    @Result(column = "u_phone", property = "phone")
            })
    @Select("select * from t_user")
    List findAll2();
	// 构造器映射 -- 适用于 Record 类型实体
    @ConstructorArgs({ @Arg(column = "u_id", javaType = Integer.class),
            @Arg(column = "u_name", javaType = String.class),
            @Arg(column = "u_age", javaType = Integer.class),
            @Arg(column = "u_gender", javaType = String.class),
            @Arg(column = "u_phone", javaType = String.class)
    })
	// 内含 where/if 等动态标签的,需用  """)
    List findAll3(@Param("user") User user);
}

后记:

遗憾的是,于 """中的代码提示功能尚无,有心人可自己开发一款 idea 插件,另外,不得不说,依旧又臭又长,或可改用更简洁的语义以完成sql拼接。

由于 xml 配置方式属实是冗长,spring 也早早就放弃了,JavaConfig 明显更人性化、现代化,其余配置也是改用了更精简的 properties 或 yaml。

关于如何让 MyBatis 变得更精简,我倒是有一个不成熟的想法:搞一个 MyBatis Minus 框架,功能上 + +,代码却 - -,直接摒弃其解析 xml 相关代码,直接注解加字符串模板,定义一套全新的语义、词法。当初试想过搞这么个框架,不过没那么多空闲时间,提供思路,留给有心人去搞吧。

下附相关框架:

语法词法分析框架:Antlr4、Javacc、jflex

字符串模板引擎框架:Enjoy、Thymeleaf,FreeMarker,Velocity


追责声名:

文中以上写法,均为测试案例,任何人因使用采用而遭致意外乃至损失的,由「三国杀官方」以及「动视暴雪」全权负责。