Spring Tips:Java 14 (或:您的 Java 能做到这一点吗?)

工程 | Josh Long | 2020 年 3 月 11 日 | ...

各位 Spring 爱好者,大家好!欢迎收看又一期 Spring Tips!在本期节目中,我们将探讨 Java 14 的新功能及其在构建 Spring Boot 应用程序中的应用。

演讲者:Josh Long (@starbuxman)

要开始,我们需要使用最新、最棒的 Java 版本 Java 14,它尚未发布。它将于 2020 年初发布。您可以在 Java.net 上下载早期访问版本。您也可以考虑使用 SDKManager (sdk),它使安装新的 JVM 版本变得非常简单。

请记住,Java 每 6 个月发布一次新版本。这些新版本可用于生产环境,但仅支持从一个版本到下一个版本之间的六个月。Java 项目有时还会发布长期支持 (LTS) 版本。目前,Java 11 是一个 LTS 版本。Java 14 仅在 Java 15 发布之前适用于生产环境。确实,我们将要介绍许多预览功能,有人可能会争辩说这些功能根本不应该用于生产环境。您已被警告!

如果您正在使用 SDKManager,可以运行以下命令来安装 Java 14。

sdk install java 14.ea.36-open 

访问 Spring Initializr,然后使用 Spring Boot 2.3 或更高版本生成新项目。您还需要选择 JDBCPostgreSQL

旧版本的 Spring Boot 尚不支持 Java 14 运行时。当然,要编辑此版本的 Java,您需要将其导入到您的 IDE 中。但在执行此操作之前,让我们修改 pom.xml 文件以配置构建以支持 Java 14。通常,当您访问 Spring Initializr 时,您还会指定 Java 版本。Java 14 尚未得到支持,因此我们需要手动配置一些内容。

通过更改 java.version 属性来确保您指定了 Java 版本

<properties>
 <java.version>14</java.version>
</properties>

这使我们的构建可以使用 Java 14 及其所有已发布的功能,但要真正体验 Java 14 的新颖性,我们需要启用预览功能——这些功能已在版本中发布,但默认情况下未启用。

<plugins>...</plugins> 部分,添加以下插件配置以启用 Java 14 的预览功能。

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <release>14</release>
        <compilerArgs>
            <arg>--enable-preview</arg>
        </compilerArgs>
        <forceJavacCompilerUse>true</forceJavacCompilerUse>
        <parameters>true</parameters>
    </configuration>
</plugin>

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <argLine>--enable-preview</argLine>
    </configuration>
</plugin>

现在您已准备就绪!让我们来看一些 Java 代码。Spring Initializr 很贴心地为我们提供了一个项目和一个骨架入口类。

package com.example.fourteen;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.sql.Types;
import java.util.List;

@SpringBootApplication
public class FourteenApplication {

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

我们将创建一个简单的、由 JDBC 驱动的服务,该服务使用 SQL 将数据写入数据库。我们需要一个对象来映射数据库表 people 中的数据。

此时,我通常会通过 IDE 的代码生成功能费力地编写 Javabean 对象,或者使用 Lombok 通过注解来创建一个编译器生成的对象,该对象包含 getter、setter、toString 方法以及 equals 方法的实现。我甚至可能会不情愿地提及其他语言能够将这种繁琐的工作变得微不足道。Scala 支持case classes。Kotlin 支持data classes

Java 14 支持 _record_s。

record Person(Integer id, String name, int emotionalState) {
}

不错吧?这段语法威力强大!它为我们提供了一个带有构造函数和构造函数参数、属性、equalstoString 方法实现等的新对象。我们可以像实例化其他任何对象一样实例化这个对象。尝试访问对象中的属性,您会发现我们的构造函数属性已变为 id()/id(int)name()/name(String)emotionalState()/emotionalState(int)。如此之少,能做到这一点很不错!

让我们看看 PeopleService 的实现。

PeopleService 使用 JdbcTemplate 来快速将数据库查询结果转换为 Java 对象。如果您曾经使用过 JdbcTemplate(谁没用过呢?),这应该相当直接。我留下了一些未实现的部分,以便我们可以直接重新审视它们。


@Service
class PeopleService {

	private final JdbcTemplate template;

	//todo
	private final String findByIdSql = null;

	private final String insertSql = null; 

	private final RowMapper<Person> personRowMapper =
		(rs, rowNum) -> new Person(rs.getInt("id"), rs.getString("name"), rs.getInt("emotional_state"));

	PeopleService(JdbcTemplate template) {
		this.template = template;
	}

	public Person create(String name, EmotionalState state) {
		 //todo
	}

	public Person findById(Integer id) {
		return this.template.queryForObject(this.findByIdSql, new Object[]{id}, this.personRowMapper);
	}
}


首先,我们将使用一些 SQL 查询。我一生中非常努力地避免在 Java 代码中输入 SQL 查询。我的天哪,如果人们知道他们可以用 Java Strings 优雅地表达 SQL 查询,他们会不会如此频繁地使用 ORM 呢?对于任何稍微复杂一点的事情,我都会将 SQL 查询提取到属性文件中,然后使用 Spring 的配置属性机制加载它们。

但是,在 Java 14 中我们可以做得更好!多行字符串终于来到 Java 了!它现在与 Python、Ruby、C++、C#、Rust、PHP、Kotlin、Scala、Groovy、Go、JavaScript、Clojure 以及其他十几种语言并驾齐驱。我很高兴它终于来了!

sql 变量替换为以下声明。

private final String findByIdSql =
    """
            select * from PEOPLE 
            where ID = ? 
    """;

