Browse Source
We now pre-initialize ClassGeneratingPropertyAccessorFactory and ClassGeneratingEntityInstantiator infrastructure to generate bytecode for their respective classes so that we include the generated code for the target AOT package. Also, we check for presence of these types to conditionally load generated classes if these are on the classpath. This change required a stable class name therefore, we're hashing the fully-qualified class name and have aligned the class name from _Accessor to __Accessor (two underscores instead of one, same for Instantiator). Closes: #2595 Original Pull Request: #3318pull/3357/head
14 changed files with 507 additions and 106 deletions
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
/* |
||||
* Copyright 2025 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.aot; |
||||
|
||||
import org.springframework.data.mapping.Association; |
||||
import org.springframework.data.mapping.PersistentEntity; |
||||
import org.springframework.data.mapping.context.AbstractMappingContext; |
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; |
||||
import org.springframework.data.mapping.model.BasicPersistentEntity; |
||||
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory; |
||||
import org.springframework.data.mapping.model.EntityInstantiator; |
||||
import org.springframework.data.mapping.model.EntityInstantiators; |
||||
import org.springframework.data.mapping.model.PersistentEntityClassInitializer; |
||||
import org.springframework.data.mapping.model.Property; |
||||
import org.springframework.data.mapping.model.SimpleTypeHolder; |
||||
import org.springframework.data.util.TypeInformation; |
||||
|
||||
/** |
||||
* Simple {@link AbstractMappingContext} for processing of AOT contributions. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 4.0 |
||||
*/ |
||||
public class AotMappingContext extends |
||||
AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> { |
||||
|
||||
private final EntityInstantiators instantiators = new EntityInstantiators(); |
||||
private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); |
||||
|
||||
/** |
||||
* Contribute entity instantiators and property accessors for the given {@link PersistentEntity} that are captured |
||||
* through Spring's {@code CglibClassHandler}. Otherwise, this is a no-op if contributions are not ran through |
||||
* {@code CglibClassHandler}. |
||||
* |
||||
* @param entity |
||||
*/ |
||||
public void contribute(PersistentEntity<?, ?> entity) { |
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); |
||||
if (instantiator instanceof PersistentEntityClassInitializer pec) { |
||||
pec.initialize(entity); |
||||
} |
||||
propertyAccessorFactory.initialize(entity); |
||||
} |
||||
|
||||
@Override |
||||
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity( |
||||
TypeInformation<T> typeInformation) { |
||||
return new BasicPersistentEntity<>(typeInformation); |
||||
} |
||||
|
||||
@Override |
||||
protected BasicPersistentProperty createPersistentProperty(Property property, |
||||
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) { |
||||
return new BasicPersistentProperty(property, owner, simpleTypeHolder); |
||||
} |
||||
|
||||
static class BasicPersistentProperty extends AnnotationBasedPersistentProperty<BasicPersistentProperty> { |
||||
|
||||
public BasicPersistentProperty(Property property, PersistentEntity<?, BasicPersistentProperty> owner, |
||||
SimpleTypeHolder simpleTypeHolder) { |
||||
super(property, owner, simpleTypeHolder); |
||||
} |
||||
|
||||
@Override |
||||
protected Association<BasicPersistentProperty> createAssociation() { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2025 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.mapping.context; |
||||
|
||||
import org.springframework.data.mapping.PersistentEntity; |
||||
import org.springframework.data.mapping.PersistentPropertyAccessor; |
||||
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory; |
||||
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory; |
||||
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; |
||||
|
||||
/** |
||||
* {@link PersistentPropertyAccessorFactory} that uses {@link ClassGeneratingPropertyAccessorFactory} if |
||||
* {@link ClassGeneratingPropertyAccessorFactory#isSupported(PersistentEntity) supported} and falls back to reflection. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 4.0 |
||||
*/ |
||||
class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentPropertyAccessorFactory { |
||||
|
||||
private final ClassGeneratingPropertyAccessorFactory accessorFactory = new ClassGeneratingPropertyAccessorFactory(); |
||||
|
||||
@Override |
||||
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) { |
||||
|
||||
if (accessorFactory.isSupported(entity)) { |
||||
return accessorFactory.getPropertyAccessor(entity, bean); |
||||
} |
||||
|
||||
return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isSupported(PersistentEntity<?, ?> entity) { |
||||
return true; |
||||
} |
||||
} |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright 2025 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.mapping.model; |
||||
|
||||
import org.springframework.data.mapping.PersistentEntity; |
||||
|
||||
/** |
||||
* @author Mark Paluch |
||||
*/ |
||||
public interface PersistentEntityClassInitializer { |
||||
|
||||
void initialize(PersistentEntity<?, ?> entity); |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2025 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.repository.aot; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; |
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RegisteredBean; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution; |
||||
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor; |
||||
|
||||
/** |
||||
* Utility class to create {@link RepositoryRegistrationAotContribution} instances for a given configuration class. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class AotUtil { |
||||
|
||||
static RepositoryRegistrationAotContributionBuilder contributionFor(Class<?> configuration) { |
||||
return contributionFor(configuration, new AnnotationConfigApplicationContext()); |
||||
} |
||||
|
||||
static RepositoryRegistrationAotContributionBuilder contributionFor(Class<?> configuration, |
||||
AnnotationConfigApplicationContext applicationContext) { |
||||
|
||||
applicationContext.register(configuration); |
||||
applicationContext.refreshForAotProcessing(new RuntimeHints()); |
||||
|
||||
return repositoryType -> { |
||||
|
||||
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType); |
||||
|
||||
assertThat(repositoryBeanNames) |
||||
.describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration) |
||||
.hasSize(1); |
||||
|
||||
String repositoryBeanName = repositoryBeanNames[0]; |
||||
|
||||
ConfigurableListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory(); |
||||
|
||||
RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext |
||||
.getBean(RepositoryRegistrationAotProcessor.class); |
||||
|
||||
repositoryAotProcessor.setBeanFactory(beanFactory); |
||||
|
||||
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName); |
||||
|
||||
BeanRegistrationAotContribution beanContribution = repositoryAotProcessor.processAheadOfTime(bean); |
||||
|
||||
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class); |
||||
|
||||
return (RepositoryRegistrationAotContribution) beanContribution; |
||||
}; |
||||
} |
||||
|
||||
@FunctionalInterface |
||||
interface RepositoryRegistrationAotContributionBuilder { |
||||
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface); |
||||
} |
||||
} |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
/* |
||||
* Copyright 2022-2025 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.repository.aot; |
||||
|
||||
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.aot.hint.TypeReference; |
||||
import org.springframework.context.annotation.ComponentScan.Filter; |
||||
import org.springframework.context.annotation.FilterType; |
||||
import org.springframework.data.repository.CrudRepository; |
||||
import org.springframework.data.repository.config.EnableRepositories; |
||||
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution; |
||||
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor; |
||||
|
||||
/** |
||||
* Integration Tests for {@link RepositoryRegistrationAotProcessor} to verify capturing generated instantiations and |
||||
* property accessors. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class GeneratedClassesCaptureIntegrationTests { |
||||
|
||||
@Test // GH-2595
|
||||
void registersGeneratedPropertyAccessorsEntityInstantiators() { |
||||
|
||||
RepositoryRegistrationAotContribution repositoryBeanContribution = AotUtil.contributionFor(Config.class) |
||||
.forRepository(Config.MyRepo.class); |
||||
|
||||
assertThatContribution(repositoryBeanContribution) //
|
||||
.codeContributionSatisfies(contribution -> { |
||||
contribution.contributesReflectionFor(TypeReference.of( |
||||
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Accessor_xj7ohs")); |
||||
contribution.contributesReflectionFor(TypeReference.of( |
||||
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Instantiator_xj7ohs")); |
||||
|
||||
// TODO: These should also appear
|
||||
/* |
||||
contribution.contributesReflectionFor(TypeReference.of( |
||||
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Accessor_xj7ohs")); |
||||
contribution.contributesReflectionFor(TypeReference.of( |
||||
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Instantiator_xj7ohs")); |
||||
*/ |
||||
}); |
||||
} |
||||
|
||||
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Config.MyRepo.class) }, |
||||
basePackageClasses = Config.class, considerNestedRepositories = true) |
||||
public class Config { |
||||
|
||||
public interface MyRepo extends CrudRepository<Person, String> { |
||||
|
||||
} |
||||
|
||||
public static class Person { |
||||
|
||||
@Nullable Address address; |
||||
|
||||
} |
||||
|
||||
public static class Address { |
||||
String street; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue