简介

Spring boot 3.0于2022年11月正式发布了,这次的发布对于咱们一般程序员的影响有多少呢?咱们是不是需求考虑立马升级到Spring Boot3.0呢?

别急,看完这篇文章再来做决定也不迟。

对JAVA17和JAVA19的支撑

信任许多小伙伴到现在仍是运用得是JDK8,但是JDK8现已发布许多年了,跟着oracle加速JDK版别的发布,现在每半年发布一次,目前最新的JDK版别现已到了19了。其间JDK11和JDK17是LTS版别,也便是说咱们常说的稳定版别。

鉴于JDK17带来的许多新特性,Spring boot的最低JDK版别支撑现已提高到了JDK17,假如你还在运用JDK8或许JDK11的话,那么首先需求把JDK版别升级到17才能够运用Spring Boot 3.0。

许多小伙伴或许不是很清楚JDK17究竟有些什么新的特性或许功用,这儿再给咱们详细介绍一下。

record

首先是在JDK14的时分引入了record这个关键词,Record是一种轻量级的class,能够看做是数据结构体。和scala中的case有点类似。

举个自界说User的比如看一下Record是怎样用的:

public record Address(
        String addressName,
        String city
) {
}
public record CustUser(
        String firstName,
        String lastName,
        Address address,
        int age
) {}

上面咱们界说了两个类,CustUser和Address。CustUser中引用了Address。

Record和一般的类的差异就在于Record多了一个括号括起来的界说的字段。

Record类默许是final的,里边的字段默许是private final的。

要想知道Record究竟是怎样工作的,咱们能够运用javap来对编译好的class文件反编译,运转javap CustUser,能够得到下面的成果:

警告: 二进制文件CustUser包括com.flydean.records.CustUser
Compiled from "CustUser.java"
public final class com.flydean.records.CustUser extends java.lang.Record {
  public com.flydean.records.CustUser(java.lang.String, java.lang.String, com.flydean.records.Address, int);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public java.lang.String firstName();
  public java.lang.String lastName();
  public com.flydean.records.Address address();
  public int age();
}

上面能够看到final class CustUser承继自java.lang.Record。

而且主动增加了默许带有一切字段的构造函数。各个主动的获取办法,并完成了toString,hashCode和equals办法。

天啦,太完美了,咱们想要的它竟然都有。

假如上面的javap还不是很清楚的话,咱们能够凭借IDE的反编译功用,打开CustUser.class文件看一看:

public final class CustUser extends java.lang.Record {
    private final java.lang.String firstName;
    private final java.lang.String lastName;
    private final com.flydean.records.Address address;
    private final int age;
    public CustUser(java.lang.String firstName, java.lang.String lastName, com.flydean.records.Address address, int age) { /* compiled code */ }
    public java.lang.String toString() { /* compiled code */ }
    public final int hashCode() { /* compiled code */ }
    public final boolean equals(java.lang.Object o) { /* compiled code */ }
    public java.lang.String firstName() { /* compiled code */ }
    public java.lang.String lastName() { /* compiled code */ }
    public com.flydean.records.Address address() { /* compiled code */ }
    public int age() { /* compiled code */ }
}

留意,上面的反编译咱们能够看到,record中的一切字段都是final的,只能在初始化的时分设置。而且办法里边也没有供给其他能够改变字段内容的办法。

Text Blocks

Text Blocks是在JDK13中以第一次预览版别引入的。现在在JDK14中是第二次预览版别 JEP 368: Text Blocks。

在咱们日常的工作中,有时分需求用到一大段的字符串,这些字符串需求换行,需求排版,需求转义。在一个文本编辑器中,这当然是十分简略的事情。但是在java代码中,便是一个噩梦了。

尽管IDE能够主动帮咱们加上换行甚至能够对字符串进行拼接。但在java程序眼中,增加的许多额外的代码破坏了代码的美感。是任何一个有洁癖的程序员都无法忍受的。

怎样办? Text Blocks便是来挽救咱们的。

咱们先来个直观的比如,然后再剖析Text Blocks的特色。

仍是举HTML的比如,假如咱们想要打印出带缩减,有格式的html,传统办法能够这样做:

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

上面的代码看着特别别扭,让咱们看看用文本块办法怎样做:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

是不是清新许多,想要当即给文本块点个赞。

别慌点赞,咱们还有更多的东西要讨论。

