From bb816c123ca86c6f373ee2bd1632f43c117a8f15 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 13 Jul 2021 19:38:24 +0100 Subject: [PATCH] Use MessageSource in HandlerMethod for error reason Closes gh-27156 --- .../web/method/HandlerMethod.java | 30 ++++++++++++- .../method/AbstractHandlerMethodMapping.java | 4 +- .../handler/AbstractHandlerMethodMapping.java | 4 +- .../ServletInvocableHandlerMethodTests.java | 44 +++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index decc2d9000a9..bbcef56c8bab 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -29,6 +29,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; @@ -70,6 +72,9 @@ public class HandlerMethod { @Nullable private final BeanFactory beanFactory; + @Nullable + private final MessageSource messageSource; + private final Class beanType; private final Method method; @@ -101,6 +106,7 @@ public HandlerMethod(Object bean, Method method) { Assert.notNull(method, "Method is required"); this.bean = bean; this.beanFactory = null; + this.messageSource = null; this.beanType = ClassUtils.getUserClass(bean); this.method = method; this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); @@ -118,6 +124,7 @@ public HandlerMethod(Object bean, String methodName, Class... parameterTypes) Assert.notNull(methodName, "Method name is required"); this.bean = bean; this.beanFactory = null; + this.messageSource = null; this.beanType = ClassUtils.getUserClass(bean); this.method = bean.getClass().getMethod(methodName, parameterTypes); this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method); @@ -132,11 +139,23 @@ public HandlerMethod(Object bean, String methodName, Class... parameterTypes) * re-create the {@code HandlerMethod} with an initialized bean. */ public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { + this(beanName, beanFactory, null, method); + } + + /** + * Variant of {@link #HandlerMethod(String, BeanFactory, Method)} that + * also accepts a {@link MessageSource}. + */ + public HandlerMethod( + String beanName, BeanFactory beanFactory, + @Nullable MessageSource messageSource, Method method) { + Assert.hasText(beanName, "Bean name is required"); Assert.notNull(beanFactory, "BeanFactory is required"); Assert.notNull(method, "Method is required"); this.bean = beanName; this.beanFactory = beanFactory; + this.messageSource = messageSource; Class beanType = beanFactory.getType(beanName); if (beanType == null) { throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'"); @@ -156,6 +175,7 @@ protected HandlerMethod(HandlerMethod handlerMethod) { Assert.notNull(handlerMethod, "HandlerMethod is required"); this.bean = handlerMethod.bean; this.beanFactory = handlerMethod.beanFactory; + this.messageSource = handlerMethod.messageSource; this.beanType = handlerMethod.beanType; this.method = handlerMethod.method; this.bridgedMethod = handlerMethod.bridgedMethod; @@ -174,6 +194,7 @@ private HandlerMethod(HandlerMethod handlerMethod, Object handler) { Assert.notNull(handler, "Handler object is required"); this.bean = handler; this.beanFactory = handlerMethod.beanFactory; + this.messageSource = handlerMethod.messageSource; this.beanType = handlerMethod.beanType; this.method = handlerMethod.method; this.bridgedMethod = handlerMethod.bridgedMethod; @@ -199,8 +220,13 @@ private void evaluateResponseStatus() { annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); } if (annotation != null) { + String reason = annotation.reason(); + String resolvedReason = (StringUtils.hasText(reason) && this.messageSource != null ? + this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) : + reason); + this.responseStatus = annotation.code(); - this.responseStatusReason = annotation.reason(); + this.responseStatusReason = resolvedReason; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index d28af9d89fe1..93ff062651e1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -258,7 +258,9 @@ protected void registerHandlerMethod(Object handler, Method method, T mapping) { protected HandlerMethod createHandlerMethod(Object handler, Method method) { if (handler instanceof String) { return new HandlerMethod((String) handler, - obtainApplicationContext().getAutowireCapableBeanFactory(), method); + obtainApplicationContext().getAutowireCapableBeanFactory(), + obtainApplicationContext(), + method); } return new HandlerMethod(handler, method); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index f0e70267d1a1..367ca74997da 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -337,7 +337,9 @@ protected void registerHandlerMethod(Object handler, Method method, T mapping) { protected HandlerMethod createHandlerMethod(Object handler, Method method) { if (handler instanceof String) { return new HandlerMethod((String) handler, - obtainApplicationContext().getAutowireCapableBeanFactory(), method); + obtainApplicationContext().getAutowireCapableBeanFactory(), + obtainApplicationContext(), + method); } return new HandlerMethod(handler, method); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java index c20cedc9a2a8..d26e4cc0c1fe 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletResponse; @@ -29,6 +30,9 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AliasFor; @@ -47,12 +51,14 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.filter.ShallowEtagHeaderFilter; +import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.view.RedirectView; +import org.springframework.web.testfixture.method.ResolvableMethod; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; @@ -180,6 +186,39 @@ public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exceptio .as("When a status reason w/ used, the request is handled").isTrue(); } + @Test + public void invokeAndHandle_responseStatusAndReasonCode() throws Exception { + Locale locale = Locale.ENGLISH; + + String beanName = "handler"; + StaticApplicationContext context = new StaticApplicationContext(); + context.registerBean(beanName, Handler.class); + context.addMessage("BadRequest.error", locale, "Bad request message"); + context.refresh(); + + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + System.out.println(beanFactory.getType(beanName)); + + LocaleContextHolder.setLocale(locale); + try { + Method method = ResolvableMethod.on(Handler.class) + .named("responseStatusWithReasonCode") + .resolveMethod(); + + HandlerMethod handlerMethod = new HandlerMethod(beanName, beanFactory, context, method); + handlerMethod = handlerMethod.createWithResolvedBean(); + + new ServletInvocableHandlerMethod(handlerMethod) + .invokeAndHandle(this.webRequest, this.mavContainer); + } + finally { + LocaleContextHolder.resetLocaleContext(); + } + + assertThat(this.response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + assertThat(this.response.getErrorMessage()).isEqualTo("Bad request message"); + } + @Test // gh-23775, gh-24635 public void invokeAndHandle_ETagFilterHasNoImpactWhenETagPresent() throws Exception { @@ -417,6 +456,11 @@ public String responseStatusWithReason() { return "foo"; } + @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "BadRequest.error") + public String responseStatusWithReasonCode() { + return "foo"; + } + @ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST) public void composedResponseStatus() { }