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 c884775c86707d96b68adf0a58a0d74dd94fc77e..950daea0dbe622cd389f5f2d5a8d8ec19405af6a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * 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. @@ -126,6 +126,10 @@ import org.springframework.core.annotation.AliasFor; * {@link org.springframework.validation.Errors} argument. * Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException} * exception is raised. + *
  • {@link SessionAttribute @SessionAttribute} annotated parameters for access + * 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 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 new file mode 100644 index 0000000000000000000000000000000000000000..9ed52c3d26d1a76d035a551555eb5145ddc8b009 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/SessionAttribute.java @@ -0,0 +1,73 @@ +/* + * 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 session attribute. + * + *

    The main motivation is to provide convenient access to existing, permanent + * session attributes (e.g. user authentication object) with an optional/required + * check and a cast to the target method parameter type. + * + *

    For use cases that require adding or removing session attributes consider + * injecting {@code org.springframework.web.context.request.WebRequest} or + * {@code javax.servlet.http.HttpSession} into the controller method. + * + *

    For temporary storage of model attributes in the session as part of the + * workflow for a controller, consider using {@link SessionAttributes} instead. + * + * @author Rossen Stoyanchev + * @since 4.3 + * @see RequestMapping + * @see SessionAttributes + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SessionAttribute { + + /** + * Alias for {@link #name}. + */ + @AliasFor("name") + String value() default ""; + + /** + * The name of the session attribute to bind to. + *

    The default name is inferred from the method parameter name. + */ + @AliasFor("value") + String name() default ""; + + /** + * Whether the session attribute is required. + *

    Defaults to {@code true}, leading to an exception being thrown + * if the attribute is missing in the session or there is no session. + * 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-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 590c06b1c4e287b285e4c38ba49f6e93855918f8..24b5f767f6de314b461251539224fdff6cee538f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * 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. @@ -290,6 +290,9 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce protected List getDefaultArgumentResolvers() { List resolvers = new ArrayList(); + // Annotation-based argument resolution + resolvers.add(new SessionAttributeMethodArgumentResolver()); + // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); 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 f19c0774a4f91fda089cd6134ed117f9c0826f12..597ef15eda3cdf967dfaef4e5fdc066cf8669ae7 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 @@ -600,6 +600,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); + resolvers.add(new SessionAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); @@ -639,6 +640,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); + resolvers.add(new SessionAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); @@ -788,46 +790,50 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); + try { + WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); + ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); + + ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); + invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); + invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); + invocableMethod.setDataBinderFactory(binderFactory); + invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); + + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); + mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); + modelFactory.initModel(webRequest, mavContainer, invocableMethod); + mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); + + AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); + asyncWebRequest.setTimeout(this.asyncRequestTimeout); + + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + asyncManager.setTaskExecutor(this.taskExecutor); + asyncManager.setAsyncWebRequest(asyncWebRequest); + asyncManager.registerCallableInterceptors(this.callableInterceptors); + asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); + + if (asyncManager.hasConcurrentResult()) { + Object result = asyncManager.getConcurrentResult(); + mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; + asyncManager.clearConcurrentResult(); + if (logger.isDebugEnabled()) { + logger.debug("Found concurrent result value [" + result + "]"); + } + invocableMethod = invocableMethod.wrapConcurrentResult(result); + } - WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); - ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); - - ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); - invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); - invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); - invocableMethod.setDataBinderFactory(binderFactory); - invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); - - ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); - modelFactory.initModel(webRequest, mavContainer, invocableMethod); - mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); - - AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); - asyncWebRequest.setTimeout(this.asyncRequestTimeout); - - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); - asyncManager.setTaskExecutor(this.taskExecutor); - asyncManager.setAsyncWebRequest(asyncWebRequest); - asyncManager.registerCallableInterceptors(this.callableInterceptors); - asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); - - if (asyncManager.hasConcurrentResult()) { - Object result = asyncManager.getConcurrentResult(); - mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; - asyncManager.clearConcurrentResult(); - if (logger.isDebugEnabled()) { - logger.debug("Found concurrent result value [" + result + "]"); + invocableMethod.invokeAndHandle(webRequest, mavContainer); + if (asyncManager.isConcurrentHandlingStarted()) { + return null; } - invocableMethod = invocableMethod.wrapConcurrentResult(result); - } - invocableMethod.invokeAndHandle(webRequest, mavContainer); - if (asyncManager.isConcurrentHandlingStarted()) { - return null; + return getModelAndView(mavContainer, modelFactory, webRequest); + } + finally { + webRequest.requestCompleted(); } - - return getModelAndView(mavContainer, modelFactory, webRequest); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..88b2f1965da012c758ac26c444a88d64c5dcb17d --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.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.SessionAttribute; +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 SessionAttribute}. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(SessionAttribute.class); + } + + + @Override + protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { + SessionAttribute annot = parameter.getParameterAnnotation(SessionAttribute.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_SESSION); + } + + @Override + protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException { + throw new ServletRequestBindingException("Missing session attribute '" + name + + "' of type " + parameter.getNestedParameterType().getSimpleName()); + } + +} 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 5ba4bb5499710a4d8fdf966db56016c39fcf980f..0194af64f29975ef4f9515ab337f532956a322b7 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 @@ -68,6 +68,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.SessionAttribute; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.SessionStatus; @@ -148,12 +149,13 @@ 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, User.class, OtherUser.class, - Model.class, UriComponentsBuilder.class }; + Color.class, HttpServletRequest.class, HttpServletResponse.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(); request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.addHeader("header", "headerValue"); @@ -171,6 +173,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { Map uriTemplateVars = new HashMap(); uriTemplateVars.put("pathvar", "pathvarValue"); request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars); + request.getSession().setAttribute("sessionAttribute", sessionAttribute); HandlerMethod handlerMethod = handlerMethod("handle", parameterTypes); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); @@ -215,6 +218,8 @@ public class RequestMappingHandlerAdapterIntegrationTests { assertEquals(User.class, model.get("user").getClass()); assertEquals(OtherUser.class, model.get("otherUser").getClass()); + assertSame(sessionAttribute, model.get("sessionAttribute")); + assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url")); } @@ -363,6 +368,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { Color customArg, HttpServletRequest request, HttpServletResponse response, + @SessionAttribute TestBean sessionAttribute, User user, @ModelAttribute OtherUser otherUser, Model model, @@ -373,6 +379,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { .addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap) .addAttribute("paramByConvention", paramByConvention).addAttribute("value", value) .addAttribute("customArg", customArg).addAttribute(user) + .addAttribute("sessionAttribute", sessionAttribute) .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 new file mode 100644 index 0000000000000000000000000000000000000000..f59e914cccb83cebd9a395ef9aa5b56ad47b178f --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolverTests.java @@ -0,0 +1,168 @@ +/* + * 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 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; + + +/** + * Unit tests for {@link SessionAttributeMethodArgumentResolver}. + * @author Rossen Stoyanchev + */ +public class SessionAttributeMethodArgumentResolverTests { + + private ServletWebRequest webRequest; + + private MockHttpServletRequest servletRequest; + + private SessionAttributeMethodArgumentResolver resolver; + + private Method handleMethod; + + + @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); + } + + + @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 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 { + } + +} diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc index cb0c19ac6b92b53c8195e796ddb6d9d3dd30342b..6455eb1001505fd8690afc32700c6c585a8dd6b7 100644 --- a/src/asciidoc/web-mvc.adoc +++ b/src/asciidoc/web-mvc.adoc @@ -1250,6 +1250,10 @@ multiple requests are allowed to access a session concurrently. * `@RequestPart` annotated parameters for access to the content of a "multipart/form-data" request part. See <> and <>. +* `@SessionAttribute` annotated parameters for access 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 `@SessionAttributes`. * `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 <>. @@ -1765,6 +1769,32 @@ attribute name: ---- +[[mvc-ann-sessionattrib-global]] +==== Using @SessionAttribute to access pre-existing global session attributes + +If you need access to pre-existing session attributes that are managed globally, +i.e. outside the controller (e.g. by a filter), and may or may not be present +use the `@SessionAttribute` annotation on a method parameter: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @RequestMapping("/") + public String handle(**@SessionAttribute** User user) { + // ... + } +---- + +For use cases that require adding or removing session attributes consider injecting +`org.springframework.web.context.request.WebRequest` or +`javax.servlet.http.HttpSession` into the controller method. + +For temporary storage of model attributes in the session as part of a controller +workflow consider using `SessionAttributes` as described in +<>. + + + [[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 505b7f284536d349650f9519fda87fe70c7f0a35..ed4c96537f680223ffe26beec9949708a655128a 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -662,8 +662,9 @@ Spring 4.3 also improves the caching abstraction as follows: === Web Improvements * Built-in support for <>. -* New `@RestControllerAdvice` annotation combines `@ControllerAdvice` with `@ResponseBody`. -* `@ResponseStatus` can be used on a controller type and is inherited for all method. +* 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 <>). * `AsyncRestTemplate` supports request interception. === WebSocket Messaging Improvements