1682 字
8 分钟
@EnableJpaRepositories 注解原理
TIP

本文涉及 Spring 扩展点,需要对 Spring 原理和扩展点有一定了解基础

@EnableJpaRepositories 源码跟踪#

在 @EnableJpaRepositories 注解中,通过 @Import 注解把 JpaRepositoriesRegistrar 注册到 IOC 容器

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({JpaRepositoriesRegistrar.class})
public @interface EnableJpaRepositories {
}

JpaRepositoriesRegistrar 继承了 RepositoryBeanDefinitionRegistrarSupport 的注册能力,它是通过实现 ImportBeanDefinitionRegistrar 接口(一般配合 @Import 使用)的 registerBeanDefinitions 方法在上下文启动时动态注册 Bean

ImportBeanDefinitionRegistrar

这个接口允许开发者在 Spring 应用启动过程中,通过编程方式注册额外的 Bean 定义

public abstract class RepositoryBeanDefinitionRegistrarSupport implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
/** @deprecated */
@Deprecated
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerBeanDefinitions(metadata, registry, ConfigurationClassPostProcessor.IMPORT_BEAN_NAME_GENERATOR);
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry, BeanNameGenerator generator) {
Assert.notNull(metadata, "AnnotationMetadata must not be null");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(this.resourceLoader, "ResourceLoader must not be null");
if (metadata.getAnnotationAttributes(this.getAnnotation().getName()) != null) {
AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, this.getAnnotation(), this.resourceLoader, this.environment, registry, generator);
RepositoryConfigurationExtension extension = this.getExtension();
RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource);
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, this.resourceLoader, this.environment);
// 在这个方法实现注册
delegate.registerRepositoriesIn(registry, extension);
}
}
}

BeanDefinition 扫描#

最后调用 registerRepositoriesIn 方法注册 Bean,通过断点和观察,发现 BeanDefinition 是先通过 extension.getRepositoryConfigurations 方法扫描出 BeanDefinition 封装成 RepositoryConfiguration,然后通过 builder.build 方法重新构建 BeanDefinition,我们先跟踪 getRepositoryConfigurations 方法看如何扫描出 BeanDefinition,再回来看 build 方法如何构建 BeanDefinition

public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension) {
if (logger.isInfoEnabled()) {
logger.info(LogMessage.format("Bootstrapping Spring Data %s repositories in %s mode.", extension.getModuleName(), this.configurationSource.getBootstrapMode().name()));
}
extension.registerBeansForRoot(registry, this.configurationSource);
RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension, this.configurationSource, this.resourceLoader, this.environment);
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.format("Scanning for %s repositories in packages %s.", extension.getModuleName(), this.configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))));
}
StopWatch watch = new StopWatch();
ApplicationStartup startup = getStartup(registry);
StartupStep repoScan = startup.start("spring.data.repository.scanning");
repoScan.tag("dataModule", extension.getModuleName());
repoScan.tag("basePackages", () -> (String)this.configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
watch.start();
// getRepositoryConfigurations 方法里扫描出 BeanDefinition 封装成 RepositoryConfiguration
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension.getRepositoryConfigurations(this.configurationSource, this.resourceLoader, this.inMultiStoreMode);
List<BeanComponentDefinition> definitions = new ArrayList();
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap(configurations.size());
Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName = new HashMap(configurations.size());
for(RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {
configurationsByRepositoryName.put(configuration.getRepositoryInterface(), configuration);
// definitionBuilder 是通过 configuration 构建 BeanDefinition,后面会回到这里分析 build 方法
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
extension.postProcess(definitionBuilder, this.configurationSource);
if (this.isXml) {
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource)this.configurationSource);
} else {
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource)this.configurationSource);
}
// beanDefinition 是从 definitionBuilder 获取
RootBeanDefinition beanDefinition = (RootBeanDefinition)definitionBuilder.getBeanDefinition();
beanDefinition.setTargetType(this.getRepositoryFactoryBeanType(configuration));
beanDefinition.setResourceDescription(configuration.getResourceDescription());
String beanName = this.configurationSource.generateBeanName(beanDefinition);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Spring Data %s - Registering repository: %s - Interface: %s - Factory: %s", extension.getModuleName(), beanName, configuration.getRepositoryInterface(), configuration.getRepositoryFactoryBeanClassName()));
}
metadataByRepositoryBeanName.put(beanName, builder.buildMetadata(configuration));
// 把 beanDefinition 注册到 BeanDefinitionRegistry
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}
potentiallyLazifyRepositories(configurationsByRepositoryName, registry, this.configurationSource.getBootstrapMode());
watch.stop();
repoScan.tag("repository.count", Integer.toString(configurations.size()));
repoScan.end();
if (logger.isInfoEnabled()) {
logger.info(LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interface%s.", watch.lastTaskInfo().getTimeMillis(), configurations.size(), extension.getModuleName(), configurations.size() == 1 ? "" : "s"));
}
this.registerAotComponents(registry, extension, metadataByRepositoryBeanName);
return definitions;
}

