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.reactive.result.method.annotation; 018 019import java.util.Collections; 020import java.util.List; 021import java.util.function.Function; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import reactor.core.publisher.Mono; 026 027import org.springframework.beans.factory.InitializingBean; 028import org.springframework.context.ApplicationContext; 029import org.springframework.context.ApplicationContextAware; 030import org.springframework.context.ConfigurableApplicationContext; 031import org.springframework.core.ReactiveAdapterRegistry; 032import org.springframework.http.codec.HttpMessageReader; 033import org.springframework.http.codec.ServerCodecConfigurer; 034import org.springframework.lang.Nullable; 035import org.springframework.util.Assert; 036import org.springframework.util.CollectionUtils; 037import org.springframework.web.bind.support.WebBindingInitializer; 038import org.springframework.web.method.HandlerMethod; 039import org.springframework.web.reactive.BindingContext; 040import org.springframework.web.reactive.HandlerAdapter; 041import org.springframework.web.reactive.HandlerMapping; 042import org.springframework.web.reactive.HandlerResult; 043import org.springframework.web.reactive.result.method.InvocableHandlerMethod; 044import org.springframework.web.server.ServerWebExchange; 045 046/** 047 * Supports the invocation of 048 * {@link org.springframework.web.bind.annotation.RequestMapping @RequestMapping} 049 * handler methods. 050 * 051 * @author Rossen Stoyanchev 052 * @since 5.0 053 */ 054public class RequestMappingHandlerAdapter implements HandlerAdapter, ApplicationContextAware, InitializingBean { 055 056 private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class); 057 058 059 private List<HttpMessageReader<?>> messageReaders = Collections.emptyList(); 060 061 @Nullable 062 private WebBindingInitializer webBindingInitializer; 063 064 @Nullable 065 private ArgumentResolverConfigurer argumentResolverConfigurer; 066 067 @Nullable 068 private ReactiveAdapterRegistry reactiveAdapterRegistry; 069 070 @Nullable 071 private ConfigurableApplicationContext applicationContext; 072 073 @Nullable 074 private ControllerMethodResolver methodResolver; 075 076 @Nullable 077 private ModelInitializer modelInitializer; 078 079 080 /** 081 * Configure HTTP message readers to de-serialize the request body with. 082 * <p>By default this is set to {@link ServerCodecConfigurer}'s readers with defaults. 083 */ 084 public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) { 085 Assert.notNull(messageReaders, "'messageReaders' must not be null"); 086 this.messageReaders = messageReaders; 087 } 088 089 /** 090 * Return the configurer for HTTP message readers. 091 */ 092 public List<HttpMessageReader<?>> getMessageReaders() { 093 return this.messageReaders; 094 } 095 096 /** 097 * Provide a WebBindingInitializer with "global" initialization to apply 098 * to every DataBinder instance. 099 */ 100 public void setWebBindingInitializer(@Nullable WebBindingInitializer webBindingInitializer) { 101 this.webBindingInitializer = webBindingInitializer; 102 } 103 104 /** 105 * Return the configured WebBindingInitializer, or {@code null} if none. 106 */ 107 @Nullable 108 public WebBindingInitializer getWebBindingInitializer() { 109 return this.webBindingInitializer; 110 } 111 112 /** 113 * Configure resolvers for controller method arguments. 114 */ 115 public void setArgumentResolverConfigurer(@Nullable ArgumentResolverConfigurer configurer) { 116 this.argumentResolverConfigurer = configurer; 117 } 118 119 /** 120 * Return the configured resolvers for controller method arguments. 121 */ 122 @Nullable 123 public ArgumentResolverConfigurer getArgumentResolverConfigurer() { 124 return this.argumentResolverConfigurer; 125 } 126 127 /** 128 * Configure the registry for adapting various reactive types. 129 * <p>By default this is an instance of {@link ReactiveAdapterRegistry} with 130 * default settings. 131 */ 132 public void setReactiveAdapterRegistry(@Nullable ReactiveAdapterRegistry registry) { 133 this.reactiveAdapterRegistry = registry; 134 } 135 136 /** 137 * Return the configured registry for adapting reactive types. 138 */ 139 @Nullable 140 public ReactiveAdapterRegistry getReactiveAdapterRegistry() { 141 return this.reactiveAdapterRegistry; 142 } 143 144 /** 145 * A {@link ConfigurableApplicationContext} is expected for resolving 146 * expressions in method argument default values as well as for 147 * detecting {@code @ControllerAdvice} beans. 148 */ 149 @Override 150 public void setApplicationContext(ApplicationContext applicationContext) { 151 if (applicationContext instanceof ConfigurableApplicationContext) { 152 this.applicationContext = (ConfigurableApplicationContext) applicationContext; 153 } 154 } 155 156 157 @Override 158 public void afterPropertiesSet() throws Exception { 159 Assert.notNull(this.applicationContext, "ApplicationContext is required"); 160 161 if (CollectionUtils.isEmpty(this.messageReaders)) { 162 ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create(); 163 this.messageReaders = codecConfigurer.getReaders(); 164 } 165 if (this.argumentResolverConfigurer == null) { 166 this.argumentResolverConfigurer = new ArgumentResolverConfigurer(); 167 } 168 if (this.reactiveAdapterRegistry == null) { 169 this.reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); 170 } 171 172 this.methodResolver = new ControllerMethodResolver(this.argumentResolverConfigurer, 173 this.reactiveAdapterRegistry, this.applicationContext, this.messageReaders); 174 175 this.modelInitializer = new ModelInitializer(this.methodResolver, this.reactiveAdapterRegistry); 176 } 177 178 179 @Override 180 public boolean supports(Object handler) { 181 return handler instanceof HandlerMethod; 182 } 183 184 @Override 185 public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) { 186 HandlerMethod handlerMethod = (HandlerMethod) handler; 187 Assert.state(this.methodResolver != null && this.modelInitializer != null, "Not initialized"); 188 189 InitBinderBindingContext bindingContext = new InitBinderBindingContext( 190 getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod)); 191 192 InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod); 193 194 Function<Throwable, Mono<HandlerResult>> exceptionHandler = 195 ex -> handleException(ex, handlerMethod, bindingContext, exchange); 196 197 return this.modelInitializer 198 .initModel(handlerMethod, bindingContext, exchange) 199 .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext))) 200 .doOnNext(result -> result.setExceptionHandler(exceptionHandler)) 201 .doOnNext(result -> bindingContext.saveModel()) 202 .onErrorResume(exceptionHandler); 203 } 204 205 private Mono<HandlerResult> handleException(Throwable exception, HandlerMethod handlerMethod, 206 BindingContext bindingContext, ServerWebExchange exchange) { 207 208 Assert.state(this.methodResolver != null, "Not initialized"); 209 210 // Success and error responses may use different content types 211 exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 212 exchange.getResponse().getHeaders().clearContentHeaders(); 213 214 InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod); 215 if (invocable != null) { 216 try { 217 if (logger.isDebugEnabled()) { 218 logger.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable); 219 } 220 bindingContext.getModel().asMap().clear(); 221 Throwable cause = exception.getCause(); 222 if (cause != null) { 223 return invocable.invoke(exchange, bindingContext, exception, cause, handlerMethod); 224 } 225 else { 226 return invocable.invoke(exchange, bindingContext, exception, handlerMethod); 227 } 228 } 229 catch (Throwable invocationEx) { 230 if (logger.isWarnEnabled()) { 231 logger.warn(exchange.getLogPrefix() + "Failure in @ExceptionHandler " + invocable, invocationEx); 232 } 233 } 234 } 235 return Mono.error(exception); 236 } 237 238}