Browse Source

Allow meta-annotations to override attributes from their parent

Issue: SPR-10181
pull/337/merge
Juergen Hoeller 13 years ago
parent
commit
6d3649858e
  1. 53
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
  2. 31
      spring-context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java
  3. 45
      spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java
  4. 202
      spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
  5. 66
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
  6. 165
      spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java
  7. 16
      spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
  8. 13
      spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
  9. 15
      spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java
  10. 30
      spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java
  11. 59
      spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java

53
spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@ -55,7 +55,8 @@ import org.springframework.core.GenericTypeResolver; @@ -55,7 +55,8 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@ -235,7 +236,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -235,7 +236,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Constructor<?> requiredConstructor = null;
Constructor<?> defaultConstructor = null;
for (Constructor<?> candidate : rawCandidates) {
Annotation annotation = findAutowiredAnnotation(candidate);
AnnotationAttributes annotation = findAutowiredAnnotation(candidate);
if (annotation != null) {
if (requiredConstructor != null) {
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
@ -333,7 +334,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -333,7 +334,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
do {
LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<InjectionMetadata.InjectedElement>();
for (Field field : targetClass.getDeclaredFields()) {
Annotation annotation = findAutowiredAnnotation(field);
AnnotationAttributes annotation = findAutowiredAnnotation(field);
if (annotation != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
@ -347,7 +348,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -347,7 +348,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
for (Method method : targetClass.getDeclaredMethods()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
Annotation annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
AnnotationAttributes annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method);
if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
@ -374,9 +375,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -374,9 +375,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return new InjectionMetadata(clazz, elements);
}
private Annotation findAutowiredAnnotation(AccessibleObject ao) {
private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
Annotation annotation = AnnotationUtils.getAnnotation(ao, type);
AnnotationAttributes annotation = AnnotatedElementUtils.getAnnotationAttributes(ao, type.getName());
if (annotation != null) {
return annotation;
}
@ -384,6 +385,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -384,6 +385,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return null;
}
/**
* Determine if the annotated field or method requires its dependency.
* <p>A 'required' dependency means that autowiring should fail when no beans
* are found. Otherwise, the autowiring process will simply bypass the field
* or method when no beans are found.
* @param annotation the Autowired annotation
* @return whether the annotation indicates that a dependency is required
*/
protected boolean determineRequiredStatus(AnnotationAttributes annotation) {
return (!annotation.containsKey(this.requiredParameterName) ||
this.requiredParameterValue == annotation.getBoolean(this.requiredParameterName));
}
/**
* Obtain all beans of the given type as autowire candidates.
* @param type the type of the bean
@ -398,31 +412,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @@ -398,31 +412,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
}
/**
* Determine if the annotated field or method requires its dependency.
* <p>A 'required' dependency means that autowiring should fail when no beans
* are found. Otherwise, the autowiring process will simply bypass the field
* or method when no beans are found.
* @param annotation the Autowired annotation
* @return whether the annotation indicates that a dependency is required
*/
protected boolean determineRequiredStatus(Annotation annotation) {
try {
Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName);
if (method == null) {
// annotations like @Inject and @Value don't have a method (attribute) named "required"
// -> default to required status
return true;
}
return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation));
}
catch (Exception ex) {
// an exception was thrown during reflective invocation of the required attribute
// -> default to required status
return true;
}
}
/**
* Register the specified bean as dependent on the autowired beans.
*/

31
spring-context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java

@ -16,12 +16,11 @@ @@ -16,12 +16,11 @@
package org.springframework.context.annotation;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
@ -29,6 +28,8 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; @@ -29,6 +28,8 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import static org.junit.Assert.*;
/**
* @author Rick Evans
* @author Chris Beams
@ -83,6 +84,15 @@ public final class AnnotationScopeMetadataResolverTests { @@ -83,6 +84,15 @@ public final class AnnotationScopeMetadataResolverTests {
assertEquals(ScopedProxyMode.NO, scopeMetadata.getScopedProxyMode());
}
@Test
public void testCustomRequestScopeWithAttribute() {
AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(AnnotatedWithCustomRequestScopeWithAttribute.class);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(bd);
assertNotNull("resolveScopeMetadata(..) must *never* return null.", scopeMetadata);
assertEquals("request", scopeMetadata.getScopeName());
assertEquals(ScopedProxyMode.TARGET_CLASS, scopeMetadata.getScopedProxyMode());
}
@Test(expected=IllegalArgumentException.class)
public void testCtorWithNullScopedProxyMode() {
new AnnotationScopeMetadataResolver(null);
@ -98,17 +108,21 @@ public final class AnnotationScopeMetadataResolverTests { @@ -98,17 +108,21 @@ public final class AnnotationScopeMetadataResolverTests {
private static final class AnnotatedWithSingletonScope {
}
@Scope("prototype")
private static final class AnnotatedWithPrototypeScope {
}
@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
private static final class AnnotatedWithScopedProxy {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("request")
public @interface CustomRequestScope {
}
@CustomRequestScope
private static final class AnnotatedWithCustomRequestScope {
}
@ -117,8 +131,13 @@ public final class AnnotationScopeMetadataResolverTests { @@ -117,8 +131,13 @@ public final class AnnotationScopeMetadataResolverTests {
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("request")
public @interface CustomRequestScope {
public @interface CustomRequestScopeWithAttribute {
ScopedProxyMode proxyMode();
}
@CustomRequestScopeWithAttribute(proxyMode = ScopedProxyMode.TARGET_CLASS)
private static final class AnnotatedWithCustomRequestScopeWithAttribute {
}
}

45
spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java

@ -20,6 +20,8 @@ import java.lang.annotation.Retention; @@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import org.springframework.tests.sample.beans.NestedTestBean;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.beans.factory.annotation.Autowired;
@ -60,9 +62,19 @@ public class BeanMethodQualificationTests { @@ -60,9 +62,19 @@ public class BeanMethodQualificationTests {
assertThat(pojo.testBean.getName(), equalTo("interesting"));
}
@Test
public void testCustomWithAttributeOverride() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class);
assertFalse(ctx.getBeanFactory().containsSingleton("testBeanX"));
CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.testBean.getName(), equalTo("interesting"));
}
@Configuration
static class StandardConfig {
@Bean @Lazy @Qualifier("interesting")
public TestBean testBean1() {
return new TestBean("interesting");
@ -76,11 +88,13 @@ public class BeanMethodQualificationTests { @@ -76,11 +88,13 @@ public class BeanMethodQualificationTests {
@Component @Lazy
static class StandardPojo {
@Autowired @Qualifier("interesting") TestBean testBean;
}
@Configuration
static class CustomConfig {
@InterestingBean
public TestBean testBean1() {
return new TestBean("interesting");
@ -92,9 +106,26 @@ public class BeanMethodQualificationTests { @@ -92,9 +106,26 @@ public class BeanMethodQualificationTests {
}
}
@Configuration
static class CustomConfigWithAttributeOverride {
@InterestingBeanWithName(name="testBeanX")
public TestBean testBean1() {
return new TestBean("interesting");
}
@Bean @Qualifier("boring")
public TestBean testBean2() {
return new TestBean("boring");
}
}
@InterestingPojo
static class CustomPojo {
@InterestingNeed TestBean testBean;
@InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean;
}
@Bean @Lazy @Qualifier("interesting")
@ -102,11 +133,25 @@ public class BeanMethodQualificationTests { @@ -102,11 +133,25 @@ public class BeanMethodQualificationTests {
public @interface InterestingBean {
}
@Bean @Lazy @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME)
public @interface InterestingBeanWithName {
String name();
}
@Autowired @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME)
public @interface InterestingNeed {
}
@Autowired @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME)
public @interface InterestingNeedWithRequiredOverride {
boolean required();
}
@Component @Lazy
@Retention(RetentionPolicy.RUNTIME)
public @interface InterestingPojo {

202
spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

@ -0,0 +1,202 @@ @@ -0,0 +1,202 @@
/*
* Copyright 2002-2013 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.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Utility class used to collect all annotation values including those declared on meta-annotations.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
*/
public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
final Set<String> types = new LinkedHashSet<String>();
process(element, annotationType, new Processor<Object>() {
@Override
public Object process(Annotation annotation, int depth) {
if (depth > 0) {
types.add(annotation.annotationType().getName());
}
return null;
}
@Override
public void postProcess(Annotation annotation, Object result) {
}
});
return (types.isEmpty() ? null : types);
}
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int depth) {
if (depth > 0) {
return true;
}
return null;
}
@Override
public void postProcess(Annotation annotation, Boolean result) {
}
}));
}
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int depth) {
return true;
}
@Override
public void postProcess(Annotation annotation, Boolean result) {
}
}));
}
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
return getAnnotationAttributes(element, annotationType, false, false);
}
public static AnnotationAttributes getAnnotationAttributes(
AnnotatedElement element, String annotationType,
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
return process(element, annotationType, new Processor<AnnotationAttributes>() {
@Override
public AnnotationAttributes process(Annotation annotation, int depth) {
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
}
@Override
public void postProcess(Annotation annotation, AnnotationAttributes result) {
for (String key : result.keySet()) {
if (!"value".equals(key)) {
Object value = AnnotationUtils.getValue(annotation, key);
if (value != null) {
result.put(key, value);
}
}
}
}
});
}
public static MultiValueMap<String, Object> getAllAnnotationAttributes(
AnnotatedElement element, final String annotationType,
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
process(element, annotationType, new Processor<Void>() {
@Override
public Void process(Annotation annotation, int depth) {
if (annotation.annotationType().getName().equals(annotationType)) {
for (Map.Entry<String, Object> entry :
AnnotationUtils.getAnnotationAttributes(
annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
attributes.add(entry.getKey(), entry.getValue());
}
}
return null;
}
@Override
public void postProcess(Annotation annotation, Void result) {
for (String key : attributes.keySet()) {
if (!"value".equals(key)) {
Object value = AnnotationUtils.getValue(annotation, key);
if (value != null) {
attributes.add(key, value);
}
}
}
}
});
return (attributes.isEmpty() ? null : attributes);
}
/**
* Process all annotations of the specified annotation type and recursively all
* meta-annotations on the specified type.
* @param element the annotated element
* @param annotationType the annotation type to find. Only items of the specified
* type or meta-annotations of the specified type will be processed.
* @param processor the processor
* @return the result of the processor
*/
private static <T> T process(AnnotatedElement element, String annotationType, Processor<T> processor) {
return doProcess(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
}
private static <T> T doProcess(AnnotatedElement element, String annotationType,
Processor<T> processor, Set<AnnotatedElement> visited, int depth) {
if (visited.add(element)) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
T result = processor.process(annotation, depth);
if (result != null) {
return result;
}
result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
for (Annotation annotation : element.getAnnotations()) {
T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
return null;
}
/**
* Callback interface used to process an annotation.
* @param <T> the result type
*/
private static interface Processor<T> {
/**
* Called to process the annotation.
* @param annotation the annotation to process
* @param depth the depth of the annotation relative to the initial match.
* For example, a matched annotation will have a depth of 0, a meta-annotation
* 1 and a meta-meta-annotation 2
* @return the result of the processing or {@code null} to continue
*/
T process(Annotation annotation, int depth);
void postProcess(Annotation annotation, T result);
}
}

66
spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java

@ -16,8 +16,7 @@ @@ -16,8 +16,7 @@
package org.springframework.core.annotation;
import static java.lang.String.format;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@ -27,10 +26,9 @@ import org.springframework.util.StringUtils; @@ -27,10 +26,9 @@ import org.springframework.util.StringUtils;
/**
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
* as read by Spring's reflection- or ASM-based {@link
* org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
* Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
* as convenience methods for looking up annotation attributes in a type-safe fashion.
* as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata}
* implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code
* as well as convenience methods for looking up annotation attributes in a type-safe fashion.
*
* @author Chris Beams
* @since 3.1.1
@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
super(map);
}
/**
* Return an {@link AnnotationAttributes} instance based on the given map; if the map
* is already an {@code AnnotationAttributes} instance, it is casted and returned
* immediately without creating any new instance; otherwise create a new instance by
* wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
* @param map original source of annotation attribute key/value pairs
*/
public static AnnotationAttributes fromMap(Map<String, Object> map) {
if (map == null) {
return null;
}
if (map instanceof AnnotationAttributes) {
return (AnnotationAttributes) map;
}
return new AnnotationAttributes(map);
}
public String getString(String attributeName) {
return doGet(attributeName, String.class);
@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@SuppressWarnings("unchecked")
public <N extends Number> N getNumber(String attributeName) {
return (N) doGet(attributeName, Integer.class);
return (N) doGet(attributeName, Number.class);
}
@SuppressWarnings("unchecked")
@ -124,11 +104,20 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @@ -124,11 +104,20 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@SuppressWarnings("unchecked")
private <T> T doGet(String attributeName, Class<T> expectedType) {
Assert.hasText(attributeName, "attributeName must not be null or empty");
Object value = this.get(attributeName);
Assert.notNull(value, format("Attribute '%s' not found", attributeName));
Assert.isAssignable(expectedType, value.getClass(),
format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
Object value = get(attributeName);
Assert.notNull(value, String.format("Attribute '%s' not found", attributeName));
if (!expectedType.isInstance(value)) {
if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1);
Array.set(arrayValue, 0, value);
value = arrayValue;
}
else {
throw new IllegalArgumentException(
String.format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
}
}
return (T) value;
}
@ -156,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @@ -156,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
}
return String.valueOf(value);
}
/**
* Return an {@link AnnotationAttributes} instance based on the given map; if the map
* is already an {@code AnnotationAttributes} instance, it is casted and returned
* immediately without creating any new instance; otherwise create a new instance by
* wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
* @param map original source of annotation attribute key/value pairs
*/
public static AnnotationAttributes fromMap(Map<String, Object> map) {
if (map == null) {
return null;
}
if (map instanceof AnnotationAttributes) {
return (AnnotationAttributes) map;
}
return new AnnotationAttributes(map);
}
}

