Java tutorial
/* * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.reactive.result.method; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import reactor.core.publisher.Mono; import org.springframework.core.CoroutinesUtils; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.server.ServerWebExchange; /** * Extension of {@link HandlerMethod} that invokes the underlying method with * argument values resolved from the current HTTP request through a list of * {@link HandlerMethodArgumentResolver}. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Sebastien Deleuze * @since 5.0 */ public class InvocableHandlerMethod extends HandlerMethod { private static final Mono<Object[]> EMPTY_ARGS = Mono.just(new Object[0]); private static final Object NO_ARG_VALUE = new Object(); private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); /** * Create an instance from a {@code HandlerMethod}. */ public InvocableHandlerMethod(HandlerMethod handlerMethod) { super(handlerMethod); } /** * Create an instance from a bean instance and a method. */ public InvocableHandlerMethod(Object bean, Method method) { super(bean, method); } /** * Configure the argument resolvers to use to use for resolving method * argument values against a {@code ServerWebExchange}. */ public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { this.resolvers.addResolvers(resolvers); } /** * Return the configured argument resolvers. */ public List<HandlerMethodArgumentResolver> getResolvers() { return this.resolvers.getResolvers(); } /** * Set the ParameterNameDiscoverer for resolving parameter names when needed * (e.g. default request attribute name). * <p>Default is a {@link DefaultParameterNameDiscoverer}. */ public void setParameterNameDiscoverer(ParameterNameDiscoverer nameDiscoverer) { this.parameterNameDiscoverer = nameDiscoverer; } /** * Return the configured parameter name discoverer. */ public ParameterNameDiscoverer getParameterNameDiscoverer() { return this.parameterNameDiscoverer; } /** * Configure a reactive adapter registry. This is needed for cases where the response is * fully handled within the controller in combination with an async void return value. * <p>By default this is a {@link ReactiveAdapterRegistry} with default settings. */ public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { this.reactiveAdapterRegistry = registry; } /** * Invoke the method for the given exchange. * @param exchange the current exchange * @param bindingContext the binding context to use * @param providedArgs optional list of argument values to match by type * @return a Mono with a {@link HandlerResult} */ @SuppressWarnings("KotlinInternalInJava") public Mono<HandlerResult> invoke(ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> { Object value; try { ReflectionUtils.makeAccessible(getBridgedMethod()); Method method = getBridgedMethod(); if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass())) { value = CoroutinesUtils.invokeHandlerMethod(method, getBean(), args); } else { value = method.invoke(getBean(), args); } } catch (IllegalArgumentException ex) { assertTargetBean(getBridgedMethod(), getBean(), args); String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); return Mono.error(new IllegalStateException(formatInvokeError(text, args), ex)); } catch (InvocationTargetException ex) { return Mono.error(ex.getTargetException()); } catch (Throwable ex) { // Unlikely to ever get here, but it must be handled... return Mono.error(new IllegalStateException(formatInvokeError("Invocation failure", args), ex)); } HttpStatus status = getResponseStatus(); if (status != null) { exchange.getResponse().setStatusCode(status); } MethodParameter returnType = getReturnType(); ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(returnType.getParameterType()); boolean asyncVoid = isAsyncVoidReturnType(returnType, adapter); if ((value == null || asyncVoid) && isResponseHandled(args, exchange)) { return (asyncVoid ? Mono.from(adapter.toPublisher(value)) : Mono.empty()); } HandlerResult result = new HandlerResult(this, value, returnType, bindingContext); return Mono.just(result); }); } private Mono<Object[]> getMethodArgumentValues(ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } List<Mono<Object>> argMonos = new ArrayList<>(parameters.length); for (MethodParameter parameter : parameters) { parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); Object providedArg = findProvidedArgument(parameter, providedArgs); if (providedArg != null) { argMonos.add(Mono.just(providedArg)); continue; } if (!this.resolvers.supportsParameter(parameter)) { return Mono .error(new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"))); } try { argMonos.add(this.resolvers.resolveArgument(parameter, bindingContext, exchange) .defaultIfEmpty(NO_ARG_VALUE) .doOnError(ex -> logArgumentErrorIfNecessary(exchange, parameter, ex))); } catch (Exception ex) { logArgumentErrorIfNecessary(exchange, parameter, ex); argMonos.add(Mono.error(ex)); } } return Mono.zip(argMonos, values -> Stream.of(values).map(value -> value != NO_ARG_VALUE ? value : null).toArray()); } private void logArgumentErrorIfNecessary(ServerWebExchange exchange, MethodParameter parameter, Throwable ex) { // Leave stack trace for later, if error is not handled... String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { if (logger.isDebugEnabled()) { logger.debug(exchange.getLogPrefix() + formatArgumentError(parameter, exMsg)); } } } private static boolean isAsyncVoidReturnType(MethodParameter returnType, @Nullable ReactiveAdapter adapter) { if (adapter != null && adapter.supportsEmpty()) { if (adapter.isNoValue()) { return true; } Type parameterType = returnType.getGenericParameterType(); if (parameterType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) parameterType; if (type.getActualTypeArguments().length == 1) { return Void.class.equals(type.getActualTypeArguments()[0]); } } } return false; } private boolean isResponseHandled(Object[] args, ServerWebExchange exchange) { if (getResponseStatus() != null || exchange.isNotModified()) { return true; } for (Object arg : args) { if (arg instanceof ServerHttpResponse || arg instanceof ServerWebExchange) { return true; } } return false; } }