领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在过去几年中,我们看到整个 Java 生态系统大量投入,以减少应用程序启动时间。主要关注点围绕着预先编译 (Ahead-of-Time, AOT) 优化。无论是将代码压缩成 GraalVM 本机可执行文件,还是使用协调检查点恢复 (Coordinated Restore at Checkpoint, CRaC) 捕获已优化的字节码,亦或是类数据共享 (Class Data Sharing, CDS) 或其最新继任者 AOT 缓存(Leyden 项目的一部分)。尽管不同方法的入门门槛各不相同,但它们都将性能优化从运行时转移到更早的阶段,例如构建时或单独的打包步骤。
Spring 产品组合已为您准备就绪:无论您选择哪个方向,它都能为您提供支持
随着 Spring Data 4.0(如果您喜欢按日历版本命名,则是 2025.1 发布列车),我们将您的仓库带入了 AOT。我们将所有在应用程序启动时进行的仓库准备工作转移到构建时。
简而言之,当设置 spring.aot.repositories.enabled=true 配置属性时,我们的 AOT 处理会通过依赖仓库的特定存储特性,将您的仓库查询方法转换为实际的源代码。生成的查询方法包含您不使用 Spring Data 运行查询时将编写的完全相同的代码。然后,生成的源代码会与您的应用程序一起编译,并支持仓库接口。
想象一个如下所述的宠物主人仓库。
仓库本身不继承 CrudRepository 等基础仓库的任何功能,以将暴露的功能保持在最低限度。尽管如此,save 方法与预定义方法之一的签名匹配,而列出的两个查询方法则使用派生和显式注解方法。
interface OwnerRepository extends Repository<Owner, Integer> {
Owner save(Owner owner);
List<OwnerSummary> findAllByLastName(String lastName);
@Transactional(readOnly = true)
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%")
Page<Owner> findByLastName(@Param("lastName") String lastName, Pageable pageable);
// ...
}
在 AOT 阶段,基础设施将只考虑与代码生成相关的部分。以前面提到的 save 方法为例:由于我们在这里使用 JPA,SimpleJpaRepository 已经为 save 方法提供了默认实现,允许代码生成跳过该方法。对于您的任何自定义实现也是如此。然而,OwnerRepository 的其余两个方法当然是 AOT 优化的对象,最终会出现在与源 OwnerRepository 相同包中的 OwnerRepositoryImpl__Aot 中。
@Generated
public class OwnerRepositoryImpl__Aot extends AotRepositoryFragmentSupport {
private final EntityManager entityManager;
public OwnerRepositoryImpl__Aot(EntityManager entityManager,
RepositoryFactoryBeanSupport.FragmentCreationContext context) {
// ...
}
/**
* AOT generated implementation of {@link OwnerRepository#findAllByLastName(String)}.
*/
public List<OwnerSummary> findAllByLastName(String lastName) {
String queryString = "SELECT o.firstName AS firstName, o.lastName AS lastName, o.city AS city FROM org.springframework.samples.petclinic.owner.Owner o WHERE o.lastName = :lastName";
Query query = this.entityManager.createQuery(queryString, Tuple.class);
query.setParameter("lastName", lastName);
return (List<OwnerSummary>) convertMany(query.getResultList(), false, OwnerSummary.class);
}
/**
* AOT generated implementation of {@link OwnerRepository#findByLastName(String,Pageable)}.
*/
public Page<Owner> findByLastName(String lastName, Pageable pageable) {
String queryString = "SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName";
String countQueryString = "SELECT count(DISTINCT owner) FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName";
if (pageable.getSort().isSorted()) {
DeclaredQuery declaredQuery = DeclaredQuery.jpqlQuery(queryString);
queryString = rewriteQuery(declaredQuery, pageable.getSort(), Owner.class);
}
Query query = this.entityManager.createQuery(queryString);
query.setParameter("lastName", "%s%%".formatted(lastName));
if (pageable.isPaged()) {
query.setFirstResult(Long.valueOf(pageable.getOffset()).intValue());
query.setMaxResults(pageable.getPageSize());
}
LongSupplier countAll = () -> {
Query countQuery = this.entityManager.createQuery(countQueryString);
countQuery.setParameter("lastName", "%s%%".formatted(lastName));
return (Long) countQuery.getSingleResult();
};
return PageableExecutionUtils.getPage((List<Owner>) query.getResultList(), pageable, countAll);
}
}
如您所见,生成的代码可能非常简单,也可能随着查询、参数绑定或请求的数据及其表示而变得复杂。在应用程序启动时,AOT 生成的类会被连接到支持为仓库接口创建的代理的仓库组合中。因此,您第一次可以实际看到并进入在仓库接口上调用方法时运行的代码。
除了可调试性之外,预生成代码还有助于解析查询和探索假设。它缩短了仓库引导期间的代码路径,从而加快了整体应用程序启动速度并减少了内存占用。
根据底层数据存储的不同,这种减少可能相当显著,例如对于 Spring Data JPA,它在已经更高效的 AOT 优化之上,还能带来额外的启动速度提升和更少的内存占用。
预先编译仓库目前是一个预览功能,其第一个版本适用于 JPA(仅通过 Hibernate)和 MongoDB,更多模块将在即将发布的里程碑中跟进。
请试用这个新功能,并随时与我们联系,告诉我们您的想法。