165
spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java

@ -1,165 +0,0 @@ @@ -1,165 +0,0 @@
/*
* Copyright 2002-2013 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.core.type;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Internal utility class used to collect all annotation values including those declared
* on meta-annotations.
*
* @author Phillip Webb
* @since 4.0
*/
class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
String annotationType) {
final Set<String> types = new LinkedHashSet<String>();
process(element, annotationType, new Processor<Object>() {
@Override
public Object process(Annotation annotation, int depth) {
if (depth > 0) {
types.add(annotation.annotationType().getName());
}
return null;
}
});
return (types.size() == 0 ? null : types);
}
public static boolean hasMetaAnnotationTypes(AnnotatedElement element,
String annotationType) {
return Boolean.TRUE.equals(
process(element, annotationType, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int depth) {
if (depth > 0) {
return true;
}
return null;
}
}));
}
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(
process(element, annotationType, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int depth) {
return true;
}
}));
}
public static Map<String, Object> getAnnotationAttributes(AnnotatedElement element,
String annotationType, final boolean classValuesAsString,
final boolean nestedAnnotationsAsMap) {
return process(element, annotationType, new Processor<Map<String, Object>>() {
@Override
public Map<String, Object> process(Annotation annotation, int depth) {
return AnnotationUtils.getAnnotationAttributes(annotation,
classValuesAsString, nestedAnnotationsAsMap);
}
});
}
public static MultiValueMap<String, Object> getAllAnnotationAttributes(
AnnotatedElement element, final String annotationType,
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
process(element, annotationType, new Processor<Object>() {
@Override
public Object process(Annotation annotation, int depth) {
if (annotation.annotationType().getName().equals(annotationType)) {
for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes(
annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
attributes.add(entry.getKey(), entry.getValue());
}
}
return null;
}
});
return (attributes.size() == 0 ? null : attributes);
}
/**
* Process all annotations of the specified annotation type and recursively all
* meta-annotations on the specified type.
* @param element the annotated element
* @param annotationType the annotation type to find. Only items of the specified type
* or meta-annotations of the specified type will be processed
* @param processor the processor
* @return the result of the processor
*/
private static <T> T process(AnnotatedElement element, String annotationType,
Processor<T> processor) {
return recursivelyProcess(element, annotationType, processor,
new HashSet<AnnotatedElement>(), 0);
}
private static <T> T recursivelyProcess(AnnotatedElement element,
String annotationType, Processor<T> processor, Set<AnnotatedElement> visited,
int depth) {
T result = null;
if (visited.add(element)) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
result = result != null ? result :
processor.process(annotation, depth);
result = result != null ? result :
recursivelyProcess(annotation.annotationType(), annotationType,
processor, visited, depth + 1);
}
}
for (Annotation annotation : element.getAnnotations()) {
result = result != null ? result :
recursivelyProcess(annotation.annotationType(), annotationType,
processor, visited, depth);
}
}
return result;
}
/**
* Callback interface used to process an annotation.
* @param <T> the result type
*/
private static interface Processor<T> {
/**
* Called to process the annotation.
* @param annotation the annotation to process
* @param depth the depth of the annotation relative to the initial match. For
* example a matched annotation will have a depth of 0, a meta-annotation 1
* and a meta-meta-annotation 2
* @return the result of the processing or {@code null} to continue
*/
T process(Annotation annotation, int depth);
}
}

