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:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.data.jpa.aot.DataJpaRuntimeHints
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user