|
/** |
|
* {@link CompilableIndexAccessor} that uses reflection to invoke the |
|
* configured read-method for index access operations. |
|
*/ |
|
static class ReflectiveIndexAccessor implements CompilableIndexAccessor { |
|
|
|
private final Class<?> targetType; |
|
|
|
private final Class<?> indexType; |
|
|
|
private final Method readMethod; |
|
|
|
private final Method readMethodToInvoke; |
|
|
|
private final String targetTypeDesc; |
|
|
|
private final String methodDescr; |
|
|
|
|
|
public ReflectiveIndexAccessor(Class<?> targetType, Class<?> indexType, String readMethodName) { |
|
this.targetType = targetType; |
|
this.indexType = indexType; |
|
this.readMethod = ReflectionUtils.findMethod(targetType, readMethodName, indexType); |
|
Assert.notNull(this.readMethod, () -> "Failed to find method '%s(%s)' in class '%s'." |
|
.formatted(readMethodName, indexType.getTypeName(), targetType.getTypeName())); |
|
this.readMethodToInvoke = ClassUtils.getInterfaceMethodIfPossible(this.readMethod, targetType); |
|
this.targetTypeDesc = CodeFlow.toDescriptor(targetType); |
|
this.methodDescr = CodeFlow.createSignatureDescriptor(this.readMethod); |
|
} |
|
|
|
|
|
@Override |
|
public Class<?>[] getSpecificTargetClasses() { |
|
return new Class[] { this.targetType }; |
|
} |
|
|
|
@Override |
|
public boolean canRead(EvaluationContext context, Object target, Object index) { |
|
return (ClassUtils.isAssignableValue(this.targetType, target) && |
|
ClassUtils.isAssignableValue(this.indexType, index)); |
|
} |
|
|
|
@Override |
|
public TypedValue read(EvaluationContext context, Object target, Object index) { |
|
ReflectionUtils.makeAccessible(this.readMethodToInvoke); |
|
Object value = ReflectionUtils.invokeMethod(this.readMethodToInvoke, target, index); |
|
return new TypedValue(value); |
|
} |
|
|
|
@Override |
|
public boolean canWrite(EvaluationContext context, Object target, Object index) { |
|
return false; |
|
} |
|
|
|
@Override |
|
public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
@Override |
|
public boolean isCompilable() { |
|
return true; |
|
} |
|
|
|
@Override |
|
public Class<?> getIndexedValueType() { |
|
return this.readMethod.getReturnType(); |
|
} |
|
|
|
@Override |
|
public void generateCode(SpelNode index, MethodVisitor mv, CodeFlow cf) { |
|
// Determine the public declaring class. |
|
Class<?> publicDeclaringClass = this.readMethodToInvoke.getDeclaringClass(); |
|
if (!Modifier.isPublic(publicDeclaringClass.getModifiers()) && this.readMethod != null) { |
|
publicDeclaringClass = CodeFlow.findPublicDeclaringClass(this.readMethod); |
|
} |
|
Assert.state(publicDeclaringClass != null && Modifier.isPublic(publicDeclaringClass.getModifiers()), |
|
() -> "Failed to find public declaring class for: " + this.readMethod); |
|
|
|
// Ensure the current object on the stack is the target type. |
|
String lastDesc = cf.lastDescriptor(); |
|
if (lastDesc == null || !lastDesc.equals(this.targetTypeDesc)) { |
|
CodeFlow.insertCheckCast(mv, this.targetTypeDesc); |
|
} |
|
|
|
// Push the index onto the stack. |
|
cf.generateCodeForArgument(mv, index, this.indexType); |
|
|
|
// Invoke the read method. |
|
String classDesc = publicDeclaringClass.getName().replace('.', '/'); |
|
boolean isStatic = Modifier.isStatic(this.readMethod.getModifiers()); |
|
boolean isInterface = publicDeclaringClass.isInterface(); |
|
int opcode = (isStatic ? INVOKESTATIC : isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL); |
|
mv.visitMethodInsn(opcode, classDesc, this.readMethod.getName(), this.methodDescr, isInterface); |
|
} |
|
} |
Overview
Somewhat analogous to the
ReflectivePropertyAccessorimplementation ofPropertyAccessor, we should introduce a general purposeReflectiveIndexAccessorimplementation as a convenience for users.However,
ReflectiveIndexAccessorshould implementCompilableIndexAccessor(instead of justIndexAccessor) in order to provide built-in compilation support.A proof of concept has already been implemented in the tests for
CompilableIndexAccessor:spring-framework/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
Lines 7238 to 7333 in 27d2200
Related Issues
IndexAccessorSPI to customize the SpELIndexer#26478CompilableIndexAccessorSPI in SpEL #32613