diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java
new file mode 100644
index 000000000000..b423e495df1d
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java
@@ -0,0 +1,66 @@
+/*
+ * 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.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.springframework.web.context.WebApplicationContext.SCOPE_APPLICATION;
+
+/**
+ * {@code @ApplicationScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web application.
+ *
+ *
Specifically, {@code @ApplicationScope} is a composed annotation that
+ * acts as a shortcut for {@code @Scope("application")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ *
{@code @ApplicationScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see RequestScope
+ * @see SessionScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION
+ * @see org.springframework.web.context.support.ServletContextScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Scope(SCOPE_APPLICATION)
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+@Documented
+public @interface ApplicationScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ *
Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java
new file mode 100644
index 000000000000..8b69106c2537
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java
@@ -0,0 +1,66 @@
+/*
+ * 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.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST;
+
+/**
+ * {@code @RequestScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web request.
+ *
+ *
Specifically, {@code @RequestScope} is a composed annotation that
+ * acts as a shortcut for {@code @Scope("request")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ *
{@code @RequestScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see SessionScope
+ * @see ApplicationScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
+ * @see org.springframework.web.context.request.RequestScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Scope(SCOPE_REQUEST)
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+@Documented
+public @interface RequestScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ *
Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java b/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java
new file mode 100644
index 000000000000..3185b9e4a656
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java
@@ -0,0 +1,66 @@
+/*
+ * 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.context.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.springframework.web.context.WebApplicationContext.SCOPE_SESSION;
+
+/**
+ * {@code @SessionScope} is a specialization of {@link Scope @Scope} for a
+ * component whose lifecycle is bound to the current web session.
+ *
+ *
Specifically, {@code @SessionScope} is a composed annotation that
+ * acts as a shortcut for {@code @Scope("session")} with the default
+ * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
+ *
+ *
{@code @SessionScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ * @see RequestScope
+ * @see ApplicationScope
+ * @see org.springframework.context.annotation.Scope
+ * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
+ * @see org.springframework.web.context.request.SessionScope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Scope(SCOPE_SESSION)
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+@Documented
+public @interface SessionScope {
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ *
Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc
index 3055500e261f..b0e0ed3537c8 100644
--- a/src/asciidoc/whats-new.adoc
+++ b/src/asciidoc/whats-new.adoc
@@ -662,10 +662,11 @@ Spring 4.3 also improves the caching abstraction as follows:
=== Web Improvements
* Built-in support for <>.
+* New `@RequestScope`, `@SessionScope`, and `@ApplicationScope` _composed annotations_ for web scopes.
* New `@RestControllerAdvice` annotation with combined `@ControllerAdvice` with `@ResponseBody` semantics.
-* `@ResponseStatus` supported on the class level and inherited on all methods.
+* `@ResponseStatus` is now supported at the class level and inherited by all methods.
* New `@SessionAttribute` annotation for access to session attributes (see <>).
-* New `@RequestAttribute` annotation for access to session attributes (see <>).
+* New `@RequestAttribute` annotation for access to request attributes (see <>).
* `@ModelAttribute` allows preventing data binding via `binding=false` attribute (see <>).
* `AsyncRestTemplate` supports request interception.
diff --git a/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java b/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
index e0712b69c660..02cd6cf2b9d1 100644
--- a/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
+++ b/src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -17,50 +17,50 @@
package org.springframework.context.annotation.scope;
import org.junit.After;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
-import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.stereotype.Component;
+import org.springframework.web.context.annotation.RequestScope;
+import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.GenericWebApplicationContext;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.context.annotation.ScopedProxyMode.DEFAULT;
+import static org.springframework.context.annotation.ScopedProxyMode.INTERFACES;
+import static org.springframework.context.annotation.ScopedProxyMode.NO;
+import static org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS;
+
/**
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
+ * @author Sam Brannen
*/
public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
private static final String DEFAULT_NAME = "default";
-
private static final String MODIFIED_NAME = "modified";
- private ServletRequestAttributes oldRequestAttributes;
-
- private ServletRequestAttributes newRequestAttributes;
+ private ServletRequestAttributes oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
+ private ServletRequestAttributes newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
private ServletRequestAttributes oldRequestAttributesWithSession;
-
private ServletRequestAttributes newRequestAttributesWithSession;
@Before
public void setUp() {
- this.oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
- this.newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
-
MockHttpServletRequest oldRequestWithSession = new MockHttpServletRequest();
oldRequestWithSession.setSession(new MockHttpSession());
this.oldRequestAttributesWithSession = new ServletRequestAttributes(oldRequestWithSession);
@@ -72,14 +72,14 @@ public void setUp() {
@After
public void tearDown() throws Exception {
- RequestContextHolder.setRequestAttributes(null);
+ RequestContextHolder.resetRequestAttributes();
}
@Test
- public void testSingletonScopeWithNoProxy() {
+ public void singletonScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -98,9 +98,9 @@ public void testSingletonScopeWithNoProxy() {
}
@Test
- public void testSingletonScopeIgnoresProxyInterfaces() {
+ public void singletonScopeIgnoresProxyInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -119,9 +119,9 @@ public void testSingletonScopeIgnoresProxyInterfaces() {
}
@Test
- public void testSingletonScopeIgnoresProxyTargetClass() {
+ public void singletonScopeIgnoresProxyTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
// should not be a proxy
@@ -140,9 +140,9 @@ public void testSingletonScopeIgnoresProxyTargetClass() {
}
@Test
- public void testRequestScopeWithNoProxy() {
+ public void requestScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("request");
// should not be a proxy
@@ -161,9 +161,9 @@ public void testRequestScopeWithNoProxy() {
}
@Test
- public void testRequestScopeWithProxiedInterfaces() {
+ public void requestScopeWithProxiedInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
// should be dynamic proxy, implementing both interfaces
@@ -182,9 +182,9 @@ public void testRequestScopeWithProxiedInterfaces() {
}
@Test
- public void testRequestScopeWithProxiedTargetClass() {
+ public void requestScopeWithProxiedTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
// should be a class-based proxy
@@ -203,9 +203,9 @@ public void testRequestScopeWithProxiedTargetClass() {
}
@Test
- public void testSessionScopeWithNoProxy() {
+ public void sessionScopeWithNoProxy() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.NO);
+ ApplicationContext context = createContext(NO);
ScopedTestBean bean = (ScopedTestBean) context.getBean("session");
// should not be a proxy
@@ -224,9 +224,9 @@ public void testSessionScopeWithNoProxy() {
}
@Test
- public void testSessionScopeWithProxiedInterfaces() {
+ public void sessionScopeWithProxiedInterfaces() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
+ ApplicationContext context = createContext(INTERFACES);
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
// should be dynamic proxy, implementing both interfaces
@@ -251,9 +251,9 @@ public void testSessionScopeWithProxiedInterfaces() {
}
@Test
- public void testSessionScopeWithProxiedTargetClass() {
+ public void sessionScopeWithProxiedTargetClass() {
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
- ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
+ ApplicationContext context = createContext(TARGET_CLASS);
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
// should be a class-based proxy
@@ -283,12 +283,7 @@ private ApplicationContext createContext(ScopedProxyMode scopedProxyMode) {
GenericWebApplicationContext context = new GenericWebApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
- scanner.setBeanNameGenerator(new BeanNameGenerator() {
- @Override
- public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
- return definition.getScope();
- }
- });
+ scanner.setBeanNameGenerator((definition, registry) -> definition.getScope());
scanner.setScopedProxyMode(scopedProxyMode);
// Scan twice in order to find errors in the bean definition compatibility check.
@@ -300,7 +295,7 @@ public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry
}
- public static interface IScopedTestBean {
+ static interface IScopedTestBean {
String getName();
@@ -308,7 +303,7 @@ public static interface IScopedTestBean {
}
- public static abstract class ScopedTestBean implements IScopedTestBean {
+ static abstract class ScopedTestBean implements IScopedTestBean {
private String name = DEFAULT_NAME;
@@ -321,23 +316,23 @@ public static abstract class ScopedTestBean implements IScopedTestBean {
@Component
- public static class SingletonScopedTestBean extends ScopedTestBean {
+ static class SingletonScopedTestBean extends ScopedTestBean {
}
- public static interface AnotherScopeTestInterface {
+ static interface AnotherScopeTestInterface {
}
@Component
- @Scope("request")
- public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
+ @RequestScope(proxyMode = DEFAULT)
+ static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
}
@Component
- @Scope("session")
- public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
+ @SessionScope(proxyMode = DEFAULT)
+ static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
}
}