Support annotation attribute aliases and overrides via @AliasFor
This commit introduces first-class support for aliases for annotation
attributes. Specifically, this commit introduces a new @AliasFor
annotation that can be used to declare a pair of aliased attributes
within a single annotation or an alias from an attribute in a custom
composed annotation to an attribute in a meta-annotation.
To support @AliasFor within annotation instances, AnnotationUtils has
been overhauled to "synthesize" any annotations returned by "get" and
"find" searches. A SynthesizedAnnotation is an annotation that is
wrapped in a JDK dynamic proxy which provides run-time support for
@AliasFor semantics. SynthesizedAnnotationInvocationHandler is the
actual handler behind the proxy.
In addition, the contract for @AliasFor is fully validated, and an
AnnotationConfigurationException is thrown in case invalid
configuration is detected.
For example, @ContextConfiguration from the spring-test module is now
declared as follows:
public @interface ContextConfiguration {
@AliasFor(attribute = "locations")
String[] value() default {};
@AliasFor(attribute = "value")
String[] locations() default {};
// ...
}
The following annotations and their related support classes have been
modified to use @AliasFor.
- @ManagedResource
- @ContextConfiguration
- @ActiveProfiles
- @TestExecutionListeners
- @TestPropertySource
- @Sql
- @ControllerAdvice
- @RequestMapping
Similarly, support for AnnotationAttributes has been reworked to
support @AliasFor as well. This allows for fine-grained control over
exactly which attributes are overridden within an annotation hierarchy.
In fact, it is now possible to declare an alias for the 'value'
attribute of a meta-annotation.
For example, given the revised declaration of @ContextConfiguration
above, one can now develop a composed annotation with a custom
attribute override as follows.
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(
annotation = ContextConfiguration.class,
attribute = "locations"
)
String[] xmlFiles();
// ...
}
Consequently, the following are functionally equivalent.
- @MyTestConfig(xmlFiles = "test.xml")
- @ContextConfiguration("test.xml")
- @ContextConfiguration(locations = "test.xml").
Issue: SPR-11512, SPR-11513
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
@@ -185,7 +186,7 @@ public class TestExecutionListenersTests {
|
||||
testContextManager.getTestExecutionListeners().size());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test(expected = AnnotationConfigurationException.class)
|
||||
public void listenersAndValueAttributesDeclared() {
|
||||
new TestContextManager(DuplicateListenersConfigTestCase.class);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,20 @@
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.mockito.BDDMockito;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
@@ -40,6 +45,9 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||
|
||||
private final TestContext testContext = mock(TestContext.class);
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
|
||||
@Test
|
||||
public void missingValueAndScriptsAtClassLevel() throws Exception {
|
||||
@@ -65,7 +73,14 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||
BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz);
|
||||
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("foo"));
|
||||
|
||||
assertExceptionContains("Only one declaration of SQL script paths is permitted");
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(either(
|
||||
containsString("attribute [value] and its alias [scripts]")).or(
|
||||
containsString("attribute [scripts] and its alias [value]")));
|
||||
exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or(
|
||||
containsString("values of [{bar}] and [{foo}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
listener.beforeTestMethod(testContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ActiveProfilesResolver;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -190,7 +191,7 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
|
||||
/**
|
||||
* @since 4.0
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test(expected = AnnotationConfigurationException.class)
|
||||
public void resolveActiveProfilesWithConflictingProfilesAndValue() {
|
||||
resolveActiveProfiles(ConflictingProfilesAndValueTestCase.class);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,12 +18,16 @@ package org.springframework.test.context.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.ContextLoader;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.context.support.ContextLoaderUtils.*;
|
||||
|
||||
@@ -35,6 +39,10 @@ import static org.springframework.test.context.support.ContextLoaderUtils.*;
|
||||
*/
|
||||
public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractContextConfigurationUtilsTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
|
||||
private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) {
|
||||
assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY,
|
||||
ContextLoader.class, false);
|
||||
@@ -55,8 +63,17 @@ public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractCont
|
||||
AnnotationConfigContextLoader.class, true);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test
|
||||
public void resolveConfigAttributesWithConflictingLocations() {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString(ConflictingLocations.class.getName()));
|
||||
exception.expectMessage(either(
|
||||
containsString("attribute [value] and its alias [locations]")).or(
|
||||
containsString("attribute [locations] and its alias [value]")));
|
||||
exception.expectMessage(either(
|
||||
containsString("values of [{x}] and [{y}]")).or(
|
||||
containsString("values of [{y}] and [{x}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
resolveContextConfigurationAttributes(ConflictingLocations.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
@@ -81,7 +82,7 @@ public class TestPropertySourceUtilsTests {
|
||||
|
||||
@Test
|
||||
public void locationsAndValueAttributes() {
|
||||
expectedException.expect(IllegalStateException.class);
|
||||
expectedException.expect(AnnotationConfigurationException.class);
|
||||
buildMergedTestPropertySources(LocationsAndValuePropertySources.class);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user