【JPA】将Spring依赖项注入JPA EntityListener

news/2025/1/15 18:05:04/

背景说明

在项目开发中,用到了JPA监听器EntityListener,并且需要将Spring依赖注入(inject)到JPA实体监听器中。但是,在调用实体监听器回调方法时,使用该Bean实例却报空指针异常错误。
于是翻阅点资料和源码,发现虽然实体监听器加上了@Component,Spring也会实例化并且注入它的相关依赖,但是JPA真正使用的实体监听器仍将由new操作(诸如Hibernate之类的框架)执行,并不会直接取用Spring容器中的Bean实例,所以才会出现上述与预期不符的情况出现。

注解只是元数据:如果没有人解释该元数据,则它不会增加任何行为或对正在运行的程序产生任何影响。

框架版本

SpringBoot2.2.13.RELEASE
Spring5.2.20
Spring Data JPA:2.2.12.RELEASE
Hibernate5.4.27.Final
javax.persistence:2.2

示例代码

① 实体类

@Entity
@Table(...)
@EntityListeners({AuditingEntityListener.class})
public class EntityModel{...@CreatedByprivate String createBy;@CreatedDateprivate Date createdAt;...
}

② 监听器类

@Component
public class AuditingEntityListener {@Autowiredprivate AuditorAware<String> auditorAware;@PrePersistpublic void onCreate(Object object) {try {BeanUtils.setProperty(object, "createdAt", new Date());BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());} cache(Exception e) {//....}}
}

③ 操作人接口

public class MyAuditorAware implements AuditorAware<String> {/*** Returns the current auditor of the application.** @return the current auditor.*/@Overridepublic Optional<String> getCurrentAuditor() {return Optional.of("测试用户");}
}

解决方案

方案一:静态依赖

这个非常好理解,即将Bean和其依赖项的依赖关系定义为“静态”,创建一个setter方法,以便Spring可以注入其依赖项。示例如下:
① 将依赖项声明为静态的

private static AuditorAware<String> auditorAware;

② 新增init方法,以便Spring容器可以注入依赖项

@Autowired
public void init(AuditorAware<String> auditorAware) {AuditingEntityListener.auditorAware = auditorAware;LOGGER.info("Initializing AuditingEntityListener.auditorHandler with dependency [" + auditorAware + "]");
}

完整代码

@Component // 必须加
public class AuditingEntityListener {private static AuditorAware<String> auditorAware;@Autowiredpublic void init(AuditorAware<String> auditorAware) {AuditingEntityListener.auditorAware = auditorAware;LOGGER.info("Initializing AuditingEntityListener.auditorHandler with dependency [" + auditorAware + "]");}@PrePersistpublic void onCreate(Object object) {try {BeanUtils.setProperty(object, "createdAt", new Date());BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());} cache(Exception e) {//....}}
}

方案二:使用ApplicationContext获取Bean实例

这个方案就是在回调函数调用时,直接从SpringContext中获取依赖项 【函数变量】。示例如下:
① SpringBeanUtil工具类:

@Component
public class SpringBeanUtil implements ApplicationContextAware {private static ApplicationContext context;private SpringBeanUtil() {}public static <T> T getBean(Class<T> clazz) throws BeansException {Assert.state(context != null, "Spring context in the SpringBeanUtil is not been initialized yet!");return context.getBean(clazz);}@Overridepublic void setApplicationContext(final ApplicationContext applicationContext) {SpringBeanUtil.context = applicationContext;}
}

② 监听器类

// @Component 无需加该注解
public class AuditingEntityListener {@PrePersistpublic void onCreate(Object object) {try {// 借助工具类从SpringContext获取实例AuditorAware<String> auditorAware = SpringBeanUtil.getBean(AuditorAware.class);BeanUtils.setProperty(object, "createdAt", new Date());BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());} cache(Exception e) {//....}}
}

方案三:回调方法使用时注入依赖项

这个方案就是在回调函数调用时,先注入依赖项 【类变量】,和方案二有异曲同工之妙。示例如下:
① AutowireHelper工具类:

@Component
public class AutowireHelper implements ApplicationContextAware {private static ApplicationContext applicationContext;private AutowireHelper() {}public static void autowire(Object classToAutowire) {AutowireHelper.applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);}@Overridepublic void setApplicationContext(final ApplicationContext applicationContext) {AutowireHelper.applicationContext = applicationContext;}
}

② 监听器类

@Component // 必须加该注解
public class AuditingEntityListener {@Autowiredprivate AuditorAware<String> auditorAware;@PrePersistpublic void onCreate(Object object) {try {// 借助工具类执行注入操作,实现自动装配AutowireHelper.autowire(this);BeanUtils.setProperty(object, "createdAt", new Date());BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());} cache(Exception e) {//....}}
}

方案四:JPA数据源配置

该方案就是将SpringBeanContainer注册到Hibernate,我们只需要通过设置适当的配置属性告诉 Hibernate使用Spring作为bean提供者即可。
① EntityManagerFactory配置

@Configuration
public class JpaConfig {@Bean(name = "entityManagerFactory")public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, EntityManagerFactoryBuilder builder, ConfigurableListableBeanFactory beanFactory) {return builder.dataSource(dataSource) //.packages("com.xxx.jpa") //.persistenceUnit("myPersistenceUnit") //.properties(Map.of(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory))) // 设置使用Spring作为bean提供者.build();}
}

另外一种写法:

@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean customCartEntityManagerFactory(DataSource customCartDataSource,EntityManagerFactoryBuilder builder,ConfigurableListableBeanFactory beanFactory) {LocalContainerEntityManagerFactoryBean mf = builder.dataSource(customCartDataSource).packages("com.my.domain").persistenceUnit("myPersistenceUnit").build();mf.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));return mf;
}

② 监听器类
如示例代码,无需任何修改。

从Hibernate 5.3开始,ManagedBeanRegistry可以直接使用Spring容器自动注入。详见:Support Hibernate 5.3’s ManagedBeanRegistry for dependency injection

方案五:高级做法-AOP编程织入

该方案主要借助@Configurable和Spring的AspectJ weaver作为java agent来实现。
① 标记监听器类为可配置

/*** This bean will NOT be instanciated by Spring but it should be configured by Spring* because of the {@link Configurable}-Annotation.* <p>* The configuration only works if the <code>AuditingEntityListener</code> is not used as an <code>EntityListener</code>* via the {@link javax.persistence.EntityListeners}-Annotation.**/
@Configurable // 标记可配置
public class AuditingEntityListener {@Nullableprivate ObjectFactory<AuditorAware> auditorAware;/*** Configures the {@link AuditorAware} to be used to set the current auditor on the domain types touched.** @param auditorAware must not be {@literal null}.*/public void setAuditorAware(ObjectFactory<AuditorAware> auditorAware) {Assert.notNull(auditorAware, "AuditorAware must not be null!");this.auditorAware = auditorAware;}@PrePersistpublic void onCreate(Object object) {try {BeanUtils.setProperty(object, "createdAt", new Date());BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());} cache(Exception e) {//....}}
}

② 切面实现

class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {......@Overridepublic void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");registerBeanConfigurerAspectIfNecessary(registry);super.registerBeanDefinitions(annotationMetadata, registry);registerInfrastructureBeanWithId(BeanDefinitionBuilder.rootBeanDefinition(AuditingBeanFactoryPostProcessor.class).getRawBeanDefinition(),AuditingBeanFactoryPostProcessor.class.getName(), registry);}......
}

具体代码可查看:spring-data-jpa#JpaAuditingRegistrar以及spring-aspectjs#AnnotationBeanConfigurerAspect
以上做法参考:spring-data-jpaAuditingEntityListener的实现,可自行看源码。

小结

在编程的世界里,蕴含着丰富的知识等着我们去挖掘,遇到的问题是我们最好的老师,解决问题的过程是促进我们学习成长的宝贵经验。解决问题不是关键,关键是通过这个过程,我们学到了什么?是否搞清弄懂其原理。


http://www.ppmy.cn/news/530220.html

相关文章

kubernetes部署prometheus

Prometheus是一款由SoundCloud开发的开源监控系统&#xff0c;它提供了实时监测和报警功能。它的优点包括&#xff1a; 易于安装和配置&#xff0c;可以快速地搭建起一个监控系统&#xff1b;提供了丰富的数据采集方式和查询语言&#xff0c;可以快速地构建监控指标&#xff1…

离子交换实验系统装置

离子交换实验系统 1.离子交换柱 4 根&#xff1a;透明有机玻璃材质、厚度 5mm&#xff0c;φ80mm1000mm&#xff0c;可串联或并联运行&#xff0c;配阴、阳离子交换树脂各 1 套。 2.原水箱和反冲洗水箱各 1 只&#xff1a;白色 PP 板、厚度 10mm&#xff0c;水箱底板上安装有放…

VRAY教学

所有常用VRAY版本 http://www.balang88.cn/thread-1033-1-1.html VRay Adv 1.5 RC3 渲染器和1.5 RC5 渲染器下载 http://www.balang88.cn/thread-9221-1-1.html 最新版VRay1.5Rc5&#xff08;中文版和英文版&#xff09; http://www.balang88.cn/thread-11236-1-1.html VRay Ad…

当游戏设计遇上建筑学

The Witness 预告片 「见证」一个全新的认知世界 The Witness, full of eureka moment Jonathan Blow 从复制和传播角度来说&#xff0c;游戏的覆盖面远超实体建筑。物质的世界一切都太昂贵了&#xff0c;实体建筑也越来越不重要了。如果建筑师们能像van Buren这样的美国中产知…

AV比拟

好比吃鸳鸯火锅&#xff0c;H264/H265/VP8视频可以理解成那口红锅汤料以及里面的各种食材&#xff0c;AAC/MP3语音可以理解成那口白锅汤料以及里面的各种食材。而mp4/rmvp/mkv/avi就是那个锅&#xff0c;负责把汤料和食材装好。 于是&#xff0c;鸳鸯锅有不同的款式&#xff08…

赛博朋克2077:游戏创作思路与经验

2020年最受期待的游戏之一《赛博朋克2077》几天前刚刚发布。故事背景设定在一个充满麻烦的未来&#xff0c;视觉效果令人惊叹&#xff0c;庞大的地图让我们好奇CD PROJEKT RED是如何在重塑赛博朋克风格的同时描绘出一个反乌托邦的未来的。该游戏技术艺术总监Krzysztof Krzyści…

邦纳传感器SM30SRLQD

邦纳传感器SM30SRLQD SM30 系列&#xff1a;接收器 - 频率 A 不锈钢 Stl 范围&#xff1a;150米&#xff1b;输入&#xff1a;10-30 V 直流 输出&#xff1a;双模态&#xff1a;1 NPN&#xff1b;1 PNP - LO & DO 迷你 4 针一体式 QD 零件编号&#xff1a; 27290 规格 感应…

VRAY控制溢色|速度和质量平衡|漏光蓝海创意云渲染分享

先说速度和质量怎样平衡。有的朋友用VRAY渲一张不大的场景也要好几个小时&#xff0c;中途还会报错退出3DMAX&#xff0c;主要是渲染参数设置太高&#xff0c;同时渲染图片太大造成的。 快速的做法是关闭材质和反射&#xff0c;用小图渲染保存VRMAP&#xff0c;之后打开材质和反…