AOT contribution for @PersistenceContext and @PersistenceUnit

Closes gh-28364
This commit is contained in:
Stephane Nicoll
2022-04-21 17:01:40 +02:00
parent 10d254983f
commit 26054fd3d4
8 changed files with 491 additions and 22 deletions

View File

@@ -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;
}
}

View File

@@ -34,7 +34,6 @@ import org.springframework.beans.factory.generator.config.BeanDefinitionRegistra
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Generate the necessary code to {@link #generateInstantiation(Executable)
@@ -53,14 +52,13 @@ import org.springframework.util.ReflectionUtils;
*/
public class InjectionGenerator {
private static final Options FIELD_INJECTION_OPTIONS = Options.defaults()
.useReflection(member -> Modifier.isPrivate(member.getModifiers())).build();
private static final Options METHOD_INJECTION_OPTIONS = Options.defaults()
.useReflection(member -> false).build();
private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator();
private final BeanFieldGenerator fieldGenerator = new BeanFieldGenerator();
/**
* Generate the necessary code to instantiate an object using the specified
@@ -110,7 +108,7 @@ public class InjectionGenerator {
return METHOD_INJECTION_OPTIONS;
}
if (member instanceof Field) {
return FIELD_INJECTION_OPTIONS;
return BeanFieldGenerator.FIELD_OPTIONS;
}
throw new IllegalArgumentException("Could not handle member " + member);
}
@@ -230,24 +228,13 @@ public class InjectionGenerator {
code.add("instanceContext.field($S", injectionPoint.getName());
code.add(")\n").indent().indent();
if (required) {
code.add(".invoke(beanFactory, (attributes) ->");
code.add(".invoke(beanFactory, ");
}
else {
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->");
}
boolean hasAssignment = Modifier.isPrivate(injectionPoint.getModifiers());
if (hasAssignment) {
code.beginControlFlow("");
String fieldName = String.format("%sField", injectionPoint.getName());
code.addStatement("$T $L = $T.findField($T.class, $S)", Field.class, fieldName, ReflectionUtils.class,
injectionPoint.getDeclaringClass(), injectionPoint.getName());
code.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName);
code.addStatement("$T.setField($L, bean, attributes.get(0))", ReflectionUtils.class, fieldName);
code.unindent().add("}");
}
else {
code.add(" bean.$L = attributes.get(0)", injectionPoint.getName());
code.add(".resolve(beanFactory, false).ifResolved(");
}
code.add(this.fieldGenerator.generateSetValue("bean", injectionPoint,
CodeBlock.of("attributes.get(0)")).toLambdaBody("(attributes) ->"));
code.add(")").unindent().unindent();
return code.build();
}

View File

@@ -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;
}
}