001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.support; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021 022import org.springframework.core.MethodParameter; 023import org.springframework.core.convert.TypeDescriptor; 024import org.springframework.expression.AccessException; 025import org.springframework.expression.EvaluationContext; 026import org.springframework.expression.MethodExecutor; 027import org.springframework.expression.TypedValue; 028import org.springframework.lang.Nullable; 029import org.springframework.util.ClassUtils; 030import org.springframework.util.ReflectionUtils; 031 032/** 033 * {@link MethodExecutor} that works via reflection. 034 * 035 * @author Andy Clement 036 * @author Juergen Hoeller 037 * @since 3.0 038 */ 039public class ReflectiveMethodExecutor implements MethodExecutor { 040 041 private final Method originalMethod; 042 043 private final Method methodToInvoke; 044 045 @Nullable 046 private final Integer varargsPosition; 047 048 private boolean computedPublicDeclaringClass = false; 049 050 @Nullable 051 private Class<?> publicDeclaringClass; 052 053 private boolean argumentConversionOccurred = false; 054 055 056 /** 057 * Create a new executor for the given method. 058 * @param method the method to invoke 059 */ 060 public ReflectiveMethodExecutor(Method method) { 061 this.originalMethod = method; 062 this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method); 063 if (method.isVarArgs()) { 064 this.varargsPosition = method.getParameterCount() - 1; 065 } 066 else { 067 this.varargsPosition = null; 068 } 069 } 070 071 072 /** 073 * Return the original method that this executor has been configured for. 074 */ 075 public final Method getMethod() { 076 return this.originalMethod; 077 } 078 079 /** 080 * Find the first public class in the methods declaring class hierarchy that declares this method. 081 * Sometimes the reflective method discovery logic finds a suitable method that can easily be 082 * called via reflection but cannot be called from generated code when compiling the expression 083 * because of visibility restrictions. For example if a non-public class overrides toString(), 084 * this helper method will walk up the type hierarchy to find the first public type that declares 085 * the method (if there is one!). For toString() it may walk as far as Object. 086 */ 087 @Nullable 088 public Class<?> getPublicDeclaringClass() { 089 if (!this.computedPublicDeclaringClass) { 090 this.publicDeclaringClass = 091 discoverPublicDeclaringClass(this.originalMethod, this.originalMethod.getDeclaringClass()); 092 this.computedPublicDeclaringClass = true; 093 } 094 return this.publicDeclaringClass; 095 } 096 097 @Nullable 098 private Class<?> discoverPublicDeclaringClass(Method method, Class<?> clazz) { 099 if (Modifier.isPublic(clazz.getModifiers())) { 100 try { 101 clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); 102 return clazz; 103 } 104 catch (NoSuchMethodException ex) { 105 // Continue below... 106 } 107 } 108 if (clazz.getSuperclass() != null) { 109 return discoverPublicDeclaringClass(method, clazz.getSuperclass()); 110 } 111 return null; 112 } 113 114 public boolean didArgumentConversionOccur() { 115 return this.argumentConversionOccurred; 116 } 117 118 119 @Override 120 public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { 121 try { 122 this.argumentConversionOccurred = ReflectionHelper.convertArguments( 123 context.getTypeConverter(), arguments, this.originalMethod, this.varargsPosition); 124 if (this.originalMethod.isVarArgs()) { 125 arguments = ReflectionHelper.setupArgumentsForVarargsInvocation( 126 this.originalMethod.getParameterTypes(), arguments); 127 } 128 ReflectionUtils.makeAccessible(this.methodToInvoke); 129 Object value = this.methodToInvoke.invoke(target, arguments); 130 return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.originalMethod, -1)).narrow(value)); 131 } 132 catch (Exception ex) { 133 throw new AccessException("Problem invoking method: " + this.methodToInvoke, ex); 134 } 135 } 136 137}