Spring 加载时织入(Load-Time Weaving)

简介

本文将带你了解 Spring 加载时织入(Load-Time Weaving)是如何工作的,以便在运行时应用 Hibernate 字节码增强机制。

一般来说,字节码增强机制是在使用 Maven 或 Gradle 插件构建项目时应用的。

Domain Model

假设有以下 Attachment 实体,如下:

@Entity
@Table(name = "attachment")
public class Attachment {
 
    @Id
    private Long id;
    
    private String name;
    
    @Enumerated
    @Column(name = "media_type")
    private MediaType mediaType;
    
    @Lob
    @Column(columnDefinition="BLOB")
    @Basic(fetch = FetchType.LAZY)
    private byte[] content;
 
    // get、set 和其他方法省略
}

content 属性使用的是 FetchType.LAZY 抓取策略,但在 POJO 实体上无法懒加载实体属性,因此需要 Hibernate 字节码增强机制来实现这一目标。

Hibernate 字节码增强机制

Hibernate 字节码增强机制允许我们更改 JPA 实体的字节码,这样就可以拦截 getter 和 setter 方法调用,从而达到以下目的

  • 懒加载属性
  • 记录实体的修改

如上所述,字节码增强机制是通过 Maven 或 Gradle 插件配置的,该插件会在项目构建时增强实体类。

不过,这并不是可以使用的唯一方法,还可以在项目启动时在运行时对 JPA 实体类进行检测。

Spring Data JPA 加载时织入

要在 Spring 应用时启用运行时字节码增强机制,必须在 Spring 配置中添加 @EnableLoadTimeWeaving 注解:

@EnableLoadTimeWeaving
public class BytecodeEnhancementConfiguration {
    ...
}

现在,需要通过 javaagent 参数向 JVM 提供 Spring Instrumentation 库。如果使用的是 Windows,参数如下:

-javaagent:%M2_REPOSITORY%\org\springframework\spring-instrument\%SPRING_VERSION%\spring-instrument-%SPRING_VERSION%.jar

测试

加载 Attachment 实体并访问 content 属性:

Attachment book = attachmentRepository.findById(1L).orElseThrow(null);
 
LOGGER.debug("Fetched book: {}", book.getName());
 
assertArrayEquals(readBytes(bookFilePath), book.getContent());

可以看到,第一个 SQL 查询并没有获取 content 列,而是在第二个 SQL 查询中按需获取:

SELECT
    a.id,
    a.media_type,
    a.name
FROM
    attachment a
WHERE
    a.id = 1
 
-- Fetched book: High-Performance Java Persistence
 
SELECT
    a.content
FROM
    attachment a
WHERE
    a.id = 1

总结

如果我们想在无法访问项目构建的情况下增强 JPA 实体类,那么就可以使用 Spring 加载时织入机制。

@EnableLoadTimeWeaving 注解告诉 Spring 注册一个 LoadTimeWeaver Bean,该 Bean 将提供给 JPA EntityManagerFactory 以增强加载的 JPA 实体,从而使我们有机会启用字节码增强的延迟加载或脏数据跟踪机制。


Ref:https://vladmihalcea.com/spring-load-time-weaving/