数据库操作审计框架启动过程解析

​本篇文章用于介绍Spring Data Jpa数据库操作审计框架的启动过程。本文中使用的示例同如何对数据库操作进行审计?

背景

数据库审计框架内部原理解析一文中,我们曾介绍过Spring Data Jpa数据库操作审计的执行过程:

  • 用户在页面上提交创建评论的请求,请求到达服务器端。在服务器端处理后,最终调用SimpleJpaRepository,触发EntityManager(即SessionImpl)的persist()方法,完成数据的持久化。

  • SessionImpl内部保存了一个映射表。该映射表要求:如果保存的实体类是Comment类型的,在persist()方法执行前,则需要先执行AuditingEntityListenertouchForCreate()的方法。

  • 这个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<String>

所以,AuditingHandler中注入的AuditorAware对象就是它了。

    @Component
    static class SpringSecurityAuditorAware implements AuditorAware<String> {
        public Optional<String> 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<String, Object> 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<EntityManagerFactory> 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<EntityManagerFactory>的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也不例外。

OpenEntityManagerInViewInterceptorpreHandle()方法中,调用了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容器中,完成AuditingEntityListenerAuditingHandlerAuditorAware的初始化,并维护它们三者的引用关系;

  • 使用LocalContainerEntityManagerFactoryBean维护了一个SessionFactoryImpl类型的对象,该对象内部保存了所有实体类(Entity)在持久化时,需要调用的回调方法;

  • 使用OpenEntityManagerInViewInterceptor,在HTTP请求到来后,通过SessionFactoryImpl创建了SessionImpl对象;

后面的处理过程在数据库审计框架内部原理解析已经介绍过了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值