16
spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java

@ -22,7 +22,7 @@ import java.util.LinkedHashSet; @@ -22,7 +22,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.MultiValueMap;
/**
@ -52,11 +52,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements @@ -52,11 +52,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
/**
* Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
* providing the option to return any nested annotations or annotation arrays in the
* form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances.
* form of {@link org.springframework.core.annotation.AnnotationAttributes} instead
* of actual {@link Annotation} instances.
* @param introspectedClass the Class to instrospect
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
* {@link AnnotationAttributes} for compatibility with ASM-based
* {@link AnnotationMetadata} implementations
* {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility
* with ASM-based {@link AnnotationMetadata} implementations
* @since 3.1.1
*/
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
@ -107,8 +108,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements @@ -107,8 +108,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
@Override
public Map<String, Object> getAnnotationAttributes(String annotationType,
boolean classValuesAsString) {
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(),
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
@ -119,8 +119,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements @@ -119,8 +119,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
@Override
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(),
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
@ -141,7 +140,6 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements @@ -141,7 +140,6 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
Method[] methods = getIntrospectedClass().getDeclaredMethods();
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
for (Method method : methods) {
// TODO: on OpenJDK 8 b99, bridge methods seem to be discovered as annotated as well...
if (AnnotatedElementUtils.isAnnotated(method, annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
}

13
spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java

@ -20,6 +20,7 @@ import java.lang.reflect.Method; @@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
@ -49,9 +50,14 @@ public class StandardMethodMetadata implements MethodMetadata { @@ -49,9 +50,14 @@ public class StandardMethodMetadata implements MethodMetadata {
}
/**
* Create a new StandardMethodMetadata wrapper for the given Method.
* Create a new StandardMethodMetadata wrapper for the given Method,
* providing the option to return any nested annotations or annotation arrays in the
* form of {@link org.springframework.core.annotation.AnnotationAttributes} instead
* of actual {@link java.lang.annotation.Annotation} instances.
* @param introspectedMethod the Method to introspect
* @param nestedAnnotationsAsMap
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
* {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility
* with ASM-based {@link AnnotationMetadata} implementations
* @since 3.1.1
*/
public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
@ -115,8 +121,7 @@ public class StandardMethodMetadata implements MethodMetadata { @@ -115,8 +121,7 @@ public class StandardMethodMetadata implements MethodMetadata {
}
@Override
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}

15
spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java

@ -20,7 +20,9 @@ import java.io.Serializable; @@ -20,7 +20,9 @@ import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
@ -37,7 +39,7 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars @@ -37,7 +39,7 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
javax.transaction.Transactional ann = AnnotationUtils.getAnnotation(ae, javax.transaction.Transactional.class);
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, javax.transaction.Transactional.class.getName());
if (ann != null) {
return parseTransactionAnnotation(ann);
}
@ -47,15 +49,20 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars @@ -47,15 +49,20 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
}
public TransactionAttribute parseTransactionAnnotation(javax.transaction.Transactional ann) {
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
}
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
rbta.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + ann.value().toString());
rbta.setPropagationBehaviorName(
RuleBasedTransactionAttribute.PREFIX_PROPAGATION + attributes.getEnum("value").toString());
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
Class[] rbf = ann.rollbackOn();
Class[] rbf = attributes.getClassArray("rollbackOn");
for (Class rbRule : rbf) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
Class[] nrbf = ann.dontRollbackOn();
Class[] nrbf = attributes.getClassArray("dontRollbackOn");
for (Class rbRule : nrbf) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
rollBackRules.add(rule);

30
spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@ -20,7 +20,9 @@ import java.io.Serializable; @@ -20,7 +20,9 @@ import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
@ -37,7 +39,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP @@ -37,7 +39,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class);
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName());
if (ann != null) {
return parseTransactionAnnotation(ann);
}
@ -47,29 +49,35 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP @@ -47,29 +49,35 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
}
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
}
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
rbta.setPropagationBehavior(ann.propagation().value());
rbta.setIsolationLevel(ann.isolation().value());
rbta.setTimeout(ann.timeout());
rbta.setReadOnly(ann.readOnly());
rbta.setQualifier(ann.value());
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
Class[] rbf = ann.rollbackFor();
Class[] rbf = attributes.getClassArray("rollbackFor");
for (Class rbRule : rbf) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
String[] rbfc = ann.rollbackForClassName();
String[] rbfc = attributes.getStringArray("rollbackForClassName");
for (String rbRule : rbfc) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
Class[] nrbf = ann.noRollbackFor();
Class[] nrbf = attributes.getClassArray("noRollbackFor");
for (Class rbRule : nrbf) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
String[] nrbfc = ann.noRollbackForClassName();
String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
for (String rbRule : nrbfc) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
rollBackRules.add(rule);

