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 { } }