	private final String insertSql =
    """
        insert into PEOPLE(name, emotional_state)
        values (?,?);
    """;

非常棒!有一些方法可以用来修剪边距等。您还可以使用反斜杠转义序列 (\) 放在每行的末尾,以指示下一行应从此处开始,否则换行符将被视为字面量。

让我们来看看 create 方法。

在数据库中将 PersonemotionalState 存储为 int 是一个实现细节。我不想将其全部暴露给用户。让我们使用枚举来描述每个 Person 的情绪状态。

enum EmotionalState {
	SAD, HAPPY, NEUTRAL
}

这算是一个开始,我想。让我们来处理实现。我们立即就有机会使用 Java 14 中的另一个很棒的新功能:更智能的 switch 表达式。Switch 表达式为我们提供了一种从 switch case 的分支返回一个值,然后将其赋给一个变量的方式。语法几乎与我们之前使用的相同,只是每个 case 都用箭头 -> 而不是 : 与分支分隔,并且不需要 break 语句。

在下面的示例中,我们将 int 值赋给变量 index,由于最近 Java 版本中的另一个很棒的功能——使用 var 进行自动类型推断,我们不需要指定其类型。

	public Person create(String name, EmotionalState state) {
		var index = switch (state) {
			case SAD -> -1;
			case HAPPY -> 1;
			case NEUTRAL -> 0;
        };
        // todo 
	}

有了 index,我们就可以创建必要的 PreparedStatement 来对数据库执行 SQL 语句。我们可以执行该准备好的语句,并传入一个 KeyHolder,它将用于收集新插入行返回的生成键。


	public Person create(String name, EmotionalState state) {
		var index = switch (state) {
			case SAD -> -1;
			case HAPPY -> 1;
			case NEUTRAL -> 0;
		};
        var declaredParameters = List.of(
            new SqlParameter(Types.VARCHAR, "name"), 
            new SqlParameter(Types.INTEGER, "emotional_state"));
		var pscf = new PreparedStatementCreatorFactory(this.insertSql, declaredParameters) {
			{
				setReturnGeneratedKeys(true);
				setGeneratedKeysColumnNames("id");
			}
		};
		var psc = pscf.newPreparedStatementCreator(List.of(name, index));
		var kh = new GeneratedKeyHolder();
		this.template.update(psc, kh);
		// todo
    }

唯一的问题是返回的键是 Number,而不是 IntegerDouble 或任何更具体的类型。这使我们有机会使用 Java 14 中另一个有趣的​​新功能——智能转换。智能转换允许我们在 instanceof 测试中进行类型测试后避免冗余的转换。它甚至更进一步,提供一个变量名,我们可以在测试范围内引用该自动转换的变量。


	public Person create(String name, EmotionalState state) {
		var index = switch (state) {
			case SAD -> -1;
			case HAPPY -> 1;
			case NEUTRAL -> 0;
		};
        var declaredParameters = List.of(
            new SqlParameter(Types.VARCHAR, "name"), 
            new SqlParameter(Types.INTEGER, "emotional_state"));
		var pscf = new PreparedStatementCreatorFactory(this.insertSql, declaredParameters) {
			{
				setReturnGeneratedKeys(true);
				setGeneratedKeysColumnNames("id");
			}
		};
		var psc = pscf.newPreparedStatementCreator(List.of(name, index));
		var kh = new GeneratedKeyHolder();
		this.template.update(psc, kh);
		if (kh.getKey() instanceof Integer id) {
			return findById(id);
		}
		throw new IllegalArgumentException("we couldn't create the " + Person.class.getName() + "!");
    }

我们需要一个 int 才能将其传递给 findById(Integer),而此方法正是完成了这项工作。方便吧?

一切都运行正常,所以让我们用一个简单的 ApplicationListener<ApplicationReadyEvent 来测试代码。


@Component
class Runner {

	private final PeopleService peopleService;

	Runner(PeopleService peopleService) {
		this.peopleService = peopleService;
	}

	@EventListener(ApplicationReadyEvent.class)
	public void exercise() throws Exception {
		var elizabeth = this.peopleService.create("Elizabeth", EmotionalState.SAD);
		System.out.println(elizabeth);
	}
}

运行它,您将看到对象已写入数据库,并且——最棒的是——您在打印生成的 Person 对象时获得了时髦的新 toString() 结果!

我们才刚刚开始触及 Java 14 中所有新功能的表面!该语言中有大量新功能,我们已在本视频中开始介绍,运行时本身也有更多安全性和性能方面的新功能。我非常强烈地建议您摆脱旧版本的 JDK(我指的是 Java 8 用户!),迁移到最新的版本。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有