本篇文章用于介绍Spring Data Jpa数据库操作审计框架的启动过程。本文中使用的示例同如何对数据库操作进行审计?。
背景
在数据库审计框架内部原理解析一文中,我们曾介绍过Spring Data Jpa数据库操作审计的执行过程:
用户在页面上提交创建评论的请求,请求到达服务器端。在服务器端处理后,最终调用
SimpleJpaRepository
,触发EntityManager
(即SessionImpl
)的persist()
方法,完成数据的持久化。SessionImpl
内部保存了一个映射表。该映射表要求:如果保存的实体类是Comment
类型的,在persist()
方法执行前,则需要先执行AuditingEntityListener
的touchForCreate()
的方法。这个
touchForCreate()
方法,完成了将创建人、创建时间、修改人、修改时间四个字段补全的操作。
本文主要介绍:
AuditingEntityListener
对象的加载过程SessionImpl
对象的创建过程
AuditingEntityListener
AuditingEntityListener
是一个bean对象,使用Spring IoC容器进行管理。我们先研究下AuditingEntityListener
对象是如何加载的。
在Application
类上,我们使用了@EnableJpaAuditing
注解标注了这个配置类,看一下@EnableJpaAuditing
这个注解的源代码:
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(JpaAuditingRegistrar.class)
public @interface EnableJpaAuditing {
...
}
很明显,起作用的是@Import(JpaAuditingRegistrar.class)
这句。
熟悉Spring框架的话就会知道:JpaAuditingRegistrar
一定是个配置类。JpaAuditingRegstrar
的继承链如下:
所以JpaAuditingRegistrar
是一个ImportBeanDefinitionRegistrar
类型的配置类。我们曾经说过,ImportBeanDefinitionRegistrar
的核心逻辑是在registerBeanDefinitions()
方法中,该方法向IoC容器中写入了相关bean的定义。
JpaAuditingRegistrar
使用下面的方法,向IoC容器中写入AuditingEntityListener
类型的bean对象定义:
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(JPA_MAPPING_CONTEXT_BEAN_NAME)) {
registry.registerBeanDefinition(JPA_MAPPING_CONTEXT_BEAN_NAME, //
new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AuditingEntityListener.class);
builder.addPropertyValue("auditingHandler",
ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), null));
registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), AuditingEntityListener.class.getName(), registry);
}
需要注意的是builder.addPropertyValue("auditingHandler", ...)
这句。这句完成了手动设置依赖关系的操作。要求AuditingEntityListener
对象在实例化时,需要从IoC容器中查找名为auditingHandler
的对象,并将其注入。
到这里,AuditingEntityListener
在IoC容器中的实例化逻辑,以及将AuditingHandler
注入到AuditingEntityListener
中的逻辑就都有了。
AuditingHandler
调用的是AuditingBeanDefinitionRegistrarSupport
中的这个方法:
private AbstractBeanDefinition registerAuditHandlerBeanDefinition(BeanDefinitionRegistry registry,
AuditingConfiguration configuration) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
AbstractBeanDefinition ahbd = getAuditHandlerBeanDefinitionBuilder(configuration).getBeanDefinition();
registry.registerBeanDefinition(getAuditingHandlerBeanName(), ahbd);
return ahbd;
}
这个方法向IoC容器中注入了一个AuditingHandler
对象。该方法调用的方法中设置了bean定义中的相关属性:
protected BeanDefinitionBuilder configureDefaultAuditHandlerAttributes(AuditingConfiguration configuration,
BeanDefinitionBuilder builder) {
if (StringUtils.hasText(configuration.getAuditorAwareRef())) {
builder.addPropertyValue(AUDITOR_AWARE,
createLazyInitTargetSourceBeanDefinition(configuration.getAuditorAwareRef()));
} else {
builder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
}
builder.addPropertyValue(SET_DATES, configuration.isSetDates());
builder.addPropertyValue(MODIFY_ON_CREATE, configuration.isModifyOnCreate());
if (StringUtils.hasText(configuration.getDateTimeProviderRef())) {
builder.addPropertyReference(DATE_TIME_PROVIDER, configuration.getDateTimeProviderRef());
} else {
builder.addPropertyValue(DATE_TIME_PROVIDER, CurrentDateTimeProvider.INSTANCE);
}
builder.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE);
return builder;
}
重点注意的是builder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE)
这句,表示使用byType类型进行依赖注入。
在AuditingHandler
中有个方法:
public void setAuditorAware(AuditorAware> auditorAware) {
Assert.notNull(auditorAware, "AuditorAware must not be null!");
this.auditorAware = Optional.of(auditorAware);
}
由于AuditingHandler
使用byType类型进行依赖注入,若IoC容器中存在类型为AuditorAware
的对象,则会被注入到AuditingHandler
中。
我们在Application
中,使用内部配置类的方式创建了一个类型是SpringSecurityAuditorAware
的bean对象,它继承自AuditorAware
。
所以,AuditingHandler
中注入的AuditorAware
对象就是它了。
@Component
static class SpringSecurityAuditorAware implements AuditorAware<String> {
public Optional getCurrentAuditor() {
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) return Optional.of("");
Authentication authentication = context.getAuthentication();
if (authentication == null) return Optional.of("");
if (!authentication.isAuthenticated()) return Optional.of("");
return Optional.of(authentication.getName());
}
}
到这里,向IoC容器中注入AuditingHandler
并将AuditorAware
类型对象作为AuditingHandler
的属性注入的逻辑就完成了。
总结一下:AuditingEntityListener
对象内部注入了AuditingHandler
对象,AuditingHandler
内部又注入了AuditorAware
对象。这个AuditorAware
是我们自定义的类,里面包含了获取用户信息的代码逻辑。
LocalContainerEntityManagerFactoryBean
Spring Data Jpa底层使用的是Hibernate。关于SessionImpl
的加载过程会稍微复杂些,我们要先介绍一些其他的概念才行。
在Spring Boot项目的spring.factories文件中的EnableAutoConfiguration列表中,包含了一个名为HibernateJpaAutoConfiguration
的配置类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {
}
它引入了另一个名为HibernateJpaConfiguration
的配置类。
HibernateJpaConfiguration
的父类JpaBaseConfiguration
中定义了一个bean对象:
@Bean
@Primary
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) {
Map vendorProperties = getVendorProperties();
customizeVendorProperties(vendorProperties);return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()).properties(vendorProperties)
.mappingResources(getMappingResources()).jta(isJta()).build();
}
LocalContainerEntityManagerFactoryBean
就是在这个地方引入IoC容器中的。
LocalContainerEntityManagerFactoryBean
的父类AbstractEntityManagerFactoryBean
中有一个nativeEntityManagerFactoryFuture
属性:
private Future nativeEntityManagerFactoryFuture;
它使用bean生命周期方法afterPropertiesSet()
进行初始化:
@Override
public void afterPropertiesSet() throws PersistenceException {
...
AsyncTaskExecutor bootstrapExecutor = getBootstrapExecutor();
if (bootstrapExecutor != null) {
this.nativeEntityManagerFactoryFuture = bootstrapExecutor.submit(this::buildNativeEntityManagerFactory);
}
else {
this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
}
...
}
buildNativeEntityManagerFactory()
方法代码如下:
private EntityManagerFactory buildNativeEntityManagerFactory() {
EntityManagerFactory emf;
try {
emf = createNativeEntityManagerFactory();
}
catch (PersistenceException ex) {
...
}
...
}
调用createNativeEntityManagerFactory()
方法创建了EntityManagerFactory
,这个EntityManagerFactory
是一个SessionFactoryImpl
类型的对象。后续所有SessionImpl
的创建,都是使用这个工厂类完成的:
@Override
protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
...
EntityManagerFactory emf = provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap());
postProcessEntityManagerFactory(emf, this.persistenceUnitInfo);
return emf;
}
EntityManagerFactory
的构造过程结合了Hibernate框架,我们就不再往下分析了。
下面我们将程序启动起来,对EntityManagerFactory
的内部结构进行介绍。
首先在上面方法的返回语句上增加断点,从下图中圈出来的位置可以确认创建的EntityManagerFactory
对象是SessionFactoryImpl
类型的对象:
展开emf,可以看到其内部保存了一个名为fastSessionServices的对象:
继续展开,可以看fastSessionServices内部结构如下:
将eventListenerGroup_PERSIST展开:
上面这个结构就很熟悉了,在数据库审计框架内部原理解析一文中,我们给出的SessionImpl
的截图中就包含着相似的结构。
到这里,我们总结下:
Spring Boot应用在引入spring-boot-starter-jpa启动器时,会从配置类中加载一个类型为
LocalContainerEntityManagerFactoryBean
的bean对象;LocalContainerEntityManagerFactoryBean
对象的内部保存了一个类型为Future
的future对象;这个
EntityManagerFactory
是一个SessionFactoryImpl
类型的实例,它的内部有一个fastSessionServices属性,这个属性中保存了数据库入库持久化时,需要调用的回调方法;通过
SessionFactoryImpl
中的fastSessionServices属性,可以关联到AuditingEntityListener
。结合上面介绍的AuditingEntityListener
的结构,我们编写的SpringSecurityAuditorAware
终将被调用,Jpa框架获取到用户身份信息并持久化到数据库中。
OpenEntityManagerInViewInterceptor
还有最后一块拼图,介绍完之后整个框架的逻辑就完整了。
上面我们介绍了SessionFactoryImpl
的内部结构,但数据持久化不是通过SessionFactoryImpl
处理的,而是通过SessionImpl
。那这部分是如何处理的呢?
首先在JpaBaseConfiguration
中包含了一个内部配置内JpaWebConfiguration
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(WebMvcConfigurer.class)
@ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class })
@ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
@ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
protected static class JpaWebConfiguration {
}
这个JpaWebConfiguration
内部向IoC容器注入了一个OpenEntityManagerInViewInterceptor
对象:
@Bean
public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
if (this.jpaProperties.getOpenInView() == null) {
logger.warn("spring.jpa.open-in-view is enabled by default. "
+ "Therefore, database queries may be performed during view "
+ "rendering. Explicitly configure spring.jpa.open-in-view to disable this warning");
}
return new OpenEntityManagerInViewInterceptor();
}
这个OpenEntityManagerInViewInterceptor
实现了WebRequestInterceptor
。在Spring WebMvc中,凡是实现了WebRequestInterceptor
接口的bean对象,都会以类似于AOP切面的形式包装在@Controller
方法外围。这个OpenEntityManagerInViewInterceptor
也不例外。
OpenEntityManagerInViewInterceptor
的preHandle()
方法中,调用了createEntityManager()
创建SessionImpl
对象,并将SessionImpl
包装成EntityManagerHolder
保存在ThreadLocal
中。
@Override
public void preHandle(WebRequest request) throws DataAccessException {
...
EntityManagerFactory emf = obtainEntityManagerFactory();
if (TransactionSynchronizationManager.hasResource(emf)) {
...
}
else {
...
try {
EntityManager em = createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
...
}
catch (PersistenceException ex) {
...
}
}
}
SessionImpl
继承自AbstractSharedSessionContract
。
AbstractSharedSessionContract
的初始化方法中,将SessionFactoryImpl
中的fastSessionServices拷贝到了其内部的fastSessionServices中。
public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) {
this.factory = factory;
this.fastSessionServices = factory.getFastSessionServices();
...
}
}
下面这张图展示了在OpenEntityManagerInViewInterceptor
创建完成SessionImpl
对象时,其内部的结构。可以看到,它保存了从SessionFactoryImpl
对象中复制的fastSessionServices属性。
总结
到这里,数据库操作审计框架启动过程就介绍完了。主要是:
在IoC容器中,完成
AuditingEntityListener
、AuditingHandler
和AuditorAware
的初始化,并维护它们三者的引用关系;使用
LocalContainerEntityManagerFactoryBean
维护了一个SessionFactoryImpl
类型的对象,该对象内部保存了所有实体类(Entity
)在持久化时,需要调用的回调方法;使用
OpenEntityManagerInViewInterceptor
,在HTTP请求到来后,通过SessionFactoryImpl
创建了SessionImpl
对象;
后面的处理过程在数据库审计框架内部原理解析已经介绍过了。