在 getRepositoryConfigurations 方法中已经可以看到 BeanDefinition 是从 configSource.getCandidates(loader) 中返回,继续跟踪 getCandidates 方法

public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(T configSource, ResourceLoader loader, boolean strictMatchesOnly) {
Assert.notNull(configSource, "ConfigSource must not be null");
Assert.notNull(loader, "Loader must not be null");
Set<RepositoryConfiguration<T>> result = new HashSet();
// BeanDefinition 是从 configSource.getCandidates(loader) 返回
for(BeanDefinition candidate : configSource.getCandidates(loader)) {
RepositoryConfiguration<T> configuration = this.<T>getRepositoryConfiguration(candidate, configSource);
Class<?> repositoryInterface = this.loadRepositoryInterface(configuration, this.getConfigurationInspectionClassLoader(loader));
if (repositoryInterface == null) {
result.add(configuration);
} else {
RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
boolean qualifiedForImplementation = !strictMatchesOnly || configSource.usesExplicitFilters() || this.isStrictRepositoryCandidate(metadata);
if (qualifiedForImplementation && this.useRepositoryConfiguration(metadata)) {
result.add(configuration);
}
}
}
return result;
}
public Streamable<BeanDefinition> getCandidates(ResourceLoader loader) {
// 继承了 spring 的 ClassPathScanningCandidateComponentProvider 的扫描能力,实现扫描 Repository 接口的逻辑
RepositoryComponentProvider scanner = new RepositoryComponentProvider(this.getIncludeFilters(), this.registry);
scanner.setConsiderNestedRepositoryInterfaces(this.shouldConsiderNestedRepositories());
scanner.setEnvironment(this.environment);
scanner.setResourceLoader(loader);
Streamable var10000 = this.getExcludeFilters();
Objects.requireNonNull(scanner);
var10000.forEach(scanner::addExcludeFilter);
// 通过 findCandidateComponents 方法返回扫描结果
return Streamable.of(() -> this.getBasePackages().stream().flatMap((it) -> scanner.findCandidateComponents(it).stream()));
}

通过阅读源码,RepositoryComponentProvider 的实例扫描出 BeanDefinition,最终通过 getCandidates 方法返回,我们看 RepositoryComponentProvider 这个类

class RepositoryComponentProvider extends ClassPathScanningCandidateComponentProvider {
private static boolean isGenericRepositoryInterface(@Nullable String interfaceName) {
return Repository.class.getName().equals(interfaceName);
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 扫描继承 Repository 接口的接口
boolean isNonRepositoryInterface = !isGenericRepositoryInterface(beanDefinition.getBeanClassName());
boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass();
boolean isConsiderNestedRepositories = this.isConsiderNestedRepositoryInterfaces();
return isNonRepositoryInterface && (isTopLevelType || isConsiderNestedRepositories);
}
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 扫描包路径下符合 isCandidateComponent 条件的组件
Set<BeanDefinition> candidates = super.findCandidateComponents(basePackage);
for(BeanDefinition candidate : candidates) {
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
}
return candidates;
}
}

