Polish ProtectedAccess options
This commit improves how protected access analysis operates. Rather than providing a static boolean, a function callback for the member to analyse is used. This permits to change the decision whether reflection can be used, or if the return type is assigned. Both of those are already applicable, with InjectionGenerator relying on reflection for private fields, and DefaultBeanInstanceGenerator assigning the bean instance if additional contributors are present. This commit also moves the logic of computing the options where the code is actually generated. See gh-28030
This commit is contained in:
@@ -33,7 +33,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Gather the need of non-public access and determine the priviledged package
|
||||
* Gather the need of non-public access and determine the privileged package
|
||||
* to use, if necessary.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
@@ -104,15 +104,6 @@ public class ProtectedAccess {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze accessing the specified {@link Member} using the default
|
||||
* {@linkplain Options#DEFAULTS options}.
|
||||
* @param member the member to analyze
|
||||
*/
|
||||
public void analyze(Member member) {
|
||||
analyze(member, Options.DEFAULTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze accessing the specified {@link Member} using the specified
|
||||
* {@link Options options}.
|
||||
@@ -123,12 +114,12 @@ public class ProtectedAccess {
|
||||
if (isProtected(member.getDeclaringClass())) {
|
||||
registerProtectedType(member.getDeclaringClass(), member);
|
||||
}
|
||||
if (!options.useReflection && isProtected(member.getModifiers())) {
|
||||
if (isProtected(member.getModifiers()) && !options.useReflection.apply(member)) {
|
||||
registerProtectedType(member.getDeclaringClass(), member);
|
||||
}
|
||||
if (member instanceof Field field) {
|
||||
ResolvableType fieldType = ResolvableType.forField(field);
|
||||
if (options.assignReturnType && isProtected(fieldType)) {
|
||||
if (isProtected(fieldType) && options.assignReturnType.apply(field)) {
|
||||
registerProtectedType(fieldType, field);
|
||||
}
|
||||
}
|
||||
@@ -138,7 +129,7 @@ public class ProtectedAccess {
|
||||
}
|
||||
else if (member instanceof Method method) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
if (!options.assignReturnType && isProtected(returnType)) {
|
||||
if (isProtected(returnType) && options.assignReturnType.apply(method)) {
|
||||
registerProtectedType(returnType, method);
|
||||
}
|
||||
analyzeParameterTypes(method, i -> ResolvableType.forMethodParameter(method, i));
|
||||
@@ -204,32 +195,82 @@ public class ProtectedAccess {
|
||||
* Options to use to analyze if invoking a {@link Member} requires
|
||||
* privileged access.
|
||||
*/
|
||||
public static class Options {
|
||||
public static final class Options {
|
||||
|
||||
/**
|
||||
* Default options that does fallback to reflection and does not
|
||||
* assign the default type.
|
||||
*/
|
||||
public static final Options DEFAULTS = new Options();
|
||||
private final Function<Member, Boolean> assignReturnType;
|
||||
|
||||
private final boolean useReflection;
|
||||
private final Function<Member, Boolean> useReflection;
|
||||
|
||||
private final boolean assignReturnType;
|
||||
|
||||
/**
|
||||
* Create a new instance with the specified options.
|
||||
* @param useReflection whether the writer can automatically use
|
||||
* reflection to invoke a protected member if it is not public
|
||||
* @param assignReturnType whether the writer needs to assign the
|
||||
* return type, or if it is irrelevant
|
||||
*/
|
||||
public Options(boolean useReflection, boolean assignReturnType) {
|
||||
this.useReflection = useReflection;
|
||||
this.assignReturnType = assignReturnType;
|
||||
private Options(Builder builder) {
|
||||
this.assignReturnType = builder.assignReturnType;
|
||||
this.useReflection = builder.useReflection;
|
||||
}
|
||||
|
||||
private Options() {
|
||||
this(true, false);
|
||||
/**
|
||||
* Initialize a {@link Builder} with default options, that is use
|
||||
* reflection if the member is private and does not assign the
|
||||
* return type.
|
||||
* @return an options builder
|
||||
*/
|
||||
public static Builder defaults() {
|
||||
return new Builder(member -> false,
|
||||
member -> Modifier.isPrivate(member.getModifiers()));
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private Function<Member, Boolean> assignReturnType;
|
||||
|
||||
private Function<Member, Boolean> useReflection;
|
||||
|
||||
private Builder(Function<Member, Boolean> assignReturnType,
|
||||
Function<Member, Boolean> useReflection) {
|
||||
this.assignReturnType = assignReturnType;
|
||||
this.useReflection = useReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if the return type is assigned so that its type can be
|
||||
* analyzed if necessary.
|
||||
* @param assignReturnType whether the return type is assigned
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder assignReturnType(boolean assignReturnType) {
|
||||
return assignReturnType(member -> assignReturnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function that determines whether the return type is
|
||||
* assigned so that its type can be analyzed.
|
||||
* @param assignReturnType whether the return type is assigned
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder assignReturnType(Function<Member, Boolean> assignReturnType) {
|
||||
this.assignReturnType = assignReturnType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function that determines whether reflection can be
|
||||
* used for a given {@link Member}.
|
||||
* @param useReflection whether reflection can be used
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder useReflection(Function<Member, Boolean> useReflection) {
|
||||
this.useReflection = useReflection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an {@link Options} instance based on the state of this
|
||||
* builder.
|
||||
* @return a new options instance
|
||||
*/
|
||||
public Options build() {
|
||||
return new Options(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -37,128 +39,126 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
*/
|
||||
class ProtectedAccessTests {
|
||||
|
||||
public static final Options DEFAULT_OPTIONS = Options.defaults().build();
|
||||
|
||||
private final ProtectedAccess protectedAccess = new ProtectedAccess();
|
||||
|
||||
@Test
|
||||
void analyzeWithPublicConstructor() throws NoSuchMethodException {
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor());
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorAndDefaultOptions() {
|
||||
this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0]);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorAndReflectionDisabled() {
|
||||
void analyzeWithPackagePrivateConstructor() {
|
||||
this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0],
|
||||
new Options(false, true));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedAccessor.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedAccessor.class.getPackageName())).isTrue();
|
||||
DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorAndReflectionEnabled() {
|
||||
Constructor<?> constructor = ProtectedAccessor.class.getDeclaredConstructors()[0];
|
||||
this.protectedAccess.analyze(constructor,
|
||||
Options.defaults().useReflection(member -> member.equals(constructor)).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateClass() {
|
||||
this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0]);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedClass.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue();
|
||||
this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0], DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateDeclaringType() {
|
||||
this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean"));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedClass.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue();
|
||||
this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorParameter() {
|
||||
this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0]);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedParameter.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedParameter.class.getPackageName())).isTrue();
|
||||
this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0], DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedParameter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethod() {
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"));
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(PublicClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodAndReflectionEnabled() {
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"),
|
||||
Options.defaults().useReflection(member -> !Modifier.isPublic(member.getModifiers())).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodAndReflectionDisabled() {
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"),
|
||||
new Options(false, false));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
void analyzeWithPackagePrivateMethodReturnType() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodReturnType() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedAccessor.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedAccessor.class.getPackageName())).isTrue();
|
||||
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnTypeFunction() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
|
||||
Options.defaults().assignReturnType(member -> false).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnType() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodParameter() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedParameter",
|
||||
ProtectedClass.class));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedClass.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue();
|
||||
ProtectedClass.class), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateField() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"));
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(PublicClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateFieldAndReflectionEnabled() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"),
|
||||
Options.defaults().useReflection(member -> true).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateFieldAndReflectionDisabled() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"),
|
||||
new Options(false, true));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(PublicClass.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(PublicClass.class.getPackageName())).isTrue();
|
||||
void analyzeWithPublicFieldAndProtectedType() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPublicFieldAndProtectedType() {
|
||||
void analyzeWithPublicFieldAndProtectedTypeAssigned() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"),
|
||||
new Options(false, true));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedClass.class.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue();
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateGenericArgument() {
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"));
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.isAccessible(PublicFactoryBean.class.getPackageName())).isTrue();
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(PublicFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeTypeWithProtectedGenericArgument() {
|
||||
this.protectedAccess.analyze(PublicFactoryBean.resolveToProtectedGenericParameter());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.isAccessible(PublicFactoryBean.class.getPackageName())).isTrue();
|
||||
assertPrivilegedAccess(PublicFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -169,13 +169,14 @@ class ProtectedAccessTests {
|
||||
|
||||
@Test
|
||||
void getProtectedPackageWithPublicAccess() throws NoSuchMethodException {
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor());
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProtectedPackageWithProtectedAccessInOnePackage() {
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"));
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(PublicFactoryBean.class.getPackageName());
|
||||
}
|
||||
@@ -185,14 +186,21 @@ class ProtectedAccessTests {
|
||||
Method protectedMethodFirstPackage = method(PublicFactoryBean.class, "protectedTypeFactoryBean");
|
||||
Method protectedMethodSecondPackage = method(ProtectedAccessor.class, "methodWithProtectedParameter",
|
||||
ProtectedClass.class);
|
||||
this.protectedAccess.analyze(protectedMethodFirstPackage);
|
||||
this.protectedAccess.analyze(protectedMethodSecondPackage);
|
||||
this.protectedAccess.analyze(protectedMethodFirstPackage,
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
this.protectedAccess.analyze(protectedMethodSecondPackage, DEFAULT_OPTIONS);
|
||||
assertThatThrownBy(() -> this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isInstanceOfSatisfying(ProtectedAccessException.class, ex ->
|
||||
assertThat(ex.getProtectedElements().stream().map(ProtectedElement::getMember))
|
||||
.containsOnly(protectedMethodFirstPackage, protectedMethodSecondPackage));
|
||||
}
|
||||
|
||||
private void assertPrivilegedAccess(Class<?> target) {
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName());
|
||||
assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue();
|
||||
}
|
||||
|
||||
private static Method method(Class<?> type, String name, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
|
||||
assertThat(method).isNotNull();
|
||||
|
||||
Reference in New Issue
Block a user