在项目开发中,用到了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{
...
@CreatedBy
private String createBy;
@CreatedDate
private Date createdAt;
...
}
② 监听器类
@Component
public class AuditingEntityListener {
@Autowired
private AuditorAware<String> auditorAware;
@PrePersist
public 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.
*/
@Override
public 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;
@Autowired
public void init(AuditorAware<String> auditorAware) {
AuditingEntityListener.auditorAware = auditorAware;
LOGGER.info("Initializing AuditingEntityListener.auditorHandler with dependency [" + auditorAware + "]");
}
@PrePersist
public void onCreate(Object object) {
try {
BeanUtils.setProperty(object, "createdAt", new Date());
BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());
} cache(Exception e) {
//....
}
}
}
这个方案就是在回调函数调用时,直接从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);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
SpringBeanUtil.context = applicationContext;
}
}
② 监听器类
// @Component 无需加该注解
public class AuditingEntityListener {
@PrePersist
public 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);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
② 监听器类
@Component // 必须加该注解
public class AuditingEntityListener {
@Autowired
private AuditorAware<String> auditorAware;
@PrePersist
public void onCreate(Object object) {
try {
// 借助工具类执行注入操作,实现自动装配
AutowireHelper.autowire(this);
BeanUtils.setProperty(object, "createdAt", new Date());
BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());
} cache(Exception e) {
//....
}
}
}
该方案就是将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容器自动注入。详见:
该方案主要借助@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 {
@Nullable
private 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;
}
@PrePersist
public void onCreate(Object object) {
try {
BeanUtils.setProperty(object, "createdAt", new Date());
BeanUtils.setProperty(object, "createBy", auditorAware.getCurrentAuditor());
} cache(Exception e) {
//....
}
}
}
② 切面实现
class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
...
...
@Override
public 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
的实现,可自行看源码。
在编程的世界里,蕴含着丰富的知识等着我们去挖掘,遇到的问题是我们最好的老师,解决问题的过程是促进我们学习成长的宝贵经验。解决问题不是关键,关键是通过这个过程,我们学到了什么?是否搞清弄懂其原理。