8 changed files with 491 additions and 22 deletions
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
/* |
||||
* Copyright 2002-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.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Modifier; |
||||
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options; |
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.javapoet.support.MultiStatement; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* Support for generating {@link Field} access. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
public class BeanFieldGenerator { |
||||
|
||||
/** |
||||
* The {@link Options} to use to access a field. |
||||
*/ |
||||
public static final Options FIELD_OPTIONS = Options.defaults() |
||||
.useReflection(member -> Modifier.isPrivate(member.getModifiers())).build(); |
||||
|
||||
|
||||
/** |
||||
* Generate the necessary code to set the specified field. Use reflection |
||||
* using {@link ReflectionUtils} if necessary. |
||||
* @param field the field to set |
||||
* @param value a code representation of the field value |
||||
* @return the code to set the specified field |
||||
*/ |
||||
public MultiStatement generateSetValue(String target, Field field, CodeBlock value) { |
||||
MultiStatement statement = new MultiStatement(); |
||||
boolean useReflection = Modifier.isPrivate(field.getModifiers()); |
||||
if (useReflection) { |
||||
String fieldName = String.format("%sField", field.getName()); |
||||
statement.addStatement("$T $L = $T.findField($T.class, $S)", Field.class, fieldName, ReflectionUtils.class, |
||||
field.getDeclaringClass(), field.getName()); |
||||
statement.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName); |
||||
statement.addStatement("$T.setField($L, $L, $L)", ReflectionUtils.class, fieldName, target, value); |
||||
} |
||||
else { |
||||
statement.addStatement("$L.$L = $L", target, field.getName(), value); |
||||
} |
||||
return statement; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2002-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.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.javapoet.support.CodeSnippet; |
||||
import org.springframework.javapoet.support.MultiStatement; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link BeanFieldGenerator}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BeanFieldGeneratorTests { |
||||
|
||||
private final BeanFieldGenerator generator = new BeanFieldGenerator(); |
||||
|
||||
@Test |
||||
void generateSetFieldWithPublicField() { |
||||
MultiStatement statement = this.generator.generateSetValue("bean", |
||||
field(SampleBean.class, "one"), CodeBlock.of("$S", "test")); |
||||
assertThat(CodeSnippet.process(statement.toCodeBlock())).isEqualTo(""" |
||||
bean.one = "test"; |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void generateSetFieldWithPrivateField() { |
||||
MultiStatement statement = this.generator.generateSetValue("example", |
||||
field(SampleBean.class, "two"), CodeBlock.of("42")); |
||||
CodeSnippet code = CodeSnippet.of(statement.toCodeBlock()); |
||||
assertThat(code.getSnippet()).isEqualTo(""" |
||||
Field twoField = ReflectionUtils.findField(BeanFieldGeneratorTests.SampleBean.class, "two"); |
||||
ReflectionUtils.makeAccessible(twoField); |
||||
ReflectionUtils.setField(twoField, example, 42); |
||||
"""); |
||||
assertThat(code.hasImport(ReflectionUtils.class)).isTrue(); |
||||
assertThat(code.hasImport(BeanFieldGeneratorTests.class)).isTrue(); |
||||
} |
||||
|
||||
|
||||
private Field field(Class<?> type, String name) { |
||||
Field field = ReflectionUtils.findField(type, name); |
||||
assertThat(field).isNotNull(); |
||||
return field; |
||||
} |
||||
|
||||
|
||||
public static class SampleBean { |
||||
|
||||
public String one; |
||||
|
||||
private int two; |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,225 @@
@@ -0,0 +1,225 @@
|
||||
/* |
||||
* Copyright 2002-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.orm.jpa.support; |
||||
|
||||
import java.util.function.Consumer; |
||||
import java.util.function.Supplier; |
||||
|
||||
import jakarta.persistence.EntityManager; |
||||
import jakarta.persistence.EntityManagerFactory; |
||||
import jakarta.persistence.PersistenceContext; |
||||
import jakarta.persistence.PersistenceProperty; |
||||
import jakarta.persistence.PersistenceUnit; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.aot.generator.CodeContribution; |
||||
import org.springframework.aot.generator.DefaultCodeContribution; |
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext; |
||||
import org.springframework.aot.generator.GeneratedType; |
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.aot.hint.TypeReference; |
||||
import org.springframework.aot.test.generator.compile.TestCompiler; |
||||
import org.springframework.aot.test.generator.file.SourceFile; |
||||
import org.springframework.aot.test.generator.file.SourceFiles; |
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.generator.ApplicationContextAotGenerator; |
||||
import org.springframework.context.support.GenericApplicationContext; |
||||
import org.springframework.javapoet.ClassName; |
||||
import org.springframework.javapoet.JavaFile; |
||||
import org.springframework.javapoet.support.CodeSnippet; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link PersistenceAnnotationBeanPostProcessor}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class PersistenceAnnotationBeanPostProcessorTests { |
||||
|
||||
@Test |
||||
void contributeForPersistenceUnitOnPublicField() { |
||||
CodeContribution contribution = contribute(DefaultPersistenceUnitField.class); |
||||
assertThat(contribution).isNotNull(); |
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty(); |
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo(""" |
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, ""); |
||||
bean.emf = entityManagerFactory; |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void contributeForPersistenceUnitOnPublicSetter() { |
||||
CodeContribution contribution = contribute(DefaultPersistenceUnitMethod.class); |
||||
assertThat(contribution).isNotNull(); |
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty(); |
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo(""" |
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, ""); |
||||
bean.setEmf(entityManagerFactory); |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void contributeForPersistenceUnitWithCustomUnitOnPublicSetter() { |
||||
CodeContribution contribution = contribute(CustomUnitNamePublicPersistenceUnitMethod.class); |
||||
assertThat(contribution).isNotNull(); |
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty(); |
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo(""" |
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "custom"); |
||||
bean.setEmf(entityManagerFactory); |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void contributeForPersistenceContextOnPrivateField() { |
||||
CodeContribution contribution = contribute(DefaultPersistenceContextField.class); |
||||
assertThat(contribution).isNotNull(); |
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> { |
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(DefaultPersistenceContextField.class)); |
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> { |
||||
assertThat(fieldHint.getName()).isEqualTo("entityManager"); |
||||
assertThat(fieldHint.isAllowWrite()).isTrue(); |
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); |
||||
}); |
||||
}); |
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo(""" |
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, ""); |
||||
EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory, null, true); |
||||
Field entityManagerField = ReflectionUtils.findField(PersistenceAnnotationBeanPostProcessorTests.DefaultPersistenceContextField.class, "entityManager"); |
||||
ReflectionUtils.makeAccessible(entityManagerField); |
||||
ReflectionUtils.setField(entityManagerField, bean, entityManager); |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void contributeForPersistenceContextWithCustomPropertiesOnMethod() { |
||||
CodeContribution contribution = contribute(CustomPropertiesPersistenceContextMethod.class); |
||||
assertThat(contribution).isNotNull(); |
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty(); |
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo(""" |
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, ""); |
||||
Properties persistenceProperties = new Properties(); |
||||
persistenceProperties.put("jpa.test", "value"); |
||||
persistenceProperties.put("jpa.test2", "value2"); |
||||
EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory, persistenceProperties, true); |
||||
bean.setEntityManager(entityManager); |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void generateEntityManagerFactoryInjection() { |
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
context.registerBeanDefinition("test", new RootBeanDefinition(DefaultPersistenceUnitField.class)); |
||||
|
||||
EntityManagerFactory entityManagerFactory = mock(EntityManagerFactory.class); |
||||
compile(context, toFreshApplicationContext(() -> { |
||||
GenericApplicationContext ctx = new GenericApplicationContext(); |
||||
ctx.getDefaultListableBeanFactory().registerSingleton("myEmf", entityManagerFactory); |
||||
return ctx; |
||||
}, aotContext -> assertThat(aotContext.getBean("test")).hasFieldOrPropertyWithValue("emf", entityManagerFactory))); |
||||
} |
||||
|
||||
private DefaultCodeContribution contribute(Class<?> type) { |
||||
BeanInstantiationContribution contributor = createContribution(type); |
||||
assertThat(contributor).isNotNull(); |
||||
DefaultCodeContribution contribution = new DefaultCodeContribution(new RuntimeHints()); |
||||
contributor.applyTo(contribution); |
||||
return contribution; |
||||
} |
||||
|
||||
@Nullable |
||||
private BeanInstantiationContribution createContribution(Class<?> beanType) { |
||||
PersistenceAnnotationBeanPostProcessor bpp = new PersistenceAnnotationBeanPostProcessor(); |
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); |
||||
return bpp.contribute(beanDefinition, beanType, "test"); |
||||
} |
||||
|
||||
@SuppressWarnings("rawtypes") |
||||
private void compile(GenericApplicationContext applicationContext, Consumer<ApplicationContextInitializer> initializer) { |
||||
DefaultGeneratedTypeContext generationContext = new DefaultGeneratedTypeContext("com.example", |
||||
packageName -> GeneratedType.of(ClassName.get(packageName, "Test"))); |
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); |
||||
generator.generateApplicationContext(applicationContext, generationContext); |
||||
SourceFiles sourceFiles = SourceFiles.none(); |
||||
for (JavaFile javaFile : generationContext.toJavaFiles()) { |
||||
sourceFiles = sourceFiles.and(SourceFile.of((javaFile::writeTo))); |
||||
} |
||||
TestCompiler.forSystem().withSources(sourceFiles).compile(compiled -> { |
||||
ApplicationContextInitializer instance = compiled.getInstance(ApplicationContextInitializer.class, "com.example.Test"); |
||||
initializer.accept(instance); |
||||
}); |
||||
} |
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" }) |
||||
private <T extends GenericApplicationContext> Consumer<ApplicationContextInitializer> toFreshApplicationContext( |
||||
Supplier<T> applicationContextFactory, Consumer<T> context) { |
||||
return applicationContextInitializer -> { |
||||
T applicationContext = applicationContextFactory.get(); |
||||
applicationContextInitializer.initialize(applicationContext); |
||||
applicationContext.refresh(); |
||||
context.accept(applicationContext); |
||||
}; |
||||
} |
||||
|
||||
|
||||
static class DefaultPersistenceUnitField { |
||||
|
||||
@PersistenceUnit |
||||
public EntityManagerFactory emf; |
||||
|
||||
} |
||||
|
||||
static class DefaultPersistenceUnitMethod { |
||||
|
||||
@PersistenceUnit |
||||
public void setEmf(EntityManagerFactory emf) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class CustomUnitNamePublicPersistenceUnitMethod { |
||||
|
||||
@PersistenceUnit(unitName = "custom") |
||||
public void setEmf(EntityManagerFactory emf) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class DefaultPersistenceContextField { |
||||
|
||||
@PersistenceContext |
||||
private EntityManager entityManager; |
||||
|
||||
} |
||||
|
||||
static class CustomPropertiesPersistenceContextMethod { |
||||
|
||||
@PersistenceContext(properties = { |
||||
@PersistenceProperty(name = "jpa.test", value = "value"), |
||||
@PersistenceProperty(name = "jpa.test2", value = "value2") }) |
||||
public void setEntityManager(EntityManager entityManager) { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue