001/* 002 * Copyright 2002-2017 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.util.ReflectionUtils; 029 030/** 031 * @author Andy Clement 032 * @author Juergen Hoeller 033 * @since 3.0 034 */ 035public class ReflectiveMethodExecutor implements MethodExecutor { 036 037 private final Method method; 038 039 private final Integer varargsPosition; 040 041 private boolean computedPublicDeclaringClass = false; 042 043 private Class<?> publicDeclaringClass; 044 045 private boolean argumentConversionOccurred = false; 046 047 048 public ReflectiveMethodExecutor(Method method) { 049 this.method = method; 050 if (method.isVarArgs()) { 051 Class<?>[] paramTypes = method.getParameterTypes(); 052 this.varargsPosition = paramTypes.length - 1; 053 } 054 else { 055 this.varargsPosition = null; 056 } 057 } 058 059 060 public Method getMethod() { 061 return this.method; 062 } 063 064 /** 065 * Find the first public class in the methods declaring class hierarchy that declares this method. 066 * Sometimes the reflective method discovery logic finds a suitable method that can easily be 067 * called via reflection but cannot be called from generated code when compiling the expression 068 * because of visibility restrictions. For example if a non public class overrides toString(), this 069 * helper method will walk up the type hierarchy to find the first public type that declares the 070 * method (if there is one!). For toString() it may walk as far as Object. 071 */ 072 public Class<?> getPublicDeclaringClass() { 073 if (!this.computedPublicDeclaringClass) { 074 this.publicDeclaringClass = discoverPublicClass(this.method, this.method.getDeclaringClass()); 075 this.computedPublicDeclaringClass = true; 076 } 077 return this.publicDeclaringClass; 078 } 079 080 private Class<?> discoverPublicClass(Method method, Class<?> clazz) { 081 if (Modifier.isPublic(clazz.getModifiers())) { 082 try { 083 clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); 084 return clazz; 085 } 086 catch (NoSuchMethodException ex) { 087 // Continue below... 088 } 089 } 090 Class<?>[] ifcs = clazz.getInterfaces(); 091 for (Class<?> ifc: ifcs) { 092 discoverPublicClass(method, ifc); 093 } 094 if (clazz.getSuperclass() != null) { 095 return discoverPublicClass(method, clazz.getSuperclass()); 096 } 097 return null; 098 } 099 100 public boolean didArgumentConversionOccur() { 101 return this.argumentConversionOccurred; 102 } 103 104 105 @Override 106 public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { 107 try { 108 if (arguments != null) { 109 this.argumentConversionOccurred = ReflectionHelper.convertArguments( 110 context.getTypeConverter(), arguments, this.method, this.varargsPosition); 111 if (this.method.isVarArgs()) { 112 arguments = ReflectionHelper.setupArgumentsForVarargsInvocation( 113 this.method.getParameterTypes(), arguments); 114 } 115 } 116 ReflectionUtils.makeAccessible(this.method); 117 Object value = this.method.invoke(target, arguments); 118 return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.method, -1)).narrow(value)); 119 } 120 catch (Exception ex) { 121 throw new AccessException("Problem invoking method: " + this.method, ex); 122 } 123 } 124 125}