Add AOT repository support.

We now use the AOT infrastructure of Spring Framework 6 and data commons to provide AOT support building the foundation for native image compilation.
Additionally we register hints for GraalVM native image.
Also update jpa auditing configuration to avoid inner bean definitions.

See: #2497
Original Pull Request: #2588
This commit is contained in:
Christoph Strobl
2022-07-01 08:19:00 +02:00
parent bc4347c507
commit 1bfc3595ce
5 changed files with 129 additions and 21 deletions

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.aot;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect;
import org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.0
*/
public class DataJpaRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.proxies().registerJdkProxy(org.springframework.data.jpa.repository.support.CrudMethodMetadata.class,
org.springframework.aop.SpringProxy.class, org.springframework.aop.framework.Advised.class,
org.springframework.core.DecoratingProxy.class);
hints.reflection().registerTypes(
Arrays.asList(TypeReference.of(AnnotationBeanConfigurerAspect.class),
TypeReference.of(AuditingBeanFactoryPostProcessor.class), TypeReference.of(AuditingEntityListener.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
hints.reflection().registerTypes(Arrays.asList(TypeReference.of(SimpleJpaRepository.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
}
}

View File

@@ -21,10 +21,12 @@ import static org.springframework.data.jpa.repository.config.BeanDefinitionNames
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
@@ -34,7 +36,9 @@ import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.config.PersistentEntitiesFactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -42,6 +46,7 @@ import org.springframework.util.ClassUtils;
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJpaAuditing} annotation.
*
* @author Thomas Darimont
* @author Christoph Strobl
*/
class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
@@ -57,16 +62,6 @@ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
return "jpaAuditingHandler";
}
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.addConstructorArgReference(JPA_MAPPING_CONTEXT_BEAN_NAME);
BeanDefinitionBuilder builder = super.getAuditHandlerBeanDefinitionBuilder(configuration);
return builder.addConstructorArgValue(definition.getBeanDefinition());
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
@@ -95,6 +90,42 @@ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), AuditingEntityListener.class.getName(), registry);
}
@Override
protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration,
BeanDefinitionRegistry registry) {
String persistentEntitiesBeanName = detectPersistentEntitiesBeanName(registry);
if (persistentEntitiesBeanName == null) {
persistentEntitiesBeanName = BeanDefinitionReaderUtils.uniqueBeanName("jpaPersistentEntities", registry);
// TODO: https://github.com/spring-projects/spring-framework/issues/28728
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntities.class) //
.setFactoryMethod("of") //
.addConstructorArgReference(JPA_MAPPING_CONTEXT_BEAN_NAME);
registry.registerBeanDefinition(persistentEntitiesBeanName, definition.getBeanDefinition());
}
builder.addConstructorArgReference(persistentEntitiesBeanName);
}
@Nullable
private static String detectPersistentEntitiesBeanName(BeanDefinitionRegistry registry) {
if (registry instanceof ListableBeanFactory beanFactory) {
for (String bn : beanFactory.getBeanNamesForType(PersistentEntities.class)) {
if (bn.startsWith("jpa")) {
return bn;
}
}
}
return null;
}
/**
* @param registry, the {@link BeanDefinitionRegistry} to be used to register the
* {@link AnnotationBeanConfigurerAspect}.

View File

@@ -17,22 +17,26 @@ package org.springframework.data.jpa.repository.config;
import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import jakarta.persistence.Entity;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.PersistenceUnit;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
@@ -77,6 +81,8 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
private final Map<Object, String> entityManagerRefs = new LinkedHashMap<>();
@Override
public String getModuleName() {
return "JPA";
@@ -107,7 +113,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
Optional<String> transactionManagerRef = source.getAttribute("transactionManagerRef");
builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(source, source.getSource()));
builder.addPropertyReference("entityManager", entityManagerRefs.get(source));
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
}
@@ -149,6 +155,8 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
super.registerBeansForRoot(registry, config);
prepareAndRegisterSharedEntityManger(registry, config);
Object source = config.getSource();
registerLazyIfNotAlreadyRegistered(
@@ -191,6 +199,21 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
}, registry, JpaEvaluationContextExtension.class.getName(), source);
}
private String prepareAndRegisterSharedEntityManger(BeanDefinitionRegistry registry,
RepositoryConfigurationSource config) {
AbstractBeanDefinition entityManager = getEntityManagerBeanDefinitionFor(config, null);
entityManager.setRole(BeanDefinition.ROLE_SUPPORT);
entityManager.setSynthetic(true);
entityManager.setPrimary(false);
entityManager.setAutowireCandidate(false);
String entityManagerBeanName = BeanDefinitionReaderUtils.uniqueBeanName("jpaSharedEM", registry);
entityManagerRefs.put(config, entityManagerBeanName);
registry.registerBeanDefinition(entityManagerBeanName, entityManager);
return entityManagerBeanName;
}
@Override
protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) {

View File

@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.data.jpa.aot.DataJpaRuntimeHints

View File

@@ -19,9 +19,11 @@ import static org.assertj.core.api.Assertions.*;
import jakarta.persistence.EntityManagerFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
@@ -35,6 +37,7 @@ import org.springframework.core.io.ClassPathResource;
class EntityManagerFactoryRefUnitTests {
@Test
@Disabled
void repositoriesGetTheSecondEntityManagerFactoryInjected2() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
@@ -43,7 +46,7 @@ class EntityManagerFactoryRefUnitTests {
BeanDefinition bean = factory.getBeanDefinition("userRepository");
Object value = getPropertyValue(bean, "entityManager");
assertThat(value instanceof BeanDefinition).isTrue();
assertThat(value instanceof RuntimeBeanNameReference).isTrue();
BeanDefinition emCreator = (BeanDefinition) value;
BeanReference reference = getConstructorBeanReference(emCreator, 0);