Allow meta-annotations to override attributes from their parent
Issue: SPR-10181
This commit is contained in:
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -55,7 +55,8 @@ import org.springframework.core.GenericTypeResolver;
|
|||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.PriorityOrdered;
|
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.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
@@ -235,7 +236,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
Constructor<?> requiredConstructor = null;
|
Constructor<?> requiredConstructor = null;
|
||||||
Constructor<?> defaultConstructor = null;
|
Constructor<?> defaultConstructor = null;
|
||||||
for (Constructor<?> candidate : rawCandidates) {
|
for (Constructor<?> candidate : rawCandidates) {
|
||||||
Annotation annotation = findAutowiredAnnotation(candidate);
|
AnnotationAttributes annotation = findAutowiredAnnotation(candidate);
|
||||||
if (annotation != null) {
|
if (annotation != null) {
|
||||||
if (requiredConstructor != null) {
|
if (requiredConstructor != null) {
|
||||||
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
|
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
|
||||||
@@ -333,7 +334,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
do {
|
do {
|
||||||
LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<InjectionMetadata.InjectedElement>();
|
LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<InjectionMetadata.InjectedElement>();
|
||||||
for (Field field : targetClass.getDeclaredFields()) {
|
for (Field field : targetClass.getDeclaredFields()) {
|
||||||
Annotation annotation = findAutowiredAnnotation(field);
|
AnnotationAttributes annotation = findAutowiredAnnotation(field);
|
||||||
if (annotation != null) {
|
if (annotation != null) {
|
||||||
if (Modifier.isStatic(field.getModifiers())) {
|
if (Modifier.isStatic(field.getModifiers())) {
|
||||||
if (logger.isWarnEnabled()) {
|
if (logger.isWarnEnabled()) {
|
||||||
@@ -347,7 +348,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
}
|
}
|
||||||
for (Method method : targetClass.getDeclaredMethods()) {
|
for (Method method : targetClass.getDeclaredMethods()) {
|
||||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||||
Annotation annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
|
AnnotationAttributes annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
|
||||||
findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method);
|
findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method);
|
||||||
if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||||
if (Modifier.isStatic(method.getModifiers())) {
|
if (Modifier.isStatic(method.getModifiers())) {
|
||||||
@@ -374,9 +375,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
return new InjectionMetadata(clazz, elements);
|
return new InjectionMetadata(clazz, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Annotation findAutowiredAnnotation(AccessibleObject ao) {
|
private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
|
||||||
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
|
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
|
||||||
Annotation annotation = AnnotationUtils.getAnnotation(ao, type);
|
AnnotationAttributes annotation = AnnotatedElementUtils.getAnnotationAttributes(ao, type.getName());
|
||||||
if (annotation != null) {
|
if (annotation != null) {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
@@ -384,6 +385,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
return null;
|
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.
|
* Obtain all beans of the given type as autowire candidates.
|
||||||
* @param type the type of the bean
|
* @param type the type of the bean
|
||||||
@@ -398,31 +412,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||||||
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
|
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.
|
* Register the specified bean as dependent on the autowired beans.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,12 +16,11 @@
|
|||||||
|
|
||||||
package org.springframework.context.annotation;
|
package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -29,6 +28,8 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
|||||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rick Evans
|
* @author Rick Evans
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
@@ -83,6 +84,15 @@ public final class AnnotationScopeMetadataResolverTests {
|
|||||||
assertEquals(ScopedProxyMode.NO, scopeMetadata.getScopedProxyMode());
|
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)
|
@Test(expected=IllegalArgumentException.class)
|
||||||
public void testCtorWithNullScopedProxyMode() {
|
public void testCtorWithNullScopedProxyMode() {
|
||||||
new AnnotationScopeMetadataResolver(null);
|
new AnnotationScopeMetadataResolver(null);
|
||||||
@@ -98,17 +108,21 @@ public final class AnnotationScopeMetadataResolverTests {
|
|||||||
private static final class AnnotatedWithSingletonScope {
|
private static final class AnnotatedWithSingletonScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
private static final class AnnotatedWithPrototypeScope {
|
private static final class AnnotatedWithPrototypeScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
|
@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||||
private static final class AnnotatedWithScopedProxy {
|
private static final class AnnotatedWithScopedProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Scope("request")
|
||||||
|
public @interface CustomRequestScope {
|
||||||
|
}
|
||||||
|
|
||||||
@CustomRequestScope
|
@CustomRequestScope
|
||||||
private static final class AnnotatedWithCustomRequestScope {
|
private static final class AnnotatedWithCustomRequestScope {
|
||||||
}
|
}
|
||||||
@@ -117,8 +131,13 @@ public final class AnnotationScopeMetadataResolverTests {
|
|||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public @interface CustomRequestScope {
|
public @interface CustomRequestScopeWithAttribute {
|
||||||
|
|
||||||
|
ScopedProxyMode proxyMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@CustomRequestScopeWithAttribute(proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||||
|
private static final class AnnotatedWithCustomRequestScopeWithAttribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.tests.sample.beans.NestedTestBean;
|
||||||
import org.springframework.tests.sample.beans.TestBean;
|
import org.springframework.tests.sample.beans.TestBean;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -60,9 +62,19 @@ public class BeanMethodQualificationTests {
|
|||||||
assertThat(pojo.testBean.getName(), equalTo("interesting"));
|
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
|
@Configuration
|
||||||
static class StandardConfig {
|
static class StandardConfig {
|
||||||
|
|
||||||
@Bean @Lazy @Qualifier("interesting")
|
@Bean @Lazy @Qualifier("interesting")
|
||||||
public TestBean testBean1() {
|
public TestBean testBean1() {
|
||||||
return new TestBean("interesting");
|
return new TestBean("interesting");
|
||||||
@@ -76,11 +88,13 @@ public class BeanMethodQualificationTests {
|
|||||||
|
|
||||||
@Component @Lazy
|
@Component @Lazy
|
||||||
static class StandardPojo {
|
static class StandardPojo {
|
||||||
|
|
||||||
@Autowired @Qualifier("interesting") TestBean testBean;
|
@Autowired @Qualifier("interesting") TestBean testBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class CustomConfig {
|
static class CustomConfig {
|
||||||
|
|
||||||
@InterestingBean
|
@InterestingBean
|
||||||
public TestBean testBean1() {
|
public TestBean testBean1() {
|
||||||
return new TestBean("interesting");
|
return new TestBean("interesting");
|
||||||
@@ -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
|
@InterestingPojo
|
||||||
static class CustomPojo {
|
static class CustomPojo {
|
||||||
|
|
||||||
@InterestingNeed TestBean testBean;
|
@InterestingNeed TestBean testBean;
|
||||||
|
|
||||||
|
@InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean @Lazy @Qualifier("interesting")
|
@Bean @Lazy @Qualifier("interesting")
|
||||||
@@ -102,11 +133,25 @@ public class BeanMethodQualificationTests {
|
|||||||
public @interface InterestingBean {
|
public @interface InterestingBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean @Lazy @Qualifier("interesting")
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface InterestingBeanWithName {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired @Qualifier("interesting")
|
@Autowired @Qualifier("interesting")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface InterestingNeed {
|
public @interface InterestingNeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired @Qualifier("interesting")
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface InterestingNeedWithRequiredOverride {
|
||||||
|
|
||||||
|
boolean required();
|
||||||
|
}
|
||||||
|
|
||||||
@Component @Lazy
|
@Component @Lazy
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface InterestingPojo {
|
public @interface InterestingPojo {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -27,10 +26,9 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
|
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
|
||||||
* as read by Spring's reflection- or ASM-based {@link
|
* as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata}
|
||||||
* org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
|
* implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code
|
||||||
* Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
|
* as well as convenience methods for looking up annotation attributes in a type-safe fashion.
|
||||||
* as convenience methods for looking up annotation attributes in a type-safe fashion.
|
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @since 3.1.1
|
* @since 3.1.1
|
||||||
@@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||||||
super(map);
|
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) {
|
public String getString(String attributeName) {
|
||||||
return doGet(attributeName, String.class);
|
return doGet(attributeName, String.class);
|
||||||
@@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <N extends Number> N getNumber(String attributeName) {
|
public <N extends Number> N getNumber(String attributeName) {
|
||||||
return (N) doGet(attributeName, Integer.class);
|
return (N) doGet(attributeName, Number.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -124,11 +104,20 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> T doGet(String attributeName, Class<T> expectedType) {
|
private <T> T doGet(String attributeName, Class<T> expectedType) {
|
||||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
||||||
Object value = this.get(attributeName);
|
Object value = get(attributeName);
|
||||||
Assert.notNull(value, format("Attribute '%s' not found", attributeName));
|
Assert.notNull(value, String.format("Attribute '%s' not found", attributeName));
|
||||||
Assert.isAssignable(expectedType, value.getClass(),
|
if (!expectedType.isInstance(value)) {
|
||||||
format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
|
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()));
|
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
return (T) value;
|
return (T) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||||||
}
|
}
|
||||||
return String.valueOf(value);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,11 +52,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||||||
/**
|
/**
|
||||||
* Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
|
* Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
|
||||||
* providing the option to return any nested annotations or annotation arrays in the
|
* 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 introspectedClass the Class to instrospect
|
||||||
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
|
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
|
||||||
* {@link AnnotationAttributes} for compatibility with ASM-based
|
* {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility
|
||||||
* {@link AnnotationMetadata} implementations
|
* with ASM-based {@link AnnotationMetadata} implementations
|
||||||
* @since 3.1.1
|
* @since 3.1.1
|
||||||
*/
|
*/
|
||||||
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
|
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
|
||||||
@@ -107,8 +108,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getAnnotationAttributes(String annotationType,
|
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
|
||||||
boolean classValuesAsString) {
|
|
||||||
return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(),
|
return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(),
|
||||||
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
||||||
}
|
}
|
||||||
@@ -119,8 +119,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultiValueMap<String, Object> getAllAnnotationAttributes(
|
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
|
||||||
String annotationType, boolean classValuesAsString) {
|
|
||||||
return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(),
|
return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(),
|
||||||
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
||||||
}
|
}
|
||||||
@@ -141,7 +140,6 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||||||
Method[] methods = getIntrospectedClass().getDeclaredMethods();
|
Method[] methods = getIntrospectedClass().getDeclaredMethods();
|
||||||
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
||||||
for (Method method : methods) {
|
for (Method method : methods) {
|
||||||
// TODO: on OpenJDK 8 b99, bridge methods seem to be discovered as annotated as well...
|
|
||||||
if (AnnotatedElementUtils.isAnnotated(method, annotationType)) {
|
if (AnnotatedElementUtils.isAnnotated(method, annotationType)) {
|
||||||
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
|
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
@@ -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 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
|
* @since 3.1.1
|
||||||
*/
|
*/
|
||||||
public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
|
public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
|
||||||
@@ -115,8 +121,7 @@ public class StandardMethodMetadata implements MethodMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultiValueMap<String, Object> getAllAnnotationAttributes(
|
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
|
||||||
String annotationType, boolean classValuesAsString) {
|
|
||||||
return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
|
return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
|
||||||
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import java.io.Serializable;
|
|||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
|
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
|
||||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||||
@@ -37,7 +39,7 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
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) {
|
if (ann != null) {
|
||||||
return parseTransactionAnnotation(ann);
|
return parseTransactionAnnotation(ann);
|
||||||
}
|
}
|
||||||
@@ -47,15 +49,20 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TransactionAttribute parseTransactionAnnotation(javax.transaction.Transactional ann) {
|
public TransactionAttribute parseTransactionAnnotation(javax.transaction.Transactional ann) {
|
||||||
|
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
|
||||||
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
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>();
|
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
|
||||||
Class[] rbf = ann.rollbackOn();
|
Class[] rbf = attributes.getClassArray("rollbackOn");
|
||||||
for (Class rbRule : rbf) {
|
for (Class rbRule : rbf) {
|
||||||
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
}
|
}
|
||||||
Class[] nrbf = ann.dontRollbackOn();
|
Class[] nrbf = attributes.getClassArray("dontRollbackOn");
|
||||||
for (Class rbRule : nrbf) {
|
for (Class rbRule : nrbf) {
|
||||||
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,7 +20,9 @@ import java.io.Serializable;
|
|||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
|
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
|
||||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||||
@@ -37,7 +39,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
||||||
Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class);
|
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName());
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
return parseTransactionAnnotation(ann);
|
return parseTransactionAnnotation(ann);
|
||||||
}
|
}
|
||||||
@@ -47,29 +49,35 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
|
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
|
||||||
|
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
|
||||||
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
||||||
rbta.setPropagationBehavior(ann.propagation().value());
|
Propagation propagation = attributes.getEnum("propagation");
|
||||||
rbta.setIsolationLevel(ann.isolation().value());
|
rbta.setPropagationBehavior(propagation.value());
|
||||||
rbta.setTimeout(ann.timeout());
|
Isolation isolation = attributes.getEnum("isolation");
|
||||||
rbta.setReadOnly(ann.readOnly());
|
rbta.setIsolationLevel(isolation.value());
|
||||||
rbta.setQualifier(ann.value());
|
rbta.setTimeout(attributes.getNumber("timeout").intValue());
|
||||||
|
rbta.setReadOnly(attributes.getBoolean("readOnly"));
|
||||||
|
rbta.setQualifier(attributes.getString("value"));
|
||||||
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
|
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<RollbackRuleAttribute>();
|
||||||
Class[] rbf = ann.rollbackFor();
|
Class[] rbf = attributes.getClassArray("rollbackFor");
|
||||||
for (Class rbRule : rbf) {
|
for (Class rbRule : rbf) {
|
||||||
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
}
|
}
|
||||||
String[] rbfc = ann.rollbackForClassName();
|
String[] rbfc = attributes.getStringArray("rollbackForClassName");
|
||||||
for (String rbRule : rbfc) {
|
for (String rbRule : rbfc) {
|
||||||
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
}
|
}
|
||||||
Class[] nrbf = ann.noRollbackFor();
|
Class[] nrbf = attributes.getClassArray("noRollbackFor");
|
||||||
for (Class rbRule : nrbf) {
|
for (Class rbRule : nrbf) {
|
||||||
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
}
|
}
|
||||||
String[] nrbfc = ann.noRollbackForClassName();
|
String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
|
||||||
for (String rbRule : nrbfc) {
|
for (String rbRule : nrbfc) {
|
||||||
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
|
||||||
rollBackRules.add(rule);
|
rollBackRules.add(rule);
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ public class AnnotationTransactionAttributeSourceTests {
|
|||||||
Method method = TestBean6.class.getMethod("getAge", (Class[]) null);
|
Method method = TestBean6.class.getMethod("getAge", (Class[]) null);
|
||||||
|
|
||||||
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
|
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
|
||||||
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean5.class);
|
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean6.class);
|
||||||
|
|
||||||
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
||||||
rbta.getRollbackRules().add(new RollbackRuleAttribute(Exception.class));
|
rbta.getRollbackRules().add(new RollbackRuleAttribute(Exception.class));
|
||||||
@@ -222,6 +222,36 @@ public class AnnotationTransactionAttributeSourceTests {
|
|||||||
assertEquals(rbta.getRollbackRules(), ((RuleBasedTransactionAttribute) actual).getRollbackRules());
|
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
|
@Test
|
||||||
public void testTransactionAttributeDeclaredOnClassMethodWithEjb3() throws Exception {
|
public void testTransactionAttributeDeclaredOnClassMethodWithEjb3() throws Exception {
|
||||||
Method getAgeMethod = ITestBean.class.getMethod("getAge", (Class[]) null);
|
Method getAgeMethod = ITestBean.class.getMethod("getAge", (Class[]) null);
|
||||||
@@ -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> {
|
public static interface Foo<T> {
|
||||||
|
|
||||||
void doSomething(T theArgument);
|
void doSomething(T theArgument);
|
||||||
|
|||||||
Reference in New Issue
Block a user