背景说明
在项目开发中,用到了JPA监听器EntityListener
,并且需要将Spring依赖注入(inject)
到JPA实体监听器中。但是,在调用实体监听器回调方法时,使用该Bean实例却报空指针异常错误。
于是翻阅点资料和源码,发现虽然实体监听器加上了@Component
,Spring也会实例化并且注入它的相关依赖,但是JPA真正使用的实体监听器仍将由new
操作(诸如Hibernate
之类的框架)执行,并不会直接取用Spring容器中的Bean实例,所以才会出现上述与预期不符的情况出现。
注解只是元数据:如果没有人解释该元数据,则它不会增加任何行为或对正在运行的程序产生任何影响。
框架版本
SpringBoot:2.2.13.RELEASE
Spring:5.2.20
Spring Data JPA:2.2.12.RELEASE
Hibernate:5.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-jpa
中AuditingEntityListener
的实现,可自行看源码。
小结
在编程的世界里,蕴含着丰富的知识等着我们去挖掘,遇到的问题是我们最好的老师,解决问题的过程是促进我们学习成长的宝贵经验。解决问题不是关键,关键是通过这个过程,我们学到了什么?是否搞清弄懂其原理。