RepositoryComponentProvider 继承了 spring 的 ClassPathScanningCandidateComponentProvider 的扫描能力,重写 isCandidateComponent 判断组件的方法,把 Repository 接口扫描出来,通过 findCandidateComponents 方法扫描指定包路径,Spring 底层对 ASM 源码进行了扩展和封装,通过读取字节码文件,解析符合条件的文件,把文件的信息封装成 BeanDefinition

ClassPathScanningCandidateComponentProvider

是 Spring 框架中一个非常核心的类,它主要用于在类路径下扫描并发现带有特定注解的组件

到此,我们知道了 JPA 是如何实现把 Repository 接口扫描出来,但 IOC 容器只能注册实例,接口无法实例化,带着疑问我们回到之前构建 BeanDefinition 的地方

BeanDefinition 构建#

回到刚才 registerRepositoriesIn 方法里面的 builder.build 方法,继续跟踪 BeanDefinition 的构建,在 rootBeanDefinition 方法中会创建一个新的 BeanDefinition,并且把 beanClassName 设置为 JpaRepositoryFactoryBean

public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {
Assert.notNull(this.registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(this.resourceLoader, "ResourceLoader must not be null");
// rootBeanDefinition 会创建一个新的 BeanDefinition,并且设置 beanClassName 为 JpaRepositoryFactoryBean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName());
builder.getRawBeanDefinition().setSource(configuration.getSource());
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.setLazyInit(configuration.isLazyInit());
builder.setPrimary(configuration.isPrimary());
configuration.getRepositoryBaseClassName().ifPresent((it) -> builder.addPropertyValue("repositoryBaseClass", it));
NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(this.extension.getDefaultNamedQueryLocation());
Optional var10000 = configuration.getNamedQueriesLocation();
Objects.requireNonNull(definitionBuilder);
var10000.ifPresent(definitionBuilder::setLocations);
String namedQueriesBeanName = BeanDefinitionReaderUtils.uniqueBeanName(this.extension.getModuleIdentifier() + ".named-queries", this.registry);
BeanDefinition namedQueries = definitionBuilder.build(configuration.getSource());
this.registry.registerBeanDefinition(namedQueriesBeanName, namedQueries);
builder.addPropertyValue("namedQueries", new RuntimeBeanReference(namedQueriesBeanName));
this.registerCustomImplementation(configuration).ifPresent((it) -> {
builder.addPropertyReference("customImplementation", it);
builder.addDependsOn(it);
});
String fragmentsBeanName = this.registerRepositoryFragments(configuration);
builder.addPropertyValue("repositoryFragments", new RuntimeBeanReference(fragmentsBeanName));
return builder;
}
public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName) {
return rootBeanDefinition(beanClassName, (String)null);
}
public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName, @Nullable String factoryMethodName) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new RootBeanDefinition());
// 设置 beanClassName 为 JpaRepositoryFactoryBean
builder.beanDefinition.setBeanClassName(beanClassName);
builder.beanDefinition.setFactoryMethodName(factoryMethodName);
return builder;
}

通过断点观察,新建的 BeanDefinition 的 beanClassName 都会被设置成 JpaRepositoryFactoryBean

图1

JpaRepositoryFactoryBean#

观察 JpaRepositoryFactoryBean 这个类,通过继承 RepositoryFactoryBeanSupport 实现了 FactoryBean、InitializingBean、BeanClassLoaderAware 等扩展接口

图2

RepositoryFactoryBeanSupport#

