1 /* 2 * Copyright 2002-2009 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.context.annotation.jsr330; 18 19 import java.lang.annotation.ElementType; 20 import java.lang.annotation.Retention; 21 import java.lang.annotation.RetentionPolicy; 22 import java.lang.annotation.Target; 23 import javax.inject.Named; 24 import javax.inject.Singleton; 25 26 import org.junit.After; 27 import static org.junit.Assert.*; 28 import org.junit.Before; 29 import org.junit.Test; 30 31 import org.springframework.aop.support.AopUtils; 32 import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 33 import org.springframework.beans.factory.config.BeanDefinition; 34 import org.springframework.context.ApplicationContext; 35 import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 36 import org.springframework.context.annotation.ScopeMetadata; 37 import org.springframework.context.annotation.ScopeMetadataResolver; 38 import org.springframework.context.annotation.ScopedProxyMode; 39 import org.springframework.mock.web.MockHttpServletRequest; 40 import org.springframework.mock.web.MockHttpSession; 41 import org.springframework.web.context.request.RequestContextHolder; 42 import org.springframework.web.context.request.ServletRequestAttributes; 43 import org.springframework.web.context.support.GenericWebApplicationContext; 44 45 /** 46 * @author Mark Fisher 47 * @author Juergen Hoeller 48 * @author Chris Beams 49 */ 50 public class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests { 51 52 private static final String DEFAULT_NAME = "default"; 53 54 private static final String MODIFIED_NAME = "modified"; 55 56 private ServletRequestAttributes oldRequestAttributes; 57 58 private ServletRequestAttributes newRequestAttributes; 59 60 private ServletRequestAttributes oldRequestAttributesWithSession; 61 62 private ServletRequestAttributes newRequestAttributesWithSession; 63 64 65 @Before setUp()66 public void setUp() { 67 this.oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); 68 this.newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); 69 70 MockHttpServletRequest oldRequestWithSession = new MockHttpServletRequest(); 71 oldRequestWithSession.setSession(new MockHttpSession()); 72 this.oldRequestAttributesWithSession = new ServletRequestAttributes(oldRequestWithSession); 73 74 MockHttpServletRequest newRequestWithSession = new MockHttpServletRequest(); 75 newRequestWithSession.setSession(new MockHttpSession()); 76 this.newRequestAttributesWithSession = new ServletRequestAttributes(newRequestWithSession); 77 } 78 79 @After tearDown()80 public void tearDown() throws Exception { 81 RequestContextHolder.setRequestAttributes(null); 82 } 83 84 85 @Test testPrototype()86 public void testPrototype() { 87 ApplicationContext context = createContext(ScopedProxyMode.NO); 88 ScopedTestBean bean = (ScopedTestBean) context.getBean("prototype"); 89 assertTrue(context.isPrototype("prototype")); 90 assertFalse(context.isSingleton("prototype")); 91 } 92 93 @Test testSingletonScopeWithNoProxy()94 public void testSingletonScopeWithNoProxy() { 95 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 96 ApplicationContext context = createContext(ScopedProxyMode.NO); 97 ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); 98 assertTrue(context.isSingleton("singleton")); 99 assertFalse(context.isPrototype("singleton")); 100 101 // should not be a proxy 102 assertFalse(AopUtils.isAopProxy(bean)); 103 104 assertEquals(DEFAULT_NAME, bean.getName()); 105 bean.setName(MODIFIED_NAME); 106 107 RequestContextHolder.setRequestAttributes(newRequestAttributes); 108 // not a proxy so this should not have changed 109 assertEquals(MODIFIED_NAME, bean.getName()); 110 111 // singleton bean, so name should be modified even after lookup 112 ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); 113 assertEquals(MODIFIED_NAME, bean2.getName()); 114 } 115 116 @Test testSingletonScopeIgnoresProxyInterfaces()117 public void testSingletonScopeIgnoresProxyInterfaces() { 118 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 119 ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); 120 ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); 121 122 // should not be a proxy 123 assertFalse(AopUtils.isAopProxy(bean)); 124 125 assertEquals(DEFAULT_NAME, bean.getName()); 126 bean.setName(MODIFIED_NAME); 127 128 RequestContextHolder.setRequestAttributes(newRequestAttributes); 129 // not a proxy so this should not have changed 130 assertEquals(MODIFIED_NAME, bean.getName()); 131 132 // singleton bean, so name should be modified even after lookup 133 ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); 134 assertEquals(MODIFIED_NAME, bean2.getName()); 135 } 136 137 @Test testSingletonScopeIgnoresProxyTargetClass()138 public void testSingletonScopeIgnoresProxyTargetClass() { 139 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 140 ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); 141 ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton"); 142 143 // should not be a proxy 144 assertFalse(AopUtils.isAopProxy(bean)); 145 146 assertEquals(DEFAULT_NAME, bean.getName()); 147 bean.setName(MODIFIED_NAME); 148 149 RequestContextHolder.setRequestAttributes(newRequestAttributes); 150 // not a proxy so this should not have changed 151 assertEquals(MODIFIED_NAME, bean.getName()); 152 153 // singleton bean, so name should be modified even after lookup 154 ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton"); 155 assertEquals(MODIFIED_NAME, bean2.getName()); 156 } 157 158 @Test testRequestScopeWithNoProxy()159 public void testRequestScopeWithNoProxy() { 160 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 161 ApplicationContext context = createContext(ScopedProxyMode.NO); 162 ScopedTestBean bean = (ScopedTestBean) context.getBean("request"); 163 164 // should not be a proxy 165 assertFalse(AopUtils.isAopProxy(bean)); 166 167 assertEquals(DEFAULT_NAME, bean.getName()); 168 bean.setName(MODIFIED_NAME); 169 170 RequestContextHolder.setRequestAttributes(newRequestAttributes); 171 // not a proxy so this should not have changed 172 assertEquals(MODIFIED_NAME, bean.getName()); 173 174 // but a newly retrieved bean should have the default name 175 ScopedTestBean bean2 = (ScopedTestBean) context.getBean("request"); 176 assertEquals(DEFAULT_NAME, bean2.getName()); 177 } 178 179 @Test testRequestScopeWithProxiedInterfaces()180 public void testRequestScopeWithProxiedInterfaces() { 181 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 182 ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); 183 IScopedTestBean bean = (IScopedTestBean) context.getBean("request"); 184 185 // should be dynamic proxy, implementing both interfaces 186 assertTrue(AopUtils.isJdkDynamicProxy(bean)); 187 assertTrue(bean instanceof AnotherScopeTestInterface); 188 189 assertEquals(DEFAULT_NAME, bean.getName()); 190 bean.setName(MODIFIED_NAME); 191 192 RequestContextHolder.setRequestAttributes(newRequestAttributes); 193 // this is a proxy so it should be reset to default 194 assertEquals(DEFAULT_NAME, bean.getName()); 195 196 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 197 assertEquals(MODIFIED_NAME, bean.getName()); 198 } 199 200 @Test testRequestScopeWithProxiedTargetClass()201 public void testRequestScopeWithProxiedTargetClass() { 202 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 203 ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); 204 IScopedTestBean bean = (IScopedTestBean) context.getBean("request"); 205 206 // should be a class-based proxy 207 assertTrue(AopUtils.isCglibProxy(bean)); 208 assertTrue(bean instanceof RequestScopedTestBean); 209 210 assertEquals(DEFAULT_NAME, bean.getName()); 211 bean.setName(MODIFIED_NAME); 212 213 RequestContextHolder.setRequestAttributes(newRequestAttributes); 214 // this is a proxy so it should be reset to default 215 assertEquals(DEFAULT_NAME, bean.getName()); 216 217 RequestContextHolder.setRequestAttributes(oldRequestAttributes); 218 assertEquals(MODIFIED_NAME, bean.getName()); 219 } 220 221 @Test testSessionScopeWithNoProxy()222 public void testSessionScopeWithNoProxy() { 223 RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); 224 ApplicationContext context = createContext(ScopedProxyMode.NO); 225 ScopedTestBean bean = (ScopedTestBean) context.getBean("session"); 226 227 // should not be a proxy 228 assertFalse(AopUtils.isAopProxy(bean)); 229 230 assertEquals(DEFAULT_NAME, bean.getName()); 231 bean.setName(MODIFIED_NAME); 232 233 RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); 234 // not a proxy so this should not have changed 235 assertEquals(MODIFIED_NAME, bean.getName()); 236 237 // but a newly retrieved bean should have the default name 238 ScopedTestBean bean2 = (ScopedTestBean) context.getBean("session"); 239 assertEquals(DEFAULT_NAME, bean2.getName()); 240 } 241 242 @Test testSessionScopeWithProxiedInterfaces()243 public void testSessionScopeWithProxiedInterfaces() { 244 RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); 245 ApplicationContext context = createContext(ScopedProxyMode.INTERFACES); 246 IScopedTestBean bean = (IScopedTestBean) context.getBean("session"); 247 248 // should be dynamic proxy, implementing both interfaces 249 assertTrue(AopUtils.isJdkDynamicProxy(bean)); 250 assertTrue(bean instanceof AnotherScopeTestInterface); 251 252 assertEquals(DEFAULT_NAME, bean.getName()); 253 bean.setName(MODIFIED_NAME); 254 255 RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); 256 // this is a proxy so it should be reset to default 257 assertEquals(DEFAULT_NAME, bean.getName()); 258 bean.setName(MODIFIED_NAME); 259 260 IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session"); 261 assertEquals(MODIFIED_NAME, bean2.getName()); 262 bean2.setName(DEFAULT_NAME); 263 assertEquals(DEFAULT_NAME, bean.getName()); 264 265 RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); 266 assertEquals(MODIFIED_NAME, bean.getName()); 267 } 268 269 @Test testSessionScopeWithProxiedTargetClass()270 public void testSessionScopeWithProxiedTargetClass() { 271 RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); 272 ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS); 273 IScopedTestBean bean = (IScopedTestBean) context.getBean("session"); 274 275 // should be a class-based proxy 276 assertTrue(AopUtils.isCglibProxy(bean)); 277 assertTrue(bean instanceof ScopedTestBean); 278 assertTrue(bean instanceof SessionScopedTestBean); 279 280 assertEquals(DEFAULT_NAME, bean.getName()); 281 bean.setName(MODIFIED_NAME); 282 283 RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession); 284 // this is a proxy so it should be reset to default 285 assertEquals(DEFAULT_NAME, bean.getName()); 286 bean.setName(MODIFIED_NAME); 287 288 IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session"); 289 assertEquals(MODIFIED_NAME, bean2.getName()); 290 bean2.setName(DEFAULT_NAME); 291 assertEquals(DEFAULT_NAME, bean.getName()); 292 293 RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession); 294 assertEquals(MODIFIED_NAME, bean.getName()); 295 } 296 297 createContext(final ScopedProxyMode scopedProxyMode)298 private ApplicationContext createContext(final ScopedProxyMode scopedProxyMode) { 299 GenericWebApplicationContext context = new GenericWebApplicationContext(); 300 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); 301 scanner.setIncludeAnnotationConfig(false); 302 scanner.setScopeMetadataResolver(new ScopeMetadataResolver() { 303 public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { 304 ScopeMetadata metadata = new ScopeMetadata(); 305 if (definition instanceof AnnotatedBeanDefinition) { 306 AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; 307 for (String type : annDef.getMetadata().getAnnotationTypes()) { 308 if (type.equals(javax.inject.Singleton.class.getName())) { 309 metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); 310 break; 311 } 312 else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { 313 metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); 314 metadata.setScopedProxyMode(scopedProxyMode); 315 break; 316 } 317 else if (type.startsWith("javax.inject")) { 318 metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); 319 } 320 } 321 } 322 return metadata; 323 } 324 }); 325 326 // Scan twice in order to find errors in the bean definition compatibility check. 327 scanner.scan(getClass().getPackage().getName()); 328 scanner.scan(getClass().getPackage().getName()); 329 330 context.registerAlias("classPathBeanDefinitionScannerJsr330ScopeIntegrationTests.SessionScopedTestBean", "session"); 331 context.refresh(); 332 return context; 333 } 334 335 336 public static interface IScopedTestBean { 337 getName()338 String getName(); 339 setName(String name)340 void setName(String name); 341 } 342 343 344 public static abstract class ScopedTestBean implements IScopedTestBean { 345 346 private String name = DEFAULT_NAME; 347 getName()348 public String getName() { return this.name; } 349 setName(String name)350 public void setName(String name) { this.name = name; } 351 } 352 353 354 @Named("prototype") 355 public static class PrototypeScopedTestBean extends ScopedTestBean { 356 } 357 358 359 @Named("singleton") 360 @Singleton 361 public static class SingletonScopedTestBean extends ScopedTestBean { 362 } 363 364 365 public static interface AnotherScopeTestInterface { 366 } 367 368 369 @Named("request") 370 @RequestScoped 371 public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { 372 } 373 374 375 @Named 376 @SessionScoped 377 public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface { 378 } 379 380 381 @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) 382 @Retention(RetentionPolicy.RUNTIME) 383 @javax.inject.Scope 384 public static @interface RequestScoped { 385 } 386 387 388 @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) 389 @Retention(RetentionPolicy.RUNTIME) 390 @javax.inject.Scope 391 public static @interface SessionScoped { 392 } 393 394 } 395