/*
* Copyright 2002-2017 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.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.internal.ArrayComparisonFailure;
import org.junit.rules.ExpectedException;
import org.springframework.core.annotation.AnnotationUtilsTests.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Indexed;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
import static org.springframework.core.annotation.AnnotationUtilsTests.*;
/**
* Unit tests for {@link AnnotatedElementUtils}.
*
* @author Sam Brannen
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0.3
* @see AnnotationUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
* @see ComposedRepeatableAnnotationsTests
*/
public class AnnotatedElementUtilsTests {
private static final String TX_NAME = Transactional.class.getName();
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void getMetaAnnotationTypesOnNonAnnotatedClass() {
assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class));
assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class.getName()));
}
@Test
public void getMetaAnnotationTypesOnClassWithMetaDepth1() {
Set<String> names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class);
assertEquals(names(Transactional.class, Component.class, Indexed.class), names);
names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName());
assertEquals(names(Transactional.class, Component.class, Indexed.class), names);
}
@Test
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class);
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Indexed.class), names);
names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName());
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Indexed.class), names);
}
private Set<String> names(Class<?>... classes) {
return stream(classes).map(Class::getName).collect(toSet());
}
@Test
public void hasMetaAnnotationTypesOnNonAnnotatedClass() {
assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, TX_NAME));
}
@Test
public void hasMetaAnnotationTypesOnClassWithMetaDepth0() {
assertFalse(hasMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName()));
}
@Test
public void hasMetaAnnotationTypesOnClassWithMetaDepth1() {
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, TX_NAME));
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName()));
}
@Test
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, TX_NAME));
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
}
@Test
public void isAnnotatedOnNonAnnotatedClass() {
assertFalse(isAnnotated(NonAnnotatedClass.class, TX_NAME));
}
@Test
public void isAnnotatedOnClassWithMetaDepth0() {
assertTrue(isAnnotated(TransactionalComponentClass.class, TransactionalComponent.class.getName()));
}
@Test
public void isAnnotatedOnSubclassWithMetaDepth0() {
assertFalse("isAnnotated() does not search the class hierarchy.",
isAnnotated(SubTransactionalComponentClass.class, TransactionalComponent.class.getName()));
}
@Test
public void isAnnotatedOnClassWithMetaDepth1() {
assertTrue(isAnnotated(TransactionalComponentClass.class, TX_NAME));
assertTrue(isAnnotated(TransactionalComponentClass.class, Component.class.getName()));
}
@Test
public void isAnnotatedOnClassWithMetaDepth2() {
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, TX_NAME));
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, Component.class.getName()));
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
}
@Test
public void getAllAnnotationAttributesOnNonAnnotatedClass() {
assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, TX_NAME));
}
@Test
public void getAllAnnotationAttributesOnClassWithLocalAnnotation() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxConfig.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on TxConfig", attributes);
assertEquals("value for TxConfig.", asList("TxConfig"), attributes.get("value"));
}
@Test
public void getAllAnnotationAttributesOnClassWithLocalComposedAnnotationAndInheritedAnnotation() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(SubClassWithInheritedAnnotation.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on SubClassWithInheritedAnnotation", attributes);
assertEquals(asList("composed2", "transactionManager"), attributes.get("qualifier"));
}
@Test
public void getAllAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(SubSubClassWithInheritedAnnotation.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedAnnotation", attributes);
assertEquals(asList("transactionManager"), attributes.get("qualifier"));
}
@Test
public void getAllAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes( SubSubClassWithInheritedComposedAnnotation.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedComposedAnnotation", attributes);
assertEquals(asList("composed1"), attributes.get("qualifier"));
}
/**
* If the "value" entry contains both "DerivedTxConfig" AND "TxConfig", then
* the algorithm is accidentally picking up shadowed annotations of the same
* type within the class hierarchy. Such undesirable behavior would cause the
* logic in {@link org.springframework.context.annotation.ProfileCondition}
* to fail.
* @see org.springframework.core.env.EnvironmentSystemIntegrationTests#mostSpecificDerivedClassDrivesEnvironment_withDevEnvAndDerivedDevConfigClass
*/
@Test
public void getAllAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(DerivedTxConfig.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on DerivedTxConfig", attributes);
assertEquals("value for DerivedTxConfig.", asList("DerivedTxConfig"), attributes.get("value"));
}
/**
* Note: this functionality is required by {@link org.springframework.context.annotation.ProfileCondition}.
* @see org.springframework.core.env.EnvironmentSystemIntegrationTests
*/
@Test
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxInheritedComposed", "TxComposed"),
attributes.get("value"));
}
@Test
public void getMergedAnnotationAttributesOnClassWithLocalAnnotation() {
Class<?> element = TxConfig.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Annotation attributes for @Transactional on TxConfig", attributes);
assertEquals("value for TxConfig.", "TxConfig", attributes.getString("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() {
Class<?> element = DerivedTxConfig.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Annotation attributes for @Transactional on DerivedTxConfig", attributes);
assertEquals("value for DerivedTxConfig.", "DerivedTxConfig", attributes.getString("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
AnnotationAttributes attributes = getMergedAnnotationAttributes(MetaCycleAnnotatedClass.class, TX_NAME);
assertNull("Should not find annotation attributes for @Transactional on MetaCycleAnnotatedClass", attributes);
}
@Test
public void getMergedAnnotationAttributesFavorsLocalComposedAnnotationOverInheritedAnnotation() {
Class<?> element = SubClassWithInheritedAnnotation.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("AnnotationAttributes for @Transactional on SubClassWithInheritedAnnotation", attributes);
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
assertTrue("readOnly flag for SubClassWithInheritedAnnotation.", attributes.getBoolean("readOnly"));
}
@Test
public void getMergedAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
Class<?> element = SubSubClassWithInheritedAnnotation.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("AnnotationAttributes for @Transactional on SubSubClassWithInheritedAnnotation", attributes);
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
assertFalse("readOnly flag for SubSubClassWithInheritedAnnotation.", attributes.getBoolean("readOnly"));
}
@Test
public void getMergedAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
Class<?> element = SubSubClassWithInheritedComposedAnnotation.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("AnnotationAttributtes for @Transactional on SubSubClassWithInheritedComposedAnnotation.", attributes);
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
assertFalse("readOnly flag for SubSubClassWithInheritedComposedAnnotation.", attributes.getBoolean("readOnly"));
}
@Test
public void getMergedAnnotationAttributesFromInterfaceImplementedBySuperclass() {
Class<?> element = ConcreteClassWithInheritedAnnotation.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNull("Should not find @Transactional on ConcreteClassWithInheritedAnnotation", attributes);
// Verify contracts between utility methods:
assertFalse(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesOnInheritedAnnotationInterface() {
Class<?> element = InheritedAnnotationInterface.class;
String name = TX_NAME;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes);
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesOnNonInheritedAnnotationInterface() {
Class<?> element = NonInheritedAnnotationInterface.class;
String name = Order.class.getName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes);
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesWithConventionBasedComposedAnnotation() {
Class<?> element = ConventionBasedComposedContextConfigClass.class;
String name = ContextConfig.class.getName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
assertArrayEquals("locations", asArray("explicitDeclaration"), attributes.getStringArray("locations"));
assertArrayEquals("value", asArray("explicitDeclaration"), attributes.getStringArray("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
/**
* This test should never pass, simply because Spring does not support a hybrid
* approach for annotation attribute overrides with transitive implicit aliases.
* See SPR-13554 for details.
* <p>Furthermore, if you choose to execute this test, it can fail for either
* the first test class or the second one (with different exceptions), depending
* on the order in which the JVM returns the attribute methods via reflection.
*/
@Ignore("Permanently disabled but left in place for illustrative purposes")
@Test
public void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation() {
for (Class<?> clazz : asList(HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1.class,
HalfConventionBasedAndHalfAliasedComposedContextConfigClassV2.class)) {
getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(clazz);
}
}
private void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(Class<?> clazz) {
String[] expected = asArray("explicitDeclaration");
String name = ContextConfig.class.getName();
String simpleName = clazz.getSimpleName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(clazz, name);
assertNotNull("Should find @ContextConfig on " + simpleName, attributes);
assertArrayEquals("locations for class [" + clazz.getSimpleName() + "]", expected, attributes.getStringArray("locations"));
assertArrayEquals("value for class [" + clazz.getSimpleName() + "]", expected, attributes.getStringArray("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(clazz, name));
}
@Test
public void getMergedAnnotationAttributesWithAliasedComposedAnnotation() {
Class<?> element = AliasedComposedContextConfigClass.class;
String name = ContextConfig.class.getName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
assertArrayEquals("value", asArray("test.xml"), attributes.getStringArray("value"));
assertArrayEquals("locations", asArray("test.xml"), attributes.getStringArray("locations"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesWithAliasedValueComposedAnnotation() {
Class<?> element = AliasedValueComposedContextConfigClass.class;
String name = ContextConfig.class.getName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
assertArrayEquals("locations", asArray("test.xml"), attributes.getStringArray("locations"));
assertArrayEquals("value", asArray("test.xml"), attributes.getStringArray("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesWithImplicitAliasesInMetaAnnotationOnComposedAnnotation() {
Class<?> element = ComposedImplicitAliasesContextConfigClass.class;
String name = ImplicitAliasesContextConfig.class.getName();
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
String[] expected = asArray("A.xml", "B.xml");
assertNotNull("Should find @ImplicitAliasesContextConfig on " + element.getSimpleName(), attributes);
assertArrayEquals("groovyScripts", expected, attributes.getStringArray("groovyScripts"));
assertArrayEquals("xmlFiles", expected, attributes.getStringArray("xmlFiles"));
assertArrayEquals("locations", expected, attributes.getStringArray("locations"));
assertArrayEquals("value", expected, attributes.getStringArray("value"));
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationWithAliasedValueComposedAnnotation() {
assertGetMergedAnnotation(AliasedValueComposedContextConfigClass.class, "test.xml");
}
@Test
public void getMergedAnnotationWithImplicitAliasesForSameAttributeInComposedAnnotation() {
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass1.class, "foo.xml");
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass2.class, "bar.xml");
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass3.class, "baz.xml");
}
@Test
public void getMergedAnnotationWithTransitiveImplicitAliases() {
assertGetMergedAnnotation(TransitiveImplicitAliasesContextConfigClass.class, "test.groovy");
}
@Test
public void getMergedAnnotationWithTransitiveImplicitAliasesWithSingleElementOverridingAnArrayViaAliasFor() {
assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesContextConfigClass.class, "test.groovy");
}
@Test
public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevel() {
assertGetMergedAnnotation(TransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml");
}
@Test
public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevelWithSingleElementOverridingAnArrayViaAliasFor() {
assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml");
}
private void assertGetMergedAnnotation(Class<?> element, String... expected) {
String name = ContextConfig.class.getName();
ContextConfig contextConfig = getMergedAnnotation(element, ContextConfig.class);
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), contextConfig);
assertArrayEquals("locations", expected, contextConfig.locations());
assertArrayEquals("value", expected, contextConfig.value());
assertArrayEquals("classes", new Class<?>[0], contextConfig.classes());
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationWithImplicitAliasesInMetaAnnotationOnComposedAnnotation() {
Class<?> element = ComposedImplicitAliasesContextConfigClass.class;
String name = ImplicitAliasesContextConfig.class.getName();
ImplicitAliasesContextConfig config = getMergedAnnotation(element, ImplicitAliasesContextConfig.class);
String[] expected = asArray("A.xml", "B.xml");
assertNotNull("Should find @ImplicitAliasesContextConfig on " + element.getSimpleName(), config);
assertArrayEquals("groovyScripts", expected, config.groovyScripts());
assertArrayEquals("xmlFiles", expected, config.xmlFiles());
assertArrayEquals("locations", expected, config.locations());
assertArrayEquals("value", expected, config.value());
// Verify contracts between utility methods:
assertTrue(isAnnotated(element, name));
}
@Test
public void getMergedAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() {
Class<?> element = InvalidConventionBasedComposedContextConfigClass.class;
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(either(containsString("attribute 'value' and its alias 'locations'")).or(
containsString("attribute 'locations' and its alias 'value'")));
exception.expectMessage(either(
containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or(
containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]")));
exception.expectMessage(containsString("but only one is permitted"));
getMergedAnnotationAttributes(element, ContextConfig.class);
}
@Test
public void getMergedAnnotationAttributesWithShadowedAliasComposedAnnotation() {
Class<?> element = ShadowedAliasComposedContextConfigClass.class;
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, ContextConfig.class);
String[] expected = asArray("test.xml");
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
assertArrayEquals("locations", expected, attributes.getStringArray("locations"));
assertArrayEquals("value", expected, attributes.getStringArray("value"));
}
@Test
public void findMergedAnnotationAttributesOnInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesOnSubInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(SubInheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesOnSubSubInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(SubSubInheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesOnNonInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesOnSubNonInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(SubNonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes);
}
@Test
public void findMergedAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException {
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface");
AnnotationAttributes attributes = findMergedAnnotationAttributes(method, Order.class);
assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method", attributes);
}
@Test
public void findMergedAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException {
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle");
AnnotationAttributes attributes = findMergedAnnotationAttributes(method, Transactional.class);
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handle() method", attributes);
}
/**
* <p>{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas,
* {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}.
* <p>As of Spring 4.2, {@code AnnotatedElementUtils.processWithFindSemantics()} does not resolve an
* <em>equivalent</em> method in {@code AbstractClassWithInheritedAnnotation} for the <em>bridged</em>
* {@code handleParameterized(String)} method.
* @since 4.2
*/
@Test
public void findMergedAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
AnnotationAttributes attributes = findMergedAnnotationAttributes(method, Transactional.class);
assertNull("Should not find @Transactional on bridged ConcreteClassWithInheritedAnnotation.handleParameterized()", attributes);
}
/**
* Bridge/bridged method setup code copied from
* {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}.
* @since 4.2
*/
@Test
public void findMergedAnnotationAttributesFromBridgeMethod() throws NoSuchMethodException {
Method[] methods = StringGenericParameter.class.getMethods();
Method bridgeMethod = null;
Method bridgedMethod = null;
for (Method method : methods) {
if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
if (method.getReturnType().equals(Object.class)) {
bridgeMethod = method;
}
else {
bridgedMethod = method;
}
}
}
assertTrue(bridgeMethod != null && bridgeMethod.isBridge());
assertTrue(bridgedMethod != null && !bridgedMethod.isBridge());
AnnotationAttributes attributes = findMergedAnnotationAttributes(bridgeMethod, Order.class);
assertNotNull("Should find @Order on StringGenericParameter.getFor() bridge method", attributes);
}
@Test
public void findMergedAnnotationAttributesOnClassWithMetaAndLocalTxConfig() {
AnnotationAttributes attributes = findMergedAnnotationAttributes(MetaAndLocalTxConfigClass.class, Transactional.class);
assertNotNull("Should find @Transactional on MetaAndLocalTxConfigClass", attributes);
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
}
@Test
public void findAndSynthesizeAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
String qualifier = "aliasForQualifier";
// 1) Find and merge AnnotationAttributes from the annotation hierarchy
AnnotationAttributes attributes = findMergedAnnotationAttributes(
AliasedTransactionalComponentClass.class, AliasedTransactional.class);
assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes);
// 2) Synthesize the AnnotationAttributes back into the target annotation
AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes,
AliasedTransactional.class, AliasedTransactionalComponentClass.class);
assertNotNull(annotation);
// 3) Verify that the AnnotationAttributes and synthesized annotation are equivalent
assertEquals("TX value via attributes.", qualifier, attributes.getString("value"));
assertEquals("TX value via synthesized annotation.", qualifier, annotation.value());
assertEquals("TX qualifier via attributes.", qualifier, attributes.getString("qualifier"));
assertEquals("TX qualifier via synthesized annotation.", qualifier, annotation.qualifier());
}
@Test
public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
AnnotationAttributes attributes = assertComponentScanAttributes(TestComponentScanClass.class, "com.example.app.test");
Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
assertNotNull(excludeFilters);
List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList());
assertEquals(asList("*Test", "*Tests"), patterns);
}
/**
* This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes}
* uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation
* attributes since attributes may be arrays.
*/
@Test
public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() {
assertComponentScanAttributes(ComponentScanWithBasePackagesAndValueAliasClass.class, "com.example.app.test");
}
@Test
public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() {
assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test");
}
@Test
public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaAliasFor() {
assertComponentScanAttributes(AliasForBasedSinglePackageComponentScanClass.class, "com.example.app.test");
}
private AnnotationAttributes assertComponentScanAttributes(Class<?> element, String... expected) {
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
assertNotNull("Should find @ComponentScan on " + element, attributes);
assertArrayEquals("value: ", expected, attributes.getStringArray("value"));
assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages"));
return attributes;
}
private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
}
@Test
public void findMergedAnnotationWithAttributeAliasesInTargetAnnotation() {
Class<?> element = AliasedTransactionalComponentClass.class;
AliasedTransactional annotation = findMergedAnnotation(element, AliasedTransactional.class);
assertNotNull("@AliasedTransactional on " + element, annotation);
assertEquals("TX value via synthesized annotation.", "aliasForQualifier", annotation.value());
assertEquals("TX qualifier via synthesized annotation.", "aliasForQualifier", annotation.qualifier());
}
@Test
public void findMergedAnnotationForMultipleMetaAnnotationsWithClashingAttributeNames() {
String[] xmlLocations = asArray("test.xml");
String[] propFiles = asArray("test.properties");
Class<?> element = AliasedComposedContextConfigAndTestPropSourceClass.class;
ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class);
assertNotNull("@ContextConfig on " + element, contextConfig);
assertArrayEquals("locations", xmlLocations, contextConfig.locations());
assertArrayEquals("value", xmlLocations, contextConfig.value());
// Synthesized annotation
TestPropSource testPropSource = AnnotationUtils.findAnnotation(element, TestPropSource.class);
assertArrayEquals("locations", propFiles, testPropSource.locations());
assertArrayEquals("value", propFiles, testPropSource.value());
// Merged annotation
testPropSource = findMergedAnnotation(element, TestPropSource.class);
assertNotNull("@TestPropSource on " + element, testPropSource);
assertArrayEquals("locations", propFiles, testPropSource.locations());
assertArrayEquals("value", propFiles, testPropSource.value());
}
@Test
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
final String[] EMPTY = new String[0];
Class<?> element = SpringAppConfigClass.class;
ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class);
assertNotNull("Should find @ContextConfig on " + element, contextConfig);
assertArrayEquals("locations for " + element, EMPTY, contextConfig.locations());
// 'value' in @SpringAppConfig should not override 'value' in @ContextConfig
assertArrayEquals("value for " + element, EMPTY, contextConfig.value());
assertArrayEquals("classes for " + element, new Class<?>[] {Number.class}, contextConfig.classes());
}
@Test
public void findMergedAnnotationWithSingleElementOverridingAnArrayViaConvention() throws Exception {
assertWebMapping(WebController.class.getMethod("postMappedWithPathAttribute"));
}
@Test
public void findMergedAnnotationWithSingleElementOverridingAnArrayViaAliasFor() throws Exception {
assertWebMapping(WebController.class.getMethod("getMappedWithValueAttribute"));
assertWebMapping(WebController.class.getMethod("getMappedWithPathAttribute"));
}
private void assertWebMapping(AnnotatedElement element) throws ArrayComparisonFailure {
WebMapping webMapping = findMergedAnnotation(element, WebMapping.class);
assertNotNull(webMapping);
assertArrayEquals("value attribute: ", asArray("/test"), webMapping.value());
assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path());
}
@Test
public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception {
Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class);
assertEquals(deprecatedCtor.getAnnotation(Deprecated.class), findMergedAnnotation(deprecatedCtor, Deprecated.class));
assertEquals(Date.class.getAnnotation(Deprecated.class), findMergedAnnotation(Date.class, Deprecated.class));
}
@Test
public void javaxAnnotationTypeViaFindMergedAnnotation() throws Exception {
assertEquals(ResourceHolder.class.getAnnotation(Resource.class), findMergedAnnotation(ResourceHolder.class, Resource.class));
assertEquals(SpringAppConfigClass.class.getAnnotation(Resource.class), findMergedAnnotation(SpringAppConfigClass.class, Resource.class));
}
@Test
public void getAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(m, Transactional.class);
assertTrue(allMergedAnnotations.isEmpty());
}
@Test
public void findAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(m, Transactional.class);
assertEquals(1, allMergedAnnotations.size());
}
// -------------------------------------------------------------------------
@MetaCycle3
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@interface MetaCycle1 {
}
@MetaCycle1
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@interface MetaCycle2 {
}
@MetaCycle2
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MetaCycle3 {
}
@MetaCycle3
static class MetaCycleAnnotatedClass {
}
// -------------------------------------------------------------------------
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
@interface Transactional {
String value() default "";
String qualifier() default "transactionManager";
boolean readOnly() default false;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
@interface AliasedTransactional {
@AliasFor(attribute = "qualifier")
String value() default "";
@AliasFor(attribute = "value")
String qualifier() default "";
}
@Transactional(qualifier = "composed1")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@interface InheritedComposed {
}
@Transactional(qualifier = "composed2", readOnly = true)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Composed {
}
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@interface TxComposedWithOverride {
String qualifier() default "txMgr";
}
@Transactional("TxInheritedComposed")
@Retention(RetentionPolicy.RUNTIME)
@interface TxInheritedComposed {
}
@Transactional("TxComposed")
@Retention(RetentionPolicy.RUNTIME)
@interface TxComposed {
}
@Transactional
@Component
@Retention(RetentionPolicy.RUNTIME)
@interface TransactionalComponent {
}
@TransactionalComponent
@Retention(RetentionPolicy.RUNTIME)
@interface ComposedTransactionalComponent {
}
@AliasedTransactional(value = "aliasForQualifier")
@Component
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedTransactionalComponent {
}
@TxComposedWithOverride
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
@Transactional(qualifier = "localTxMgr")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MetaAndLocalTxConfig {
}
/**
* Mock of {@code org.springframework.test.context.TestPropertySource}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface TestPropSource {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
}
/**
* Mock of {@code org.springframework.test.context.ContextConfiguration}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ContextConfig {
@AliasFor(attribute = "locations")
String[] value() default {};
@AliasFor(attribute = "value")
String[] locations() default {};
Class<?>[] classes() default {};
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ConventionBasedComposedContextConfig {
String[] locations() default {};
}
@ContextConfig(value = "duplicateDeclaration")
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidConventionBasedComposedContextConfig {
String[] locations();
}
/**
* This hybrid approach for annotation attribute overrides with transitive implicit
* aliases is unsupported. See SPR-13554 for details.
*/
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface HalfConventionBasedAndHalfAliasedComposedContextConfig {
String[] locations() default {};
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlConfigFiles() default {};
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedComposedContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlConfigFiles();
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedValueComposedContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "value")
String[] locations();
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] groovyScripts() default {};
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlFiles() default {};
// intentionally omitted: attribute = "locations"
@AliasFor(annotation = ContextConfig.class)
String[] locations() default {};
// intentionally omitted: attribute = "locations" (SPR-14069)
@AliasFor(annotation = ContextConfig.class)
String[] value() default {};
}
@ImplicitAliasesContextConfig(xmlFiles = {"A.xml", "B.xml"})
@Retention(RetentionPolicy.RUNTIME)
@interface ComposedImplicitAliasesContextConfig {
}
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesContextConfig {
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "xmlFiles")
String[] xml() default {};
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
String[] groovy() default {};
}
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface SingleLocationTransitiveImplicitAliasesContextConfig {
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "xmlFiles")
String xml() default "";
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
String groovy() default "";
}
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesWithSkippedLevelContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xml() default {};
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
String[] groovy() default {};
}
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String xml() default "";
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts")
String groovy() default "";
}
/**
* Although the configuration declares an explicit value for 'value' and
* requires a value for the aliased 'locations', this does not result in
* an error since 'locations' effectively <em>shadows</em> the 'value'
* attribute (which cannot be set via the composed annotation anyway).
*
* If 'value' were not shadowed, such a declaration would not make sense.
*/
@ContextConfig(value = "duplicateDeclaration")
@Retention(RetentionPolicy.RUNTIME)
@interface ShadowedAliasComposedContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlConfigFiles();
}
@ContextConfig(locations = "shadowed.xml")
@TestPropSource(locations = "test.properties")
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedComposedContextConfigAndTestPropSource {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] xmlConfigFiles() default "default.xml";
}
/**
* Mock of {@code org.springframework.boot.test.SpringApplicationConfiguration}.
*/
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface SpringAppConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
String[] locations() default {};
@AliasFor("value")
Class<?>[] classes() default {};
@AliasFor("classes")
Class<?>[] value() default {};
}
/**
* Mock of {@code org.springframework.context.annotation.ComponentScan}
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Filter[] excludeFilters() default {};
}
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
String pattern();
}
@ComponentScan(excludeFilters = {@Filter(pattern = "*Test"), @Filter(pattern = "*Tests")})
@Retention(RetentionPolicy.RUNTIME)
@interface TestComponentScan {
@AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
String[] packages();
}
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@interface ConventionBasedSinglePackageComponentScan {
String basePackages();
}
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForBasedSinglePackageComponentScan {
@AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
String pkg();
}
// -------------------------------------------------------------------------
static class NonAnnotatedClass {
}
@TransactionalComponent
static class TransactionalComponentClass {
}
static class SubTransactionalComponentClass extends TransactionalComponentClass {
}
@ComposedTransactionalComponent
static class ComposedTransactionalComponentClass {
}
@AliasedTransactionalComponent
static class AliasedTransactionalComponentClass {
}
@Transactional
static class ClassWithInheritedAnnotation {
}
@Composed
static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation {
}
static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation {
}
@InheritedComposed
static class ClassWithInheritedComposedAnnotation {
}
@Composed
static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedComposedAnnotation {
}
static class SubSubClassWithInheritedComposedAnnotation extends SubClassWithInheritedComposedAnnotation {
}
@MetaAndLocalTxConfig
static class MetaAndLocalTxConfigClass {
}
@Transactional("TxConfig")
static class TxConfig {
}
@Transactional("DerivedTxConfig")
static class DerivedTxConfig extends TxConfig {
}
@TxInheritedComposed
@TxComposed
static class TxFromMultipleComposedAnnotations {
}
@Transactional
static interface InterfaceWithInheritedAnnotation {
@Order
void handleFromInterface();
}
static abstract class AbstractClassWithInheritedAnnotation<T> implements InterfaceWithInheritedAnnotation {
@Transactional
public abstract void handle();
@Transactional
public void handleParameterized(T t) {
}
}
static class ConcreteClassWithInheritedAnnotation extends AbstractClassWithInheritedAnnotation<String> {
@Override
public void handle() {
}
@Override
public void handleParameterized(String s) {
}
@Override
public void handleFromInterface() {
}
}
public interface GenericParameter<T> {
T getFor(Class<T> cls);
}
@SuppressWarnings("unused")
private static class StringGenericParameter implements GenericParameter<String> {
@Order
@Override
public String getFor(Class<String> cls) {
return "foo";
}
public String getFor(Integer integer) {
return "foo";
}
}
@Transactional
public interface InheritedAnnotationInterface {
}
public interface SubInheritedAnnotationInterface extends InheritedAnnotationInterface {
}
public interface SubSubInheritedAnnotationInterface extends SubInheritedAnnotationInterface {
}
@Order
public interface NonInheritedAnnotationInterface {
}
public interface SubNonInheritedAnnotationInterface extends NonInheritedAnnotationInterface {
}
public interface SubSubNonInheritedAnnotationInterface extends SubNonInheritedAnnotationInterface {
}
@ConventionBasedComposedContextConfig(locations = "explicitDeclaration")
static class ConventionBasedComposedContextConfigClass {
}
@InvalidConventionBasedComposedContextConfig(locations = "requiredLocationsDeclaration")
static class InvalidConventionBasedComposedContextConfigClass {
}
@HalfConventionBasedAndHalfAliasedComposedContextConfig(xmlConfigFiles = "explicitDeclaration")
static class HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1 {
}
@HalfConventionBasedAndHalfAliasedComposedContextConfig(locations = "explicitDeclaration")
static class HalfConventionBasedAndHalfAliasedComposedContextConfigClassV2 {
}
@AliasedComposedContextConfig(xmlConfigFiles = "test.xml")
static class AliasedComposedContextConfigClass {
}
@AliasedValueComposedContextConfig(locations = "test.xml")
static class AliasedValueComposedContextConfigClass {
}
@ImplicitAliasesContextConfig("foo.xml")
static class ImplicitAliasesContextConfigClass1 {
}
@ImplicitAliasesContextConfig(locations = "bar.xml")
static class ImplicitAliasesContextConfigClass2 {
}
@ImplicitAliasesContextConfig(xmlFiles = "baz.xml")
static class ImplicitAliasesContextConfigClass3 {
}
@TransitiveImplicitAliasesContextConfig(groovy = "test.groovy")
static class TransitiveImplicitAliasesContextConfigClass {
}
@SingleLocationTransitiveImplicitAliasesContextConfig(groovy = "test.groovy")
static class SingleLocationTransitiveImplicitAliasesContextConfigClass {
}
@TransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml")
static class TransitiveImplicitAliasesWithSkippedLevelContextConfigClass {
}
@SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml")
static class SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass {
}
@ComposedImplicitAliasesContextConfig
static class ComposedImplicitAliasesContextConfigClass {
}
@ShadowedAliasComposedContextConfig(xmlConfigFiles = "test.xml")
static class ShadowedAliasComposedContextConfigClass {
}
@AliasedComposedContextConfigAndTestPropSource(xmlConfigFiles = "test.xml")
static class AliasedComposedContextConfigAndTestPropSourceClass {
}
@ComponentScan(value = "com.example.app.test", basePackages = "com.example.app.test")
static class ComponentScanWithBasePackagesAndValueAliasClass {
}
@TestComponentScan(packages = "com.example.app.test")
static class TestComponentScanClass {
}
@ConventionBasedSinglePackageComponentScan(basePackages = "com.example.app.test")
static class ConventionBasedSinglePackageComponentScanClass {
}
@AliasForBasedSinglePackageComponentScan(pkg = "com.example.app.test")
static class AliasForBasedSinglePackageComponentScanClass {
}
@SpringAppConfig(Number.class)
static class SpringAppConfigClass {
}
@Resource(name = "x")
static class ResourceHolder {
}
interface TransactionalService {
@Transactional
void doIt();
}
class TransactionalServiceImpl implements TransactionalService {
@Override
public void doIt() {
}
}
}