001/* 002 * Copyright 2002-2020 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.web.method.support; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.util.Arrays; 022 023import org.springframework.core.DefaultParameterNameDiscoverer; 024import org.springframework.core.MethodParameter; 025import org.springframework.core.ParameterNameDiscoverer; 026import org.springframework.lang.Nullable; 027import org.springframework.util.ObjectUtils; 028import org.springframework.util.ReflectionUtils; 029import org.springframework.web.bind.WebDataBinder; 030import org.springframework.web.bind.support.SessionStatus; 031import org.springframework.web.bind.support.WebDataBinderFactory; 032import org.springframework.web.context.request.NativeWebRequest; 033import org.springframework.web.method.HandlerMethod; 034 035/** 036 * Extension of {@link HandlerMethod} that invokes the underlying method with 037 * argument values resolved from the current HTTP request through a list of 038 * {@link HandlerMethodArgumentResolver}. 039 * 040 * @author Rossen Stoyanchev 041 * @author Juergen Hoeller 042 * @since 3.1 043 */ 044public class InvocableHandlerMethod extends HandlerMethod { 045 046 private static final Object[] EMPTY_ARGS = new Object[0]; 047 048 049 private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); 050 051 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 052 053 @Nullable 054 private WebDataBinderFactory dataBinderFactory; 055 056 057 /** 058 * Create an instance from a {@code HandlerMethod}. 059 */ 060 public InvocableHandlerMethod(HandlerMethod handlerMethod) { 061 super(handlerMethod); 062 } 063 064 /** 065 * Create an instance from a bean instance and a method. 066 */ 067 public InvocableHandlerMethod(Object bean, Method method) { 068 super(bean, method); 069 } 070 071 /** 072 * Construct a new handler method with the given bean instance, method name and parameters. 073 * @param bean the object bean 074 * @param methodName the method name 075 * @param parameterTypes the method parameter types 076 * @throws NoSuchMethodException when the method cannot be found 077 */ 078 public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) 079 throws NoSuchMethodException { 080 081 super(bean, methodName, parameterTypes); 082 } 083 084 085 /** 086 * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} 087 * to use for resolving method argument values. 088 */ 089 public void setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) { 090 this.resolvers = argumentResolvers; 091 } 092 093 /** 094 * Set the ParameterNameDiscoverer for resolving parameter names when needed 095 * (e.g. default request attribute name). 096 * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. 097 */ 098 public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { 099 this.parameterNameDiscoverer = parameterNameDiscoverer; 100 } 101 102 /** 103 * Set the {@link WebDataBinderFactory} to be passed to argument resolvers allowing them 104 * to create a {@link WebDataBinder} for data binding and type conversion purposes. 105 */ 106 public void setDataBinderFactory(WebDataBinderFactory dataBinderFactory) { 107 this.dataBinderFactory = dataBinderFactory; 108 } 109 110 111 /** 112 * Invoke the method after resolving its argument values in the context of the given request. 113 * <p>Argument values are commonly resolved through 114 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. 115 * The {@code providedArgs} parameter however may supply argument values to be used directly, 116 * i.e. without argument resolution. Examples of provided argument values include a 117 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance. 118 * Provided argument values are checked before argument resolvers. 119 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the 120 * resolved arguments. 121 * @param request the current request 122 * @param mavContainer the ModelAndViewContainer for this request 123 * @param providedArgs "given" arguments matched by type, not resolved 124 * @return the raw value returned by the invoked method 125 * @throws Exception raised if no suitable argument resolver can be found, 126 * or if the method raised an exception 127 * @see #getMethodArgumentValues 128 * @see #doInvoke 129 */ 130 @Nullable 131 public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, 132 Object... providedArgs) throws Exception { 133 134 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 135 if (logger.isTraceEnabled()) { 136 logger.trace("Arguments: " + Arrays.toString(args)); 137 } 138 return doInvoke(args); 139 } 140 141 /** 142 * Get the method argument values for the current request, checking the provided 143 * argument values and falling back to the configured argument resolvers. 144 * <p>The resulting array will be passed into {@link #doInvoke}. 145 * @since 5.1.2 146 */ 147 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, 148 Object... providedArgs) throws Exception { 149 150 MethodParameter[] parameters = getMethodParameters(); 151 if (ObjectUtils.isEmpty(parameters)) { 152 return EMPTY_ARGS; 153 } 154 155 Object[] args = new Object[parameters.length]; 156 for (int i = 0; i < parameters.length; i++) { 157 MethodParameter parameter = parameters[i]; 158 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 159 args[i] = findProvidedArgument(parameter, providedArgs); 160 if (args[i] != null) { 161 continue; 162 } 163 if (!this.resolvers.supportsParameter(parameter)) { 164 throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); 165 } 166 try { 167 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); 168 } 169 catch (Exception ex) { 170 // Leave stack trace for later, exception may actually be resolved and handled... 171 if (logger.isDebugEnabled()) { 172 String exMsg = ex.getMessage(); 173 if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { 174 logger.debug(formatArgumentError(parameter, exMsg)); 175 } 176 } 177 throw ex; 178 } 179 } 180 return args; 181 } 182 183 /** 184 * Invoke the handler method with the given argument values. 185 */ 186 @Nullable 187 protected Object doInvoke(Object... args) throws Exception { 188 ReflectionUtils.makeAccessible(getBridgedMethod()); 189 try { 190 return getBridgedMethod().invoke(getBean(), args); 191 } 192 catch (IllegalArgumentException ex) { 193 assertTargetBean(getBridgedMethod(), getBean(), args); 194 String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); 195 throw new IllegalStateException(formatInvokeError(text, args), ex); 196 } 197 catch (InvocationTargetException ex) { 198 // Unwrap for HandlerExceptionResolvers ... 199 Throwable targetException = ex.getTargetException(); 200 if (targetException instanceof RuntimeException) { 201 throw (RuntimeException) targetException; 202 } 203 else if (targetException instanceof Error) { 204 throw (Error) targetException; 205 } 206 else if (targetException instanceof Exception) { 207 throw (Exception) targetException; 208 } 209 else { 210 throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException); 211 } 212 } 213 } 214 215}