59
spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java

@ -214,12 +214,42 @@ public class AnnotationTransactionAttributeSourceTests { @@ -214,12 +214,42 @@ public class AnnotationTransactionAttributeSourceTests {
Method method = TestBean6.class.getMethod("getAge", (Class[]) null);
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean5.class);
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean6.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 testCustomClassAttributeWithReadOnlyOverrideDetected() throws Exception {
Method method = TestBean7.class.getMethod("getAge", (Class[]) null);
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean7.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());
assertTrue(actual.isReadOnly());
}
@Test
public void testCustomMethodAttributeWithReadOnlyOverrideDetected() throws Exception {
Method method = TestBean8.class.getMethod("getAge", (Class[]) null);
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean8.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());
assertTrue(actual.isReadOnly());
}
@Test
@ -543,6 +573,33 @@ public class AnnotationTransactionAttributeSourceTests { @@ -543,6 +573,33 @@ public class AnnotationTransactionAttributeSourceTests {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor=Exception.class, noRollbackFor={IOException.class})
public @interface TxWithAttribute {
boolean readOnly();
}
@TxWithAttribute(readOnly=true)
public static class TestBean7 {
public int getAge() {
return 10;
}
}
public static class TestBean8 {
@TxWithAttribute(readOnly=true)
public int getAge() {
return 10;
}
}
public static interface Foo<T> {
void doSomething(T theArgument);

Loading…
Cancel
Save