diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index a1cff64cbb..cc509b3b11 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -48,6 +48,7 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -914,7 +915,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA private static final String INSTANCE_PARAMETER = "instance"; private static final Consumer ALLOW_WRITE = builder -> builder - .allowWrite(true); + .withMode(FieldMode.WRITE); private final Class target; diff --git a/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java b/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java index 593d5b5588..9516b47cfa 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java @@ -19,6 +19,7 @@ package org.springframework.aot.hint; import java.lang.reflect.Field; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * A hint that describes the need of reflection on a {@link Field}. @@ -28,23 +29,33 @@ import org.springframework.lang.Nullable; */ public final class FieldHint extends MemberHint { - private final boolean allowWrite; + private final FieldMode mode; private final boolean allowUnsafeAccess; private FieldHint(Builder builder) { super(builder.name); - this.allowWrite = (builder.allowWrite != null) ? builder.allowWrite : true; + this.mode = (builder.mode != null ? builder.mode : FieldMode.WRITE); this.allowUnsafeAccess = builder.allowUnsafeAccess; } /** * Return whether setting the value of the field should be allowed. * @return {@code true} to allow {@link Field#set(Object, Object)}. + * @deprecated in favor of {@link #getMode()} */ + @Deprecated public boolean isAllowWrite() { - return this.allowWrite; + return this.mode == FieldMode.WRITE; + } + + /** + * Return the {@linkplain FieldMode mode} that apply to this hint. + * @return the mode + */ + public FieldMode getMode() { + return this.mode; } /** @@ -64,7 +75,7 @@ public final class FieldHint extends MemberHint { private final String name; @Nullable - private Boolean allowWrite; + private FieldMode mode; private boolean allowUnsafeAccess; @@ -77,9 +88,26 @@ public final class FieldHint extends MemberHint { * Specify if setting the value of the field should be allowed. * @param allowWrite {@code true} to allow {@link Field#set(Object, Object)} * @return {@code this}, to facilitate method chaining + * @deprecated in favor of {@link #withMode(FieldMode)} */ + @Deprecated public Builder allowWrite(boolean allowWrite) { - this.allowWrite = allowWrite; + if (allowWrite) { + return withMode(FieldMode.WRITE); + } + return this; + } + + /** + * Specify that the {@linkplain FieldMode mode} is required. + * @param mode the required mode + * @return {@code this}, to facilitate method chaining + */ + public Builder withMode(FieldMode mode) { + Assert.notNull(mode, "'mode' must not be null"); + if ((this.mode == null || !this.mode.includes(mode))) { + this.mode = mode; + } return this; } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/FieldMode.java b/spring-core/src/main/java/org/springframework/aot/hint/FieldMode.java new file mode 100644 index 0000000000..9a8b3a04c5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/FieldMode.java @@ -0,0 +1,51 @@ +/* + * 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.aot.hint; + +import java.lang.reflect.Field; + +import org.springframework.lang.Nullable; + +/** + * Represents the need of reflection for a given {@link Field}. + * + * @author Phillip Webb + * @since 6.0 + * @see ReflectionHints + */ +public enum FieldMode { + + /** + * Only field read is required. + */ + READ, + + /** + * Full field read and write is required. + */ + WRITE; + + /** + * Specify if this mode already includes the specified {@code other} mode. + * @param other the other mode to check + * @return {@code true} if this mode includes the other mode + */ + boolean includes(@Nullable FieldMode other) { + return (other == null || this.ordinal() >= other.ordinal()); + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index 8261788e60..eb1beb2ec8 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -127,12 +127,12 @@ public class ReflectionHints { /** * Register the need for reflection on the specified {@link Field}, - * enabling write access. + * enabling {@link FieldMode#WRITE}. * @param field the field that requires reflection * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerField(Field field) { - return registerField(field, fieldHint -> fieldHint.allowWrite(true)); + return registerField(field, fieldHint -> fieldHint.withMode(FieldMode.WRITE)); } /** @@ -146,7 +146,6 @@ public class ReflectionHints { typeHint -> typeHint.withField(field.getName(), fieldHint)); } - /** * Register the need for reflection on the specified {@link Constructor}, * enabling {@link ExecutableMode#INVOKE}. diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index 61b7dbd845..ca8f857bee 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -29,6 +29,7 @@ import java.util.function.Predicate; import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; @@ -401,7 +402,7 @@ public class ReflectionHintsPredicates { private boolean exactMatch(TypeHint typeHint) { return typeHint.fields().anyMatch(fieldHint -> this.field.getName().equals(fieldHint.getName()) - && (!this.allowWrite || this.allowWrite == fieldHint.isAllowWrite()) + && (!this.allowWrite || fieldHint.getMode() == FieldMode.WRITE) && (!this.allowUnsafeAccess || this.allowUnsafeAccess == fieldHint.isAllowUnsafeAccess())); } } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java index d370570c9a..454222b866 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.TypeHint; @@ -74,8 +75,8 @@ class ReflectionHintsWriter { private Map toAttributes(FieldHint hint) { Map attributes = new LinkedHashMap<>(); attributes.put("name", hint.getName()); - if (hint.isAllowWrite()) { - attributes.put("allowWrite", hint.isAllowWrite()); + if (hint.getMode() == FieldMode.WRITE) { + attributes.put("allowWrite", true); } if (hint.isAllowUnsafeAccess()) { attributes.put("allowUnsafeAccess", hint.isAllowUnsafeAccess()); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/FieldModeTests.java b/spring-core/src/test/java/org/springframework/aot/hint/FieldModeTests.java new file mode 100644 index 0000000000..bb6ecf6f04 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/FieldModeTests.java @@ -0,0 +1,61 @@ +/* + * 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.aot.hint; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FieldMode}. + * + * @author Phillip Webb + * @since 6.0 + */ +class FieldModeTests { + + @Test + void writeIncludesNullMode() { + assertThat(FieldMode.WRITE.includes(null)).isTrue(); + } + + @Test + void writeIncludesRead() { + assertThat(FieldMode.WRITE.includes(FieldMode.READ)).isTrue(); + } + + @Test + void writeIncludesWrite() { + assertThat(FieldMode.WRITE.includes(FieldMode.WRITE)).isTrue(); + } + + @Test + void readIncludesNullMode() { + assertThat(FieldMode.READ.includes(null)).isTrue(); + } + + @Test + void readIncludesRead() { + assertThat(FieldMode.READ.includes(FieldMode.READ)).isTrue(); + } + + @Test + void readDoesNotIncludeWrite() { + assertThat(FieldMode.READ.includes(FieldMode.WRITE)).isFalse(); + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 8ab8d7180d..1aafef6ea1 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -127,7 +127,7 @@ class ReflectionHintsTests { this.reflectionHints.registerField(field); assertTestTypeFieldHint(fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("field"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } @@ -139,7 +139,7 @@ class ReflectionHintsTests { this.reflectionHints.registerField(field, fieldHint -> {}); assertTestTypeFieldHint(fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("field"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } @@ -149,14 +149,26 @@ class ReflectionHintsTests { Field field = ReflectionUtils.findField(TestType.class, "field"); assertThat(field).isNotNull(); this.reflectionHints.registerField(field, fieldHint -> - fieldHint.allowWrite(false).allowUnsafeAccess(true)); + fieldHint.withMode(FieldMode.READ).allowUnsafeAccess(true)); assertTestTypeFieldHint(fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("field"); - assertThat(fieldHint.isAllowWrite()).isFalse(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ); assertThat(fieldHint.isAllowUnsafeAccess()).isTrue(); }); } + @Test // gh-29055 + void registerFieldWithCustomizersCannotDowngradeWrite() { + Field field = ReflectionUtils.findField(TestType.class, "field"); + assertThat(field).isNotNull(); + this.reflectionHints.registerField(field, fieldHint -> fieldHint.withMode(FieldMode.WRITE)); + this.reflectionHints.registerField(field, fieldHint -> fieldHint.withMode(FieldMode.READ)); + assertTestTypeFieldHint(fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("field"); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); + }); + } + private void assertTestTypeFieldHint(Consumer fieldHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java index 2bdf754c88..e5490e026d 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java @@ -59,7 +59,7 @@ class TypeHintTests { assertFieldHint(TypeHint.of(TypeReference.of(String.class)) .withField("value"), fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } @@ -69,7 +69,7 @@ class TypeHintTests { assertFieldHint(TypeHint.of(TypeReference.of(String.class)) .withField("value", fieldHint -> {}), fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } @@ -78,11 +78,11 @@ class TypeHintTests { void createWithFieldAndCustomizerAppliesCustomization() { assertFieldHint(TypeHint.of(TypeReference.of(String.class)) .withField("value", fieldHint -> { - fieldHint.allowWrite(false); + fieldHint.withMode(FieldMode.READ); fieldHint.allowUnsafeAccess(true); }), fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); - assertThat(fieldHint.isAllowWrite()).isFalse(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ); assertThat(fieldHint.isAllowUnsafeAccess()).isTrue(); }); } @@ -92,12 +92,12 @@ class TypeHintTests { Builder builder = TypeHint.of(TypeReference.of(String.class)); builder.withField("value", fieldHint -> fieldHint.allowUnsafeAccess(true)); builder.withField("value", fieldHint -> { - fieldHint.allowWrite(true); + fieldHint.withMode(FieldMode.WRITE); fieldHint.allowUnsafeAccess(false); }); assertFieldHint(builder, fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index 6df28fe571..dfbe7de03f 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; @@ -471,7 +472,7 @@ class ReflectionHintsPredicatesTests { @Test void fieldWriteReflectionDoesNotMatchFieldHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", - fieldHint -> fieldHint.allowWrite(false))); + fieldHint -> fieldHint.withMode(FieldMode.WRITE))); assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").allowWrite()); } @@ -485,7 +486,7 @@ class ReflectionHintsPredicatesTests { @Test void fieldWriteReflectionMatchesFieldHintWithWrite() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withField("publicField", fieldHint -> fieldHint.allowWrite(true))); + typeHint.withField("publicField", fieldHint -> fieldHint.withMode(FieldMode.WRITE))); assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").allowWrite()); } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index d72f531f2a..79537eb4f7 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -32,6 +32,7 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ProxyHints; import org.springframework.aot.hint.ReflectionHints; @@ -106,9 +107,9 @@ public class FileNativeConfigurationWriterTests { MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) + .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.withMode(FieldMode.READ)) .withField("defaultCharset", fieldBuilder -> { - fieldBuilder.allowWrite(true); + fieldBuilder.withMode(FieldMode.WRITE); fieldBuilder.allowUnsafeAccess(true); }) .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java index b47f9a440d..bcdb73b08b 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java @@ -27,6 +27,7 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.TypeReference; @@ -57,9 +58,9 @@ public class ReflectionHintsWriterTests { MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) + .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.withMode(FieldMode.READ)) .withField("defaultCharset", fieldBuilder -> { - fieldBuilder.allowWrite(true); + fieldBuilder.withMode(FieldMode.WRITE); fieldBuilder.allowUnsafeAccess(true); }) .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessorAotContributionTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessorAotContributionTests.java index 40dc0f29f8..a1af961299 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessorAotContributionTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessorAotContributionTests.java @@ -32,6 +32,7 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.FieldMode; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess; import org.springframework.aot.test.generator.compile.Compiled; @@ -132,7 +133,7 @@ class PersistenceAnnotationBeanPostProcessorAotContributionTests { .satisfies(fieldHint -> { assertThat(fieldHint.getName()) .isEqualTo("entityManager"); - assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); });