Allow meta-annotations to override attributes from their parent
Issue: SPR-10181
This commit is contained in:
parent
87c2d6fd12
commit
6d3649858e
|
|
@ -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;
|
|||
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
|
|||
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
|
|||
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
|
|||
}
|
||||
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
|
|||
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
|
|||
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
|
|||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
@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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
}
|
||||
}
|
||||
|
||||
@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 {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
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;
|
|||
|
||||
/**
|
||||
* {@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> {
|
|||
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> {
|
|||
|
||||
@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> {
|
|||
@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> {
|
|||
}
|
||||
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.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
|
|||
/**
|
||||
* 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
|
|||
}
|
||||
|
||||
@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
|
|||
}
|
||||
|
||||
@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
|
|||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
|
||||
@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
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
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
|
|||
|
||||
@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
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ 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));
|
||||
|
|
@ -222,6 +222,36 @@ public class AnnotationTransactionAttributeSourceTests {
|
|||
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
|
||||
public void testTransactionAttributeDeclaredOnClassMethodWithEjb3() throws Exception {
|
||||
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> {
|
||||
|
||||
void doSomething(T theArgument);
|
||||
|
|
|
|||
Loading…
Reference in New Issue