或许有人又有问题了,文本块好用是好用,你这输出成果中,字段前面的空格都去哪了了呀?

这儿就要介绍这个概念了:英文名字叫Indentation,中文我把它翻译为编列。

再看一下上面的代码,这一次咱们把代码前面的空格以点来表示:

String html = """
..............<html>
..............    <body>
..............        <p>Hello, world</p>
..............    </body>
..............</html>
..............""";

Indentation的规则便是以最下面的“”“为界,对每一行都移除相同数量的空格。

上面的代码输出:

<html>
    <body>
        <p>Hello, world</p>
    </body>
</html>

上面的比如,最下面的”“”刚好在最左面的位置,假如把“”“向右移动4个空格会发生什么呢?

String html = """
..............<html>
..............    <body>
..............        <p>Hello, world</p>
..............    </body>
..............</html>
..................""";

输出成果:

<html>
    <body>
        <p>Hello, world</p>
    </body>
</html>

咱们看到输出成果是不变的,这样咱们又得到一条定论:假如”“”向右移动,则以text block中最左的那一行记载为准。

假如咱们把“”“向左移动四位,就会发现终究的输出成果每行前面都有四个空格。

这个功用是和String增加的新的String::stripIndent()对应的。

Switch Expressions

switch的新特性但是源源不绝,早在JDK 12就以预览功用被引入了,终究在JDK 14成为了正式版别的功用:JEP 361: Switch Expressions (Standard)。

其实Switch新增的功用有两个,一个便是能够连写case,一个便是switch能够带回来值了。

先看一个老版别的比如:

    @Test
    public void useOldSwitch(){
        switch (MONDAY) {
            case MONDAY:
            case FRIDAY:
            case SUNDAY:
                System.out.println(6);
                break;
            case TUESDAY:
                System.out.println(7);
                break;
            case THURSDAY:
            case SATURDAY:
                System.out.println(8);
                break;
            case WEDNESDAY:
                System.out.println(9);
                break;
        }
    }

上面的比如中,咱们想要匹配一切的星期,然后打印出相应的成果。写了许多个case句子,不美观。

再看一下新版别的比如:

    @Test
    public void useNewSwitch(){
        switch (MONDAY) {
            case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
            case TUESDAY                -> System.out.println(7);
            case THURSDAY, SATURDAY     -> System.out.println(8);
            case WEDNESDAY              -> System.out.println(9);
        }
    }

一个漂亮的连写,将一切都带走。

留意这儿switch句子没有回来值,所以并不需求default句子。

考虑一个在switch中赋值的情况:

    @Test
    public void oldSwitchWithReturnValue(){
        int numLetters;
        switch (MONDAY) {
            case MONDAY:
            case FRIDAY:
            case SUNDAY:
                numLetters = 6;
                break;
            case TUESDAY:
                numLetters = 7;
                break;
            case THURSDAY:
            case SATURDAY:
                numLetters = 8;
                break;
            case WEDNESDAY:
                numLetters = 9;
                break;
            default:
                throw new IllegalStateException("这天没发见人!");
        }
    }

传统办法咱们需求界说一个局部变量,并在case中给这个局部变量赋值。

咱们看下怎样运用新版的switch替换:

    @Test
    public void newSwitchWithReturnValue(){
        int numLetters = switch (MONDAY) {
            case MONDAY, FRIDAY, SUNDAY -> 6;
            case TUESDAY                -> 7;
            case THURSDAY, SATURDAY     -> 8;
            case WEDNESDAY              -> 9;
            default -> throw new IllegalStateException("这天没发见人!");
        };
    }

是不是十分简略。

留意,这儿需求一个default操作,不然会报编译过错。由于或许存在未遍历的值。

上面的switch回来值的情况,假如case后面的表达式比较复杂,那么就需求运用大括号来围起来。这种情况咱们需求运用到yield来回来要回来的值。

    @Test
    public void withYield(){
        int result = switch (MONDAY) {
            case MONDAY: {
                yield 1;
            }
            case TUESDAY: {
                yield 2;
            }
            default: {
                System.out.println("不是MONDAY,也不是TUESDAY!");
                yield 0;
            }
        };
    }

instanceof形式匹配

怎样理解呢?

咱们先举个历史版别中运用instanceof的比如。

假如咱们是动物园的办理员,动物园里边有Girraffe和Hippo两种动物。

