diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java
new file mode 100644
index 00000000000..edbf720fef0
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestAttribute.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2015 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
+ *
+ * http://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.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation to bind a method parameter to a request attribute.
+ *
+ *
The main motivation is to provide convenient access to request attributes
+ * from a controller method with an optional/required check and a cast to the
+ * target method parameter type.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see RequestMapping
+ * @see SessionAttribute
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestAttribute {
+
+ /**
+ * Alias for {@link #name}.
+ */
+ @AliasFor("name")
+ String value() default "";
+
+ /**
+ * The name of the request attribute to bind to.
+ *
The default name is inferred from the method parameter name.
+ */
+ @AliasFor("value")
+ String name() default "";
+
+ /**
+ * Whether the request attribute is required.
+ *
Defaults to {@code true}, leading to an exception being thrown
+ * if the attribute is missing. Switch this to {@code false} if you prefer
+ * a {@code null} or Java 1.8+ {@code java.util.Optional} if the attribute
+ * doesn't exist.
+ */
+ boolean required() default true;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 950daea0dbe..e4430e9e4f0 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -130,6 +130,8 @@ import org.springframework.core.annotation.AliasFor;
* to existing, permanent session attributes (e.g. user authentication object)
* as opposed to model attributes temporarily stored in the session as part of
* a controller workflow via {@link SessionAttributes}.
+ *
{@link RequestAttribute @RequestAttribute} annotated parameters for access
+ * to request attributes.
* {@link org.springframework.http.HttpEntity HttpEntity<?>} parameters
* (Servlet-only) for access to the Servlet request HTTP headers and contents.
* The request stream will be converted to the entity body using
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java
index 9ed52c3d26d..71def69d214 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java
@@ -42,6 +42,7 @@ import org.springframework.core.annotation.AliasFor;
* @since 4.3
* @see RequestMapping
* @see SessionAttributes
+ * @see RequestAttribute
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
index 24b5f767f6d..76f9aa084b6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
@@ -292,6 +292,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
// Annotation-based argument resolution
resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
new file mode 100644
index 00000000000..3399068ea26
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.servlet.mvc.method.annotation;
+
+import javax.servlet.ServletException;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
+
+/**
+ * Resolves method arguments annotated with an @{@link RequestAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(RequestAttribute.class);
+ }
+
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestAttribute annot = parameter.getParameterAnnotation(RequestAttribute.class);
+ return new NamedValueInfo(annot.name(), annot.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
+ return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing request attribute '" + name +
+ "' of type " + parameter.getNestedParameterType().getSimpleName());
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
index 597ef15eda3..74d579e5d11 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -601,6 +601,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
@@ -641,6 +642,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
new file mode 100644
index 00000000000..3369883e7fe
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.bind.support.WebRequestDataBinder;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Base class for {@code @RequestAttribute} and {@code @SessionAttribute} method
+ * method argument resolution tests.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public abstract class AbstractRequestAttributesArgumentResolverTests {
+
+ private ServletWebRequest webRequest;
+
+ private HandlerMethodArgumentResolver resolver;
+
+ private Method handleMethod;
+
+
+ @Before
+ public void setUp() throws Exception {
+ HttpServletRequest request = new MockHttpServletRequest();
+ HttpServletResponse response = new MockHttpServletResponse();
+ this.webRequest = new ServletWebRequest(request, response);
+ this.resolver = createResolver();
+ this.handleMethod = AbstractRequestAttributesArgumentResolverTests.class
+ .getDeclaredMethod(getHandleMethodName(), Foo.class, Foo.class, Foo.class, Optional.class);
+ }
+
+
+ protected abstract HandlerMethodArgumentResolver createResolver();
+
+ protected abstract String getHandleMethodName();
+
+ protected abstract int getScope();
+
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
+ assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
+ }
+
+ @Test
+ public void resolve() throws Exception {
+ MethodParameter param = initMethodParameter(0);
+ try {
+ testResolveArgument(param);
+ fail("Should be required by default");
+ }
+ catch (ServletRequestBindingException ex) {
+ assertTrue(ex.getMessage().startsWith("Missing "));
+ }
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveWithName() throws Exception {
+ MethodParameter param = initMethodParameter(1);
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("specialFoo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+ MethodParameter param = initMethodParameter(2);
+ assertNull(testResolveArgument(param));
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+ assertSame(foo, testResolveArgument(param));
+ }
+
+ @Test
+ public void resolveOptional() throws Exception {
+ WebDataBinder dataBinder = new WebRequestDataBinder(null);
+ dataBinder.setConversionService(new DefaultConversionService());
+ WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
+ given(factory.createBinder(this.webRequest, null, "foo")).willReturn(dataBinder);
+
+ MethodParameter param = initMethodParameter(3);
+ Object actual = testResolveArgument(param, factory);
+ assertNotNull(actual);
+ assertEquals(Optional.class, actual.getClass());
+ assertFalse(((Optional) actual).isPresent());
+
+ Foo foo = new Foo();
+ this.webRequest.setAttribute("foo", foo, getScope());
+
+ actual = testResolveArgument(param, factory);
+ assertNotNull(actual);
+ assertEquals(Optional.class, actual.getClass());
+ assertTrue(((Optional) actual).isPresent());
+ assertSame(foo, ((Optional) actual).get());
+ }
+
+ private Object testResolveArgument(MethodParameter param) throws Exception {
+ return testResolveArgument(param, null);
+ }
+
+ private Object testResolveArgument(MethodParameter param, WebDataBinderFactory factory) throws Exception {
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ return this.resolver.resolveArgument(param, mavContainer, this.webRequest, factory);
+ }
+
+ private MethodParameter initMethodParameter(int parameterIndex) {
+ MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
+ param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
+ return param;
+ }
+
+
+ @SuppressWarnings("unused")
+ private void handleWithRequestAttribute(
+ @RequestAttribute Foo foo,
+ @RequestAttribute("specialFoo") Foo namedFoo,
+ @RequestAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @RequestAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ @SuppressWarnings("unused")
+ private void handleWithSessionAttribute(
+ @SessionAttribute Foo foo,
+ @SessionAttribute("specialFoo") Foo namedFoo,
+ @SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @SessionAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ private static class Foo {
+ }
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java
new file mode 100644
index 00000000000..90f875b26b1
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolverTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.servlet.mvc.method.annotation;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+
+
+/**
+ * Unit tests for {@link RequestAttributeMethodArgumentResolver}.
+ * @author Rossen Stoyanchev
+ */
+public class RequestAttributeMethodArgumentResolverTests
+ extends AbstractRequestAttributesArgumentResolverTests {
+
+
+ @Override
+ protected HandlerMethodArgumentResolver createResolver() {
+ return new RequestAttributeMethodArgumentResolver();
+ }
+
+ @Override
+ protected String getHandleMethodName() {
+ return "handleWithRequestAttribute";
+ }
+
+ @Override
+ protected int getScope() {
+ return RequestAttributes.SCOPE_REQUEST;
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
index 0194af64f29..b9f2e8489ec 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
@@ -62,6 +62,7 @@ import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@@ -149,13 +150,14 @@ public class RequestMappingHandlerAdapterIntegrationTests {
public void handle() throws Exception {
Class>[] parameterTypes = new Class>[] { int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
- Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class,
+ Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class,
User.class, OtherUser.class, Model.class, UriComponentsBuilder.class };
String datePattern = "yyyy.MM.dd";
String formattedDate = "2011.03.16";
Date date = new GregorianCalendar(2011, Calendar.MARCH, 16).getTime();
TestBean sessionAttribute = new TestBean();
+ TestBean requestAttribute = new TestBean();
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.addHeader("header", "headerValue");
@@ -174,6 +176,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
uriTemplateVars.put("pathvar", "pathvarValue");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
request.getSession().setAttribute("sessionAttribute", sessionAttribute);
+ request.setAttribute("requestAttribute", requestAttribute);
HandlerMethod handlerMethod = handlerMethod("handle", parameterTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
@@ -219,6 +222,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals(OtherUser.class, model.get("otherUser").getClass());
assertSame(sessionAttribute, model.get("sessionAttribute"));
+ assertSame(requestAttribute, model.get("requestAttribute"));
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
}
@@ -369,6 +373,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
HttpServletRequest request,
HttpServletResponse response,
@SessionAttribute TestBean sessionAttribute,
+ @RequestAttribute TestBean requestAttribute,
User user,
@ModelAttribute OtherUser otherUser,
Model model,
@@ -380,6 +385,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
.addAttribute("customArg", customArg).addAttribute(user)
.addAttribute("sessionAttribute", sessionAttribute)
+ .addAttribute("requestAttribute", requestAttribute)
.addAttribute("url", builder.path("/path").build().toUri());
assertNotNull(request);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java
index f59e914cccb..5a4f76f9abd 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java
@@ -15,154 +15,31 @@
*/
package org.springframework.web.servlet.mvc.method.annotation;
-import java.lang.reflect.Method;
-import java.util.Optional;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.GenericTypeResolver;
-import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.SynthesizingMethodParameter;
-import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.mock.web.test.MockHttpServletRequest;
-import org.springframework.mock.web.test.MockHttpServletResponse;
-import org.springframework.web.bind.ServletRequestBindingException;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.SessionAttribute;
-import org.springframework.web.bind.support.WebDataBinderFactory;
-import org.springframework.web.bind.support.WebRequestDataBinder;
-import org.springframework.web.context.request.ServletWebRequest;
-import org.springframework.web.method.support.ModelAndViewContainer;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
/**
* Unit tests for {@link SessionAttributeMethodArgumentResolver}.
* @author Rossen Stoyanchev
*/
-public class SessionAttributeMethodArgumentResolverTests {
-
- private ServletWebRequest webRequest;
-
- private MockHttpServletRequest servletRequest;
-
- private SessionAttributeMethodArgumentResolver resolver;
-
- private Method handleMethod;
+public class SessionAttributeMethodArgumentResolverTests
+ extends AbstractRequestAttributesArgumentResolverTests {
- @Before
- public void setUp() throws Exception {
- this.servletRequest = new MockHttpServletRequest();
- this.webRequest = new ServletWebRequest(this.servletRequest, new MockHttpServletResponse());
- this.resolver = new SessionAttributeMethodArgumentResolver();
- this.handleMethod = getClass().getDeclaredMethod("handle", Foo.class, Foo.class,
- Foo.class, Optional.class, Foo.class);
+ @Override
+ protected HandlerMethodArgumentResolver createResolver() {
+ return new SessionAttributeMethodArgumentResolver();
}
-
- @Test
- public void supportsParameter() throws Exception {
- assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
- assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
+ @Override
+ protected String getHandleMethodName() {
+ return "handleWithSessionAttribute";
}
- @Test
- public void resolve() throws Exception {
- MethodParameter param = initMethodParameter(0);
- try {
- testResolveArgument(param);
- fail("Should be required by default");
- }
- catch (ServletRequestBindingException ex) {
- assertTrue(ex.getMessage().startsWith("Missing session attribute"));
- }
-
- Foo foo = new Foo();
- this.servletRequest.getSession().setAttribute("foo", foo);
- assertSame(foo, testResolveArgument(param));
- }
-
- @Test
- public void resolveWithName() throws Exception {
- MethodParameter param = initMethodParameter(1);
- Foo foo = new Foo();
- this.servletRequest.getSession().setAttribute("specialFoo", foo);
- assertSame(foo, testResolveArgument(param));
- }
-
- @Test
- public void resolveNotRequired() throws Exception {
- MethodParameter param = initMethodParameter(2);
- assertNull(testResolveArgument(param));
-
- Foo foo = new Foo();
- this.servletRequest.getSession().setAttribute("foo", foo);
- assertSame(foo, testResolveArgument(param));
- }
-
- @Test
- public void resolveOptional() throws Exception {
- WebDataBinder dataBinder = new WebRequestDataBinder(null);
- dataBinder.setConversionService(new DefaultConversionService());
- WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
- given(factory.createBinder(this.webRequest, null, "foo")).willReturn(dataBinder);
-
- MethodParameter param = initMethodParameter(3);
- Object actual = testResolveArgument(param, factory);
- assertNotNull(actual);
- assertEquals(Optional.class, actual.getClass());
- assertFalse(((Optional) actual).isPresent());
-
- Foo foo = new Foo();
- this.servletRequest.getSession().setAttribute("foo", foo);
-
- actual = testResolveArgument(param, factory);
- assertNotNull(actual);
- assertEquals(Optional.class, actual.getClass());
- assertTrue(((Optional) actual).isPresent());
- assertSame(foo, ((Optional) actual).get());
- }
-
-
- private Object testResolveArgument(MethodParameter param) throws Exception {
- return testResolveArgument(param, null);
- }
-
- private Object testResolveArgument(MethodParameter param, WebDataBinderFactory factory) throws Exception {
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- return this.resolver.resolveArgument(param, mavContainer, this.webRequest, factory);
- }
-
- private MethodParameter initMethodParameter(int parameterIndex) {
- MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
- param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
- GenericTypeResolver.resolveParameterType(param, SessionAttributeMethodArgumentResolver.class);
- return param;
- }
-
-
- @SuppressWarnings("unused")
- private void handle(
- @SessionAttribute Foo foo,
- @SessionAttribute("specialFoo") Foo namedFoo,
- @SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
- @SessionAttribute(name="foo") Optional optionalFoo,
- Foo notAnnotatedFoo) {
- }
-
- private static class Foo {
+ @Override
+ protected int getScope() {
+ return RequestAttributes.SCOPE_SESSION;
}
}
diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc
index 6455eb10015..d7fd6b91840 100644
--- a/src/asciidoc/web-mvc.adoc
+++ b/src/asciidoc/web-mvc.adoc
@@ -1254,6 +1254,7 @@ multiple requests are allowed to access a session concurrently.
session attributes (e.g. user authentication object) as opposed to model
attributes temporarily stored in the session as part of a controller workflow
via `@SessionAttributes`.
+* `@RequestAttribute` annotated parameters for access to request attributes.
* `HttpEntity>` parameters for access to the Servlet request HTTP headers and
contents. The request stream will be converted to the entity body using
++HttpMessageConverter++s. See <>.
@@ -1794,6 +1795,23 @@ workflow consider using `SessionAttributes` as described in
<>.
+[[mvc-ann-requestattrib]]
+==== Using @RequestAttribute to access request attributes
+
+Similar to `@SessionAttribute` the `@RequestAttribute` annotation can be used to
+access pre-existing request attributes created by a filter or interceptor:
+
+[source,java,indent=0]
+[subs="verbatim,quotes"]
+----
+ @RequestMapping("/")
+ public String handle(**@RequestAttribute** Client client) {
+ // ...
+ }
+----
+
+
+
[[mvc-ann-form-urlencoded-data]]
==== Working with "application/x-www-form-urlencoded" data
diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc
index ed4c96537f6..60fd8a0c805 100644
--- a/src/asciidoc/whats-new.adoc
+++ b/src/asciidoc/whats-new.adoc
@@ -665,6 +665,7 @@ Spring 4.3 also improves the caching abstraction as follows:
* New `@RestControllerAdvice` annotation with combined `@ControllerAdvice` with `@ResponseBody` semantics.
* `@ResponseStatus` supported on the class level and inherited on all methods.
* New `@SessionAttribute` annotation for access to session attributes (see <>).
+* New `@RequestAttribute` annotation for access to session attributes (see <>).
* `AsyncRestTemplate` supports request interception.
=== WebSocket Messaging Improvements