Browse Source

Introduce composed annotations for web scopes

This commit introduces the following common composed annotations for
web scopes.

- @RequestScope
- @SessionScope
- @ApplicationScope

Issue: SPR-13993
pull/982/head
Sam Brannen 9 years ago
parent
commit
b423596b2e
  1. 66
      spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java
  2. 66
      spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java
  3. 66
      spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java
  4. 5
      src/asciidoc/whats-new.adoc
  5. 89
      src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java

66
spring-web/src/main/java/org/springframework/web/context/annotation/ApplicationScope.java

@ -0,0 +1,66 @@ @@ -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.
*
* <p>Specifically, {@code @ApplicationScope} is a <em>composed annotation</em> that
* acts as a shortcut for {@code @Scope("application")} with the default
* {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
*
* <p>{@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}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

66
spring-web/src/main/java/org/springframework/web/context/annotation/RequestScope.java

@ -0,0 +1,66 @@ @@ -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.
*
* <p>Specifically, {@code @RequestScope} is a <em>composed annotation</em> that
* acts as a shortcut for {@code @Scope("request")} with the default
* {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
*
* <p>{@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}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

66
spring-web/src/main/java/org/springframework/web/context/annotation/SessionScope.java

@ -0,0 +1,66 @@ @@ -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.
*
* <p>Specifically, {@code @SessionScope} is a <em>composed annotation</em> that
* acts as a shortcut for {@code @Scope("session")} with the default
* {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}.
*
* <p>{@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}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

5
src/asciidoc/whats-new.adoc

@ -662,10 +662,11 @@ Spring 4.3 also improves the caching abstraction as follows: @@ -662,10 +662,11 @@ Spring 4.3 also improves the caching abstraction as follows:
=== Web Improvements
* Built-in support for <<mvc-ann-requestmapping-head-options,HTTP HEAD and HTTP OPTIONS>>.
* 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 <<mvc-ann-sessionattrib-global, example>>).
* New `@RequestAttribute` annotation for access to session attributes (see <<mvc-ann-requestattrib, example>>).
* New `@RequestAttribute` annotation for access to request attributes (see <<mvc-ann-requestattrib, example>>).
* `@ModelAttribute` allows preventing data binding via `binding=false` attribute (see <<mvc-ann-modelattrib-method-args, reference>>).
* `AsyncRestTemplate` supports request interception.

89
src/test/java/org/springframework/context/annotation/scope/ClassPathBeanDefinitionScannerScopeIntegrationTests.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -72,14 +72,14 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -98,9 +98,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -119,9 +119,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -140,9 +140,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -161,9 +161,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -182,9 +182,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -203,9 +203,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -224,9 +224,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -251,9 +251,9 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
@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 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -283,12 +283,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
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 class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -300,7 +295,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
public static interface IScopedTestBean {
static interface IScopedTestBean {
String getName();
@ -308,7 +303,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -308,7 +303,7 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
}
public static abstract class ScopedTestBean implements IScopedTestBean {
static abstract class ScopedTestBean implements IScopedTestBean {
private String name = DEFAULT_NAME;
@ -321,23 +316,23 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests { @@ -321,23 +316,23 @@ public class ClassPathBeanDefinitionScannerScopeIntegrationTests {
@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 {
}
}

Loading…
Cancel
Save