@Data
public class Girraffe {
    private String name;
}
@Data
public class Hippo {
    private String name;
}

为了简略起见,上面两种动物咱们都之界说一个name属性。

接下来咱们要对两种动物进行办理,传入一个动物,判断一下这个动物是不是上面两种动物之一,按照传统的办法,咱们应该这样做:

    public void testZooOld(Object animal){
        if(animal instanceof Girraffe){
            Girraffe girraffe = (Girraffe) animal;
            log.info("girraffe name is {}",girraffe.getName());
        }else if(animal instanceof Hippo){
            Hippo hippo = (Hippo) animal;
            log.info("hippo name is {}",hippo.getName());
        }
        throw new IllegalArgumentException("对不住,该动物不是地球上的生物!");
    }

上面的代码中, 假如instanceof承认成功,咱们还需求将目标进行转换,才能调用相应目标中的办法。

有了JDK 14,一切都变得简略了,咱们看下最新的JDK 14的形式匹配怎样做:

    public void testZooNew(Object animal){
        if(animal instanceof Girraffe girraffe){
            log.info("name is {}",girraffe.getName());
        }else if(animal instanceof Hippo hippo){
            log.info("name is {}",hippo.getName());
        }
        throw new IllegalArgumentException("对不住,该动物不是地球上的生物!");
    }

留意instanceof的用法,通过instanceof的形式匹配,就不需求二次转换了。直接运用就能够了。而且形式匹配的目标还被限制了效果域规模,会愈加安全。

Sealed Classes and Interfaces

在Java中,类层次结构通过承继完成代码的重用,父类的办法能够被许多子类承继。

但是,类层次结构的意图并不总是重用代码。有时,其意图是对域中存在的各种或许性进行建模,例如图形库支撑的形状类型或金融使用程序支撑的贷款类型。

当以这种办法运用类层次结构时,咱们或许需求限制子类集然后来简化建模。

由于咱们引入了sealed class或interfaces,这些class或许interfaces只允许被指定的类或许interface进行扩展和完成。

举个比如:

package com.example.geometry;
public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

上面的比如中,咱们指定了Shape只允许被Circle, Rectangle, Square来承继。

上面的比如中并没有指定类的包名,咱们能够这样写:

package com.example.geometry;
public abstract sealed class Shape 
    permits com.example.polar.Circle,
            com.example.quad.Rectangle,
            com.example.quad.simple.Square {...}

搬迁到Jakarta EE

除了下面一些spring依赖包的更新之外:

Spring Framework 6.0.
Spring AMQP 3.0.
Spring Batch 5.0.
Spring Data 2022.0.
Spring GraphQL 1.1.
Spring HATEOAS 2.0.
Spring Integration 6.0.
Spring Kafka 3.0.
Spring LDAP 3.0.
Spring REST Docs 3.0.
Spring Retry 2.0.
Spring Security 6.0 
Spring Session 3.0
Spring WS 4.0.

spring boot3最大的改变便是把Java EE 搬迁到了Jakarta EE,也便是说咱们需求把 javax.* 替换成为 jakarta.*。

举个比如HttpServletRequest需求从:

import javax.servlet.http.HttpServletRequest;

替换成为:

import jakarta.servlet.http.HttpServletRequest;

GraalVM Native Image Support

Spring Boot3的一个十分大的功用点便是能够使用Spring的AOT技能,将spring boot的使用编译成为native的image,然后大大提高体系的运转功率。

比如,咱们能够这样增加一个native的build profile:

<profiles>
    <profile>
        <id>native</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.graalvm.buildtools</groupId>
                    <artifactId>native-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>build-native</id>
                            <goals>
                                <goal>compile-no-fork</goal>
                            </goals>
                            <phase>package</phase>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

然后运转下面的指令就能够把spring boot项目打包成native项目了:

mvn clean package -Pnative

对Micrometer的支撑

在spring boot3中默许供给了对Micrometer 1.10的支撑,spring boot会主动帮你配置一个ObservationRegistry的实例。

Micrometer能够用来搜集使用程序各项指标数据,然后完成对使用程序的各种监控。

其他的一些改动

当然,除了上面的主要的改变之外,Spring boot3还供给了其他的一些小的调整,咱们感兴趣的话能够亲身升级到spring boot3尝试一下。

更多内容请参阅 www.flydean.com

最浅显的解读,最深入的干货,最简洁的教程,很多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技能,更懂你!