diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 77f63b2523a754c6afa6571561581349ec153131..59ac62ecc9f904918f03a678c8fcb5358aa8ad94 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -19,7 +19,6 @@ package org.springframework.context.annotation; import java.util.LinkedHashSet; import java.util.Set; -import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -318,17 +317,4 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo } - /** - * Inner factory class used to just introduce an AOP framework dependency - * when actually creating a scoped proxy. - */ - private static class ScopedProxyCreator { - - public static BeanDefinitionHolder createScopedProxy( - BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) { - - return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass); - } - } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 5c32e9ad2716858c8d3ae01c120dbb22badf3137..4411c9c778ca69aa1bb51bdd88219164e6b53c1e 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -26,7 +26,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -184,7 +183,7 @@ class ConfigurationClassBeanDefinitionReader { // replace the original bean definition with the target one, if necessary BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { - BeanDefinitionHolder proxyDef = ScopedProxyUtils.createScopedProxy( + BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), registry, proxyMode == ScopedProxyMode.TARGET_CLASS); beanDefToRegister = proxyDef.getBeanDefinition(); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index aaa6c2fcf8963b57140b5aab290ebd7d3154692d..3fd7b5f928b59ce6bf6800a4739dc9a01f9a5e10 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -60,14 +60,17 @@ class ConfigurationClassEnhancer { */ public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) { Assert.notNull(beanFactory, "BeanFactory must not be null"); + this.callbackInstances.add(new BeanMethodInterceptor(beanFactory)); this.callbackInstances.add(NoOp.INSTANCE); + for (Callback callback : this.callbackInstances) { this.callbackTypes.add(callback.getClass()); } + // Set up the callback filter to return the index of the BeanMethodInterceptor when // handling a @Bean-annotated method; otherwise, return index of the NoOp callback. - this.callbackFilter = new CallbackFilter() { + callbackFilter = new CallbackFilter() { public int accept(Method candidateMethod) { return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1; } @@ -85,8 +88,8 @@ class ConfigurationClassEnhancer { logger.debug("Enhancing " + configClass.getName()); } Class enhancedClass = createClass(newEnhancer(configClass)); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s", + if (logger.isInfoEnabled()) { + logger.info(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); } return enhancedClass; @@ -151,6 +154,7 @@ class ConfigurationClassEnhancer { } // determine whether this bean is a scoped-proxy + // TODO: remove hard ScopedProxyUtils dependency Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { String scopedBeanName = ScopedProxyUtils.getTargetBeanName(beanName); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 1944b5533ce3c949283376d7c7ac83141c13931d..d53ddd6a6cf48b226de62d2c82d21b89e7bb7eb5 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -17,7 +17,9 @@ package org.springframework.context.annotation; import java.io.IOException; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -121,7 +123,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */ - protected final void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { + protected void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { Set configCandidates = new LinkedHashSet(); for (String beanName : registry.getBeanDefinitionNames()) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); @@ -157,14 +159,19 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor new ConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getModel()); } - private boolean checkConfigurationClassCandidate(BeanDefinition beanDef) { + /** + * Check whether the given bean definition is a candidate for a configuration class, + * and mark it accordingly. + * @param beanDef the bean definition to check + * @return whether the candidate qualifies as (any kind of) configuration class + */ + protected boolean checkConfigurationClassCandidate(BeanDefinition beanDef) { AnnotationMetadata metadata; // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { - Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); - metadata = new StandardAnnotationMetadata(beanClass); + metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass()); } else { String className = beanDef.getBeanClassName(); @@ -199,12 +206,16 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor * Candidate status is determined by BeanDefinition attribute metadata. * @see ConfigurationClassEnhancer */ - private void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { - Set configBeanDefs = new LinkedHashSet(); + protected void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { + Map configBeanDefs = new LinkedHashMap(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); if (CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) { - configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName)); + if (!(beanDef instanceof AbstractBeanDefinition)) { + throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); + } + configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } } if (configBeanDefs.isEmpty()) { @@ -212,27 +223,23 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor return; } if (!cglibAvailable) { - Set beanNames = new LinkedHashSet(); - for (BeanDefinitionHolder holder : configBeanDefs) { - beanNames.add(holder.getBeanName()); - } throw new IllegalStateException("CGLIB is required to process @Configuration classes. " + "Either add CGLIB to the classpath or remove the following @Configuration bean definitions: " + - beanNames); + configBeanDefs.keySet()); } ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(beanFactory); - for (BeanDefinitionHolder holder : configBeanDefs) { - AbstractBeanDefinition beanDef = (AbstractBeanDefinition) holder.getBeanDefinition(); + for (Map.Entry entry : configBeanDefs.entrySet()) { + AbstractBeanDefinition beanDef = entry.getValue(); try { Class configClass = beanDef.resolveBeanClass(this.beanClassLoader); Class enhancedClass = enhancer.enhance(configClass); if (logger.isDebugEnabled()) { logger.debug(String.format("Replacing bean definition '%s' existing class name '%s' " + - "with enhanced class name '%s'", holder.getBeanName(), configClass.getName(), enhancedClass.getName())); + "with enhanced class name '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } beanDef.setBeanClass(enhancedClass); } - catch (ClassNotFoundException ex) { + catch (Throwable ex) { throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyCreator.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..bfd6a67e14a9c581869d2881b4cf311544aa8870 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyCreator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2009 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.context.annotation; + +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +/** + * Delegate factory class used to just introduce an AOP framework dependency + * when actually creating a scoped proxy. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.aop.scope.ScopedProxyUtils#createScopedProxy + */ +class ScopedProxyCreator { + + public static BeanDefinitionHolder createScopedProxy( + BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) { + + return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass); + } + +} diff --git a/org.springframework.context/src/test/java/example/scannable/CustomStereotype.java b/org.springframework.context/src/test/java/example/scannable/CustomStereotype.java index 656ad49bd8f7bb4f5f913a2ef34d5dd47e1f1b16..b373bbabd6333b89538d31e40b99d2d05919d88a 100644 --- a/org.springframework.context/src/test/java/example/scannable/CustomStereotype.java +++ b/org.springframework.context/src/test/java/example/scannable/CustomStereotype.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -21,14 +21,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; /** * @author Juergen Hoeller */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Component +@Service +@Scope("prototype") public @interface CustomStereotype { String value() default "thoreau"; diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index c2f159251f3f184882a38e9da13fe382453bba3a..4d214eb2c07ead4b6ff2d51d1dcd16a1b5beaa3f 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -69,6 +69,7 @@ public class ClassPathBeanDefinitionScannerTests { FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class); assertTrue(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")); assertEquals("bar", service.foo(1)); + assertTrue(context.isPrototype("thoreau")); } @Test diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index 642a977c187ff7bf5d301fae9462351c2d6e2a41..3ea25d3725d421052061e1f57223b73b2e2fe0a0 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -16,13 +16,22 @@ package org.springframework.context.annotation; -import static org.junit.Assert.*; - import java.util.Iterator; import java.util.Set; import java.util.regex.Pattern; +import example.scannable.FooDao; +import example.scannable.FooService; +import example.scannable.FooServiceImpl; +import example.scannable.MessageBean; +import example.scannable.NamedComponent; +import example.scannable.NamedStubDao; +import example.scannable.ServiceInvocationCounter; +import example.scannable.StubFooDao; +import org.aspectj.lang.annotation.Aspect; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; @@ -31,16 +40,6 @@ import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; -import org.springframework.util.ClassUtils; - -import example.scannable.FooDao; -import example.scannable.FooService; -import example.scannable.FooServiceImpl; -import example.scannable.MessageBean; -import example.scannable.NamedComponent; -import example.scannable.NamedStubDao; -import example.scannable.ServiceInvocationCounter; -import example.scannable.StubFooDao; /** * @author Mark Fisher @@ -95,7 +94,7 @@ public class ClassPathScanningCandidateComponentProviderTests { provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertEquals(3, candidates.size()); + assertEquals(2, candidates.size()); assertTrue(containsBeanClass(candidates, NamedComponent.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); assertFalse(containsBeanClass(candidates, FooServiceImpl.class)); @@ -107,8 +106,7 @@ public class ClassPathScanningCandidateComponentProviderTests { @Test public void testWithAspectAnnotationOnly() throws Exception { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); - provider.addIncludeFilter(new AnnotationTypeFilter( - ClassUtils.forName("org.aspectj.lang.annotation.Aspect"))); + provider.addIncludeFilter(new AnnotationTypeFilter(Aspect.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); assertEquals(1, candidates.size()); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 608c2e881049cf67385557627e638e5b02ac1b86..ecde5ce6f55a09c18435dfa575666c661f700ea8 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -28,11 +28,12 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.StandardScopes; +import org.springframework.context.support.GenericApplicationContext; /** * Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and error @@ -64,14 +65,20 @@ public class ConfigurationClassProcessingTests { @Test public void customBeanNameIsRespected() { - BeanFactory factory = initBeanFactory(ConfigWithBeanWithCustomName.class); - assertSame(factory.getBean("customName"), ConfigWithBeanWithCustomName.testBean); + GenericApplicationContext ac = new GenericApplicationContext(); + AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); + ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithCustomName.class)); + ac.refresh(); + assertSame(ac.getBean("customName"), ConfigWithBeanWithCustomName.testBean); // method name should not be registered try { - factory.getBean("methodName"); + ac.getBean("methodName"); fail("bean should not have been registered with 'methodName'"); - } catch (NoSuchBeanDefinitionException ex) { /* expected */ } + } + catch (NoSuchBeanDefinitionException ex) { + // expected + } } @Test @@ -165,7 +172,7 @@ public class ConfigurationClassProcessingTests { return bar; } - @Bean @Scope(StandardScopes.PROTOTYPE) + @Bean @Scope("prototype") public TestBean baz() { return new TestBean("bar"); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java index 0fb107db0b528123476901abd8bc039a79593c3c..66ef989471916709b78a93c744a301f9bee099bb 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java @@ -29,7 +29,6 @@ import test.beans.TestBean; import org.springframework.aop.scope.ScopedObject; import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.Bean; @@ -37,7 +36,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.context.annotation.StandardScopes; import org.springframework.context.support.GenericApplicationContext; /** @@ -52,7 +50,9 @@ public class ScopingTests { public static String flag = "1"; private static final String SCOPE = "my scope"; + private CustomScope customScope; + private GenericApplicationContext ctx; @Before @@ -117,10 +117,8 @@ public class ScopingTests { assertFalse(bean instanceof ScopedObject); } - @Test public void testScopedProxyConfiguration() throws Exception { - TestBean singleton = (TestBean) ctx.getBean("singletonWithScopedInterfaceDep"); ITestBean spouse = singleton.getSpouse(); assertTrue("scoped bean is not wrapped by the scoped-proxy", spouse instanceof ScopedObject); @@ -224,7 +222,7 @@ public class ScopingTests { static class ScopeTestConfiguration { @Bean - @Scope(value=StandardScopes.SESSION, proxyMode=ScopedProxyMode.INTERFACES) + @Scope(value="session", proxyMode=ScopedProxyMode.INTERFACES) public Foo foo() { return new Foo(); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index faf76154c23d63ca2e97d1234ce9f8a8de463761..484ce377e7835a6ba8b727371c8ecd7873a7f4d1 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -137,6 +137,14 @@ public abstract class AnnotationUtils { return annotation; } } + if (!Annotation.class.isAssignableFrom(clazz)) { + for (Annotation ann : clazz.getAnnotations()) { + annotation = findAnnotation(ann.annotationType(), annotationType); + if (annotation != null) { + return annotation; + } + } + } Class superClass = clazz.getSuperclass(); if (superClass == null || superClass.equals(Object.class)) { return null; diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index 1d1f2b64bcccf71d026b4d2d8c470e591de8fd30..ec7c64b89cf3a07523781be37243c01d01535e10 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -37,6 +37,14 @@ public interface AnnotationMetadata extends ClassMetadata { */ Set getAnnotationTypes(); + /** + * Return the names of all meta-annotation types defined on the + * given annotation type of the underlying class. + * @param annotationType the meta-annotation type to look for + * @return the meta-annotation type names + */ + Set getMetaAnnotationTypes(String annotationType); + /** * Determine whether the underlying class has an annotation of the given * type defined. @@ -45,6 +53,14 @@ public interface AnnotationMetadata extends ClassMetadata { */ boolean hasAnnotation(String annotationType); + /** + * Determine whether the underlying class has an annotation that + * is itself annotated with the meta-annotation of the given type. + * @param metaAnnotationType the meta-annotation type to look for + * @return whether a matching meta-annotation is defined + */ + boolean hasMetaAnnotation(String metaAnnotationType); + /** * Retrieve the attributes of the annotation of the given type, * if any (i.e. if defined on the underlying class). @@ -55,22 +71,6 @@ public interface AnnotationMetadata extends ClassMetadata { */ Map getAnnotationAttributes(String annotationType); - /** - * Return the names of all meta-annotation types defined on the - * given annotation type of the underlying class. - * @param annotationType the meta-annotation type to look for - * @return the meta-annotation type names - */ - Set getMetaAnnotationTypes(String annotationType); - - /** - * Determine whether the underlying class has an annotation that - * is itself annotated with the meta-annotation of the given type. - * @param metaAnnotationType the meta-annotation type to look for - * @return whether a matching meta-annotation is defined - */ - boolean hasMetaAnnotation(String metaAnnotationType); - /** * Retrieve the method metadata for all methods that are annotated * with at least one annotation type. diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index ac672ad3313974fc7c162ed769396b7771ea6355..77302c6095fb5fd9612f3e40fc24301bb5ef3629 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -18,7 +18,6 @@ package org.springframework.core.type; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -45,7 +44,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public Set getAnnotationTypes() { - Set types = new HashSet(); + Set types = new LinkedHashSet(); Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { types.add(ann.annotationType().getName()); @@ -53,52 +52,65 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return types; } - public boolean hasAnnotation(String annotationType) { + public Set getMetaAnnotationTypes(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { - return true; + Set types = new LinkedHashSet(); + Annotation[] metaAnns = ann.annotationType().getAnnotations(); + for (Annotation metaAnn : metaAnns) { + types.add(metaAnn.annotationType().getName()); + for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { + types.add(metaMetaAnn.annotationType().getName()); + } + } + return types; } } - return false; + return null; } - public Map getAnnotationAttributes(String annotationType) { + public boolean hasAnnotation(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes(ann, true); + return true; } } - return null; + return false; } - public Set getMetaAnnotationTypes(String annotationType) { + public boolean hasMetaAnnotation(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - Set types = new HashSet(); - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation meta : metaAnns) { - types.add(meta.annotationType().getName()); + Annotation[] metaAnns = ann.annotationType().getAnnotations(); + for (Annotation metaAnn : metaAnns) { + if (metaAnn.annotationType().getName().equals(annotationType)) { + return true; + } + for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { + if (metaMetaAnn.annotationType().getName().equals(annotationType)) { + return true; + } } - return types; } } - return null; + return false; } - public boolean hasMetaAnnotation(String annotationType) { + public Map getAnnotationAttributes(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation meta : metaAnns) { - if (meta.annotationType().getName().equals(annotationType)) { - return true; + if (ann.annotationType().getName().equals(annotationType)) { + return AnnotationUtils.getAnnotationAttributes(ann, true); + } + for (Annotation metaAnn : ann.annotationType().getAnnotations()) { + if (metaAnn.annotationType().getName().equals(annotationType)) { + return AnnotationUtils.getAnnotationAttributes(metaAnn, true); } } } - return false; + return null; } public Set getAnnotatedMethods() { diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 2105b2d4dbc6e1c507ad2ae1b0abce10ef8b6b76..a21d022873c351bdfda4c3eb67e4d24c0bb6fa78 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -20,8 +20,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -30,6 +30,7 @@ import org.springframework.asm.Type; import org.springframework.asm.commons.EmptyVisitor; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.core.annotation.AnnotationUtils; /** * ASM visitor which looks for the annotations defined on a class or method. @@ -47,7 +48,7 @@ final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { private final ClassLoader classLoader; - private final Map attributes = new LinkedHashMap(); + private final Map attributes = new LinkedHashMap(); public AnnotationAttributesReadingVisitor( @@ -120,6 +121,7 @@ final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { } public void visitEnd() { + this.annotationMap.put(this.annotationType, this.attributes); try { Class annotationClass = this.classLoader.loadClass(this.annotationType); // Check declared default values of attributes in the annotation type. @@ -133,10 +135,16 @@ final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { } // Register annotations that the annotation type is annotated with. if (this.metaAnnotationMap != null) { - Annotation[] metaAnnotations = annotationClass.getAnnotations(); - Set metaAnnotationTypeNames = new HashSet(); - for (Annotation metaAnnotation : metaAnnotations) { + Set metaAnnotationTypeNames = new LinkedHashSet(); + for (Annotation metaAnnotation : annotationClass.getAnnotations()) { metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); + if (!this.annotationMap.containsKey(metaAnnotation.annotationType().getName())) { + this.annotationMap.put(metaAnnotation.annotationType().getName(), + AnnotationUtils.getAnnotationAttributes(metaAnnotation, true)); + } + for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { + metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); + } } this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames); } @@ -144,7 +152,6 @@ final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { catch (ClassNotFoundException ex) { // Class not found - can't determine meta-annotations. } - this.annotationMap.put(this.annotationType, this.attributes); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 1e9967e8e5b8b171add40eedfede8a5606cae03c..cd5109bb19eeae7c579c8dd2b205660f19df64a5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -39,10 +39,12 @@ import org.springframework.core.type.MethodMetadata; */ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { - private final Map> annotationMap = new LinkedHashMap>(); + private final Set annotationSet = new LinkedHashSet(); private final Map> metaAnnotationMap = new LinkedHashMap>(); + private final Map> attributeMap = new LinkedHashMap>(); + private final Set methodMetadataSet = new LinkedHashSet(); private final ClassLoader classLoader; @@ -63,26 +65,23 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { String className = Type.getType(desc).getClassName(); - return new AnnotationAttributesReadingVisitor(className, this.annotationMap, this.metaAnnotationMap, this.classLoader); + this.annotationSet.add(className); + return new AnnotationAttributesReadingVisitor(className, this.attributeMap, this.metaAnnotationMap, this.classLoader); } public Set getAnnotationTypes() { - return this.annotationMap.keySet(); + return this.annotationSet; } - public boolean hasAnnotation(String annotationType) { - return this.annotationMap.containsKey(annotationType); + public Set getMetaAnnotationTypes(String annotationType) { + return this.metaAnnotationMap.get(annotationType); } - public Map getAnnotationAttributes(String annotationType) { - return this.annotationMap.get(annotationType); + public boolean hasAnnotation(String annotationType) { + return this.annotationSet.contains(annotationType); } - public Set getMetaAnnotationTypes(String annotationType) { - return this.metaAnnotationMap.get(annotationType); - } - public boolean hasMetaAnnotation(String metaAnnotationType) { Collection> allMetaTypes = this.metaAnnotationMap.values(); for (Set metaTypes : allMetaTypes) { @@ -93,6 +92,10 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor return false; } + public Map getAnnotationAttributes(String annotationType) { + return this.attributeMap.get(annotationType); + } + public Set getAnnotatedMethods() { Set annotatedMethods = new LinkedHashSet(); for (MethodMetadata method : this.methodMetadataSet) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java index 777ead9157a80ab79bbfa6f493124ecb6cadb68d..5f256ad435fe43b6134ca38efa11b251aa3c24da 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -77,8 +77,8 @@ public class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter } else if (superClassName.startsWith("java.")) { try { - Class clazz = getClass().getClassLoader().loadClass(superClassName); - return Boolean.valueOf(clazz.getAnnotation(this.annotationType) != null); + Class clazz = getClass().getClassLoader().loadClass(superClassName); + return (clazz.getAnnotation(this.annotationType) != null); } catch (ClassNotFoundException ex) { // Class not found - can't determine a match that way. diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index cc98efa11ec7162047d1ef75ec408d84d98c9385..15262aefba66051fed153494291e248350f591b6 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -18,6 +18,7 @@ package org.springframework.transaction.annotation; import java.io.Serializable; import java.lang.reflect.AnnotatedElement; +import java.lang.annotation.Annotation; import java.util.ArrayList; import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; @@ -35,6 +36,14 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { Transactional ann = ae.getAnnotation(Transactional.class); + if (ann == null) { + for (Annotation metaAnn : ae.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(Transactional.class); + if (ann != null) { + break; + } + } + } if (ann != null) { return parseTransactionAnnotation(ann); } @@ -51,23 +60,23 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP rbta.setReadOnly(ann.readOnly()); ArrayList rollBackRules = new ArrayList(); Class[] rbf = ann.rollbackFor(); - for (int i = 0; i < rbf.length; ++i) { - RollbackRuleAttribute rule = new RollbackRuleAttribute(rbf[i]); + for (Class rbRule : rbf) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); rollBackRules.add(rule); } String[] rbfc = ann.rollbackForClassName(); - for (int i = 0; i < rbfc.length; ++i) { - RollbackRuleAttribute rule = new RollbackRuleAttribute(rbfc[i]); + for (String rbRule : rbfc) { + RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); rollBackRules.add(rule); } Class[] nrbf = ann.noRollbackFor(); - for (int i = 0; i < nrbf.length; ++i) { - NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbf[i]); + for (Class rbRule : nrbf) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); rollBackRules.add(rule); } String[] nrbfc = ann.noRollbackForClassName(); - for (int i = 0; i < nrbfc.length; ++i) { - NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(nrbfc[i]); + for (String rbRule : nrbfc) { + NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); rollBackRules.add(rule); } rbta.getRollbackRules().addAll(rollBackRules); diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java index 64fb2e77a28725408dbc5d3b842d42c5add1c15c..322a659bb472b559b34ab4acfa03c22ebd3611dc 100644 --- a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java @@ -18,11 +18,15 @@ package org.springframework.transaction.annotation; import java.io.IOException; import java.io.Serializable; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Method; - import javax.ejb.TransactionAttributeType; -import junit.framework.TestCase; +import static org.junit.Assert.*; +import org.junit.Test; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyFactory; @@ -38,8 +42,9 @@ import org.springframework.util.SerializationTestUtils; * @author Colin Sampaleanu * @author Juergen Hoeller */ -public class AnnotationTransactionAttributeSourceTests extends TestCase { +public class AnnotationTransactionAttributeSourceTests { + @Test public void testSerializable() throws Exception { TestBean1 tb = new TestBean1(); CallCountingTransactionManager ptm = new CallCountingTransactionManager(); @@ -63,6 +68,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { assertEquals(2, serializedPtm.commits); } + @Test public void testNullOrEmpty() throws Exception { Method method = Empty.class.getMethod("getAge", (Class[]) null); @@ -77,6 +83,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { * Test the important case where the invocation is on a proxied interface method * but the attribute is defined on the target class. */ + @Test public void testTransactionAttributeDeclaredOnClassMethod() throws Exception { Method classMethod = ITestBean.class.getMethod("getAge", (Class[]) null); @@ -91,6 +98,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { /** * Test case where attribute is on the interface method. */ + @Test public void testTransactionAttributeDeclaredOnInterfaceMethodOnly() throws Exception { Method interfaceMethod = ITestBean2.class.getMethod("getAge", (Class[]) null); @@ -104,6 +112,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { /** * Test that when an attribute exists on both class and interface, class takes precedence. */ + @Test public void testTransactionAttributeOnTargetClassMethodOverridesAttributeOnInterfaceMethod() throws Exception { Method interfaceMethod = ITestBean3.class.getMethod("getAge", (Class[]) null); Method interfaceMethod2 = ITestBean3.class.getMethod("getName", (Class[]) null); @@ -124,6 +133,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { assertEquals(TransactionAttribute.PROPAGATION_REQUIRED, actual2.getPropagationBehavior()); } + @Test public void testRollbackRulesAreApplied() throws Exception { Method method = TestBean3.class.getMethod("getAge", (Class[]) null); @@ -153,6 +163,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { * Test that transaction attribute is inherited from class * if not specified on method. */ + @Test public void testDefaultsToClassTransactionAttribute() throws Exception { Method method = TestBean4.class.getMethod("getAge", (Class[]) null); @@ -165,6 +176,33 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { assertEquals(rbta.getRollbackRules(), ((RuleBasedTransactionAttribute) actual).getRollbackRules()); } + @Test + public void testCustomClassAttributeDetected() throws Exception { + Method method = TestBean5.class.getMethod("getAge", (Class[]) null); + + AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource(); + TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean5.class); + + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + rbta.getRollbackRules().add(new RollbackRuleAttribute(Exception.class)); + rbta.getRollbackRules().add(new NoRollbackRuleAttribute(IOException.class)); + assertEquals(rbta.getRollbackRules(), ((RuleBasedTransactionAttribute) actual).getRollbackRules()); + } + + @Test + public void testCustomMethodAttributeDetected() throws Exception { + Method method = TestBean6.class.getMethod("getAge", (Class[]) null); + + AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource(); + TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean5.class); + + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + rbta.getRollbackRules().add(new RollbackRuleAttribute(Exception.class)); + rbta.getRollbackRules().add(new NoRollbackRuleAttribute(IOException.class)); + assertEquals(rbta.getRollbackRules(), ((RuleBasedTransactionAttribute) actual).getRollbackRules()); + } + + @Test public void testTransactionAttributeDeclaredOnClassMethodWithEjb3() throws Exception { Method getAgeMethod = ITestBean.class.getMethod("getAge", (Class[]) null); Method getNameMethod = ITestBean.class.getMethod("getName", (Class[]) null); @@ -176,6 +214,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { assertEquals(TransactionAttribute.PROPAGATION_SUPPORTS, getNameAttr.getPropagationBehavior()); } + @Test public void testTransactionAttributeDeclaredOnClassWithEjb3() throws Exception { Method getAgeMethod = ITestBean.class.getMethod("getAge", (Class[]) null); Method getNameMethod = ITestBean.class.getMethod("getName", (Class[]) null); @@ -187,6 +226,7 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { assertEquals(TransactionAttribute.PROPAGATION_SUPPORTS, getNameAttr.getPropagationBehavior()); } + @Test public void testTransactionAttributeDeclaredOnInterfaceWithEjb3() throws Exception { Method getAgeMethod = ITestEjb.class.getMethod("getAge", (Class[]) null); Method getNameMethod = ITestEjb.class.getMethod("getName", (Class[]) null); @@ -401,6 +441,31 @@ public class AnnotationTransactionAttributeSourceTests extends TestCase { } + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Transactional(rollbackFor=Exception.class, noRollbackFor={IOException.class}) + public @interface Tx { + } + + + @Tx + public static class TestBean5 { + + public int getAge() { + return 10; + } + } + + + public static class TestBean6 { + + @Tx + public int getAge() { + return 10; + } + } + + public static interface Foo { void doSomething(T theArgument);