阅读 RepositoryFactoryBeanSupport 的源码,它主要通过实现 InitializingBeanafterPropertiesSet 方法,在 Bean 初始化完成后通过动态代理生成 SimpleJpaRepository 的代理对象交给 this.repository 成员变量,然后实现 FactoryBean 接口的 getObject 方法,返回这个代理对象

经常使用 JPA 的人应该对 SimpleJpaRepository 这个代理类不陌生,平时使用 CRUD 等内置方法,就是通过它调用的

  • InitializingBean 接口是 Bean 的生命周期扩展,afterPropertiesSet 方法是在 Bean 初始化之后执行
  • FactoryBean 接口是 Spring 的扩展点,当从 IOC 容器调用 getBean 方法获取 Bean 时,实际会调用 getObject 方法获取

所以刚才的疑问已经解决,实际注册到 IOC 容器的是 SimpleJpaRepository 这个代理类的实例

public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID> implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, ApplicationEventPublisherAware {
public void afterPropertiesSet() {
this.factory = this.createRepositoryFactory();
this.factory.setQueryLookupStrategyKey(this.queryLookupStrategyKey);
this.factory.setNamedQueries(this.namedQueries);
this.factory.setEvaluationContextProvider((QueryMethodEvaluationContextProvider)this.evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));
this.factory.setBeanClassLoader(this.classLoader);
this.factory.setBeanFactory(this.beanFactory);
if (this.publisher != null) {
this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(this.publisher));
}
Optional var10000 = this.repositoryBaseClass;
RepositoryFactorySupport var10001 = this.factory;
Objects.requireNonNull(var10001);
var10000.ifPresent(var10001::setRepositoryBaseClass);
this.repositoryFactoryCustomizers.forEach((customizer) -> customizer.customize(this.factory));
RepositoryComposition.RepositoryFragments customImplementationFragment = (RepositoryComposition.RepositoryFragments)this.customImplementation.map((xva$0) -> RepositoryFragments.just(new Object[]{xva$0})).orElseGet(RepositoryComposition.RepositoryFragments::empty);
RepositoryComposition.RepositoryFragments repositoryFragmentsToUse = ((RepositoryComposition.RepositoryFragments)this.repositoryFragments.orElseGet(RepositoryComposition.RepositoryFragments::empty)).append(customImplementationFragment);
this.repositoryMetadata = this.factory.getRepositoryMetadata(this.repositoryInterface);
// getRepository 方法里生成 JDK 动态代理(SimpleJpaRepository),在 getObject 方法中加载并返回
this.repository = Lazy.of(() -> (Repository)this.factory.getRepository(this.repositoryInterface, repositoryFragmentsToUse));
// 这里会校验 repository 关联的 Entity 是否存在
// Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)
this.mappingContext.ifPresent((it) -> it.getPersistentEntity(this.repositoryMetadata.getDomainType()));
if (!this.lazyInit) {
this.repository.get();
}
}
public T getObject() {
// 返回代理类
return this.repository.get();
}
}

总结#

  • @EnableJpaRepositories 注解是通过 ImportBeanDefinitionRegistrar 接口的 registerBeanDefinitions 方法在启动时动态注册 Bean
  • 通过继承 ClassPathScanningCandidateComponentProvider 重写判断组件的方法扩展 spring 的扫描能力,扫描出继承了 Repository 接口的 BeanDefinition
  • 把 Repository 接口的 BeanDefinition 替换成新建 BeanClassName 为 JpaRepositoryFactoryBean 的 BeanDefinition
  • 当 JpaRepositoryFactoryBean 初始化后调用 InitializingBean 接口的 afterPropertiesSet 方法生成 SimpleJpaRepository 的动态代理对象
  • IOC 容器 getBean 时,通过 FactoryBean 接口的 getObject 方法获取到代理对象
@EnableJpaRepositories 注解原理
https://cloop.zone.id/posts/technology/enable-jpa-repositories/
作者
Cloop
发布于
2025-04-08
许可协议
CC BY-NC-SA 4.0