Skip to content

Commit

Permalink
AOT contribution for @PersistenceContext and @PersistenceUnit
Browse files Browse the repository at this point in the history
Closes gh-28364
  • Loading branch information
snicoll committed Apr 25, 2022
1 parent 10d2549 commit 26054fd
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
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)
Expand All @@ -53,14 +52,13 @@
*/
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
Expand Down Expand Up @@ -110,7 +108,7 @@ public Options getProtectedAccessInjectionOptions(Member member) {
return METHOD_INJECTION_OPTIONS;
}
if (member instanceof Field) {
return FIELD_INJECTION_OPTIONS;
return BeanFieldGenerator.FIELD_OPTIONS;
}
throw new IllegalArgumentException("Could not handle member " + member);
}
Expand Down Expand Up @@ -230,24 +228,13 @@ CodeBlock generateFieldInjection(Field injectionPoint, boolean required) {
code.add("instanceContext.field($S", injectionPoint.getName());
code.add(")\n").indent().indent();
if (required) {
code.add(".invoke(beanFactory, (attributes) ->");
}
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("}");
code.add(".invoke(beanFactory, ");
}
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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ public boolean isEmpty() {
return this.statements.isEmpty();
}

/**
* Add the statements defined in the specified multi statement to this instance.
* @param multiStatement the statements to add
* @return {@code this}, to facilitate method chaining
*/
public MultiStatement add(MultiStatement multiStatement) {
this.statements.addAll(multiStatement.statements);
return this;
}

/**
* Add the specified {@link CodeBlock codeblock} rendered as-is.
* @param codeBlock the code block to add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,17 @@ void multiStatementsWithAddAllAndLambda() {
"}");
}

@Test
void addWithAnotherMultiStatement() {
MultiStatement statements = new MultiStatement();
statements.addStatement(CodeBlock.of("test.invoke()"));
MultiStatement another = new MultiStatement();
another.addStatement(CodeBlock.of("test.another()"));
statements.add(another);
assertThat(statements.toCodeBlock().toString()).isEqualTo("""
test.invoke();
test.another();
""");
}

}
1 change: 1 addition & 0 deletions spring-orm/spring-orm.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
optional("org.eclipse.persistence:org.eclipse.persistence.jpa")
optional("org.hibernate:hibernate-core-jakarta")
optional("jakarta.servlet:jakarta.servlet-api")
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-context")))
testImplementation(testFixtures(project(":spring-core")))
Expand Down
Loading

0 comments on commit 26054fd

Please sign in to comment.