Description
Sam Brannen opened SPR-11393 and commented
Background
Java supports a special annotation attribute named value
that can be used without being named if an annotation is declared with a single attribute. Due to the convenience it provides, the value
attribute is used frequently in annotations across the Spring Framework. However, there are some downsides to making an attribute available only via the value
attribute name:
- Readability: when the
value
attribute is used without being named (i.e., as a single attribute), the readability is enhanced due to the reduction of clutter, but if multiple attributes are declared, the readability and usability of the code base suffers. Seeing "value=..." repeated across annotations makes the intent of the code unclear, especially to newcomers to the framework. - Composability: Spring Framework 4.0 (and previous versions of the framework to a lesser extent) provides extensive support for composed annotations (i.e., annotations composed by multiple meta-annotations) and meta-annotation attribute overriding in such composed annotations; however, the
value
attribute is (for good reasons) not supported as a candidate for overriding.
Example: Multiple Attribute Declarations
Consider the following annotated handler method from a Spring MVC controller for JUG events.
@RequestMapping("/events")
public String list(Model model) {
model.addAttribute("events", this.eventService.findAllEvents());
return "events/list";
}
In the above example, only the path for the request mapping is declared as an annotation attribute. In such use cases, the use of the unnamed value
attribute is ideal. But how does the code look if multiple attributes must be declared?
@RequestMapping(value = "/events", method = GET)
public String list(Model model) {
/* body */
}
In the above example, supplying the path on its own is insufficient for the use case at hand. Thus, both the path and method must be supplied. Since the path can only be specified via the value
attribute, the code becomes unclear. What does "value" mean in this context?
Wouldn't it be better to be able to be very explicit?
@RequestMapping(path = "/events", method = GET)
public String list(Model model) {
/* body */
}
The above example introduces an alias for the value
attribute. By specifying the path via a path
attribute, the meaning of the code becomes clear, even to readers who are not familiar with Spring MVC. Although this annotation is supported in both Servlet and Portlet environments, it has been decided to name the alias path
since the Servlet use case is more common.
Example: Meta-annotation Attribute Overrides
Consider the @ContextConfiguration
from the Spring TestContext Framework (TCF). When it was introduced in Spring 2.5, it only supported a locations
attribute for specifying the resource locations to use for loading an ApplicationContext
. After observing common use cases for this annotation in the developer community it became apparent that developers typically only need to declare the locations and none of the other available attributes. So in Spring 3.0, the value
attribute was introduced in @ContextConfiguration
as an alias for the existing locations
attribute.
The fact that both the value
and locations
attributes are supported in @ContextConfiguration
not only overcomes the aforementioned readability issue, but it also allows for @ContextConfiguration
to be used as a meta-annotation on a composed annotation with attribute overrides for the locations.
@Transactional
@ContextConfiguration
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionalTestConfig {
String[] locations() default {};
}
@TransactionalTestConfig(locations = "/test-config.xml")
public class RepositoryTests {
/* test body */
}
The above example demonstrates how to override the locations
attribute of @ContextConfiguration
in a custom composed annotation (@TransactionalTestConfig
).
Without the locations
alias, this overriding would not be possible.
Deliverables
- Introduce a set of common test fixtures that assist in ensuring that annotation attribute look-up is tested consistently across modules.
- Special consideration should be taken with regard to component names.
- The framework provides support for determining the name of an annotated component via special handling of the
value
attribute in@Component
classes or any class meta-annotated with@Component
; however, it may prove useful to declare an alias for component names (e.g.,componentName
) in order to allow the name of a component to be specified via a custom composed annotation without the ambiguity of thevalue
attribute. - Component annotations:
@Component
@Service
@Repository
@Controller
@ControllerAdvice
@RestController
@Configuration
- The framework provides support for determining the name of an annotated component via special handling of the
- For each annotation in the Candidate Annotations section below (which has not been rejected):
- Ensure that the annotation has an alias for the
value
attribute with a meaningful name. - Ensure that the alias name is unique enough that it will not likely lead to naming collisions when multiple annotations are used to construct composed annotations.
- For example, something as generic as name should likely be avoided if possible.
- Review the Javadoc for the
value
attribute and its alias and revise as necessary in order to ensure clarity of purpose. - Ensure that all code in the framework that currently looks up the
value
of said annotation now uses the newly introduced functionality for looking up annotation attributes with aliases (see Introduce unified support for declaring and looking up annotation attribute aliases [SPR-11512] #16137).
- Ensure that the annotation has an alias for the
Candidate Annotations
The tables below contain annotations in the Spring Framework that fall into one of two categories:
- The annotation has a
value
attribute in addition to other attributes. - The annotation only has a
value
attribute (i.e., no additional attributes), but it might still benefit from having an alias so that it can be overridden in custom composed annotations (e.g., using the simple name-based convention).
Annotations with a value
attribute and other attributes
module | Annotation | Alias Exists? | Alias Name |
---|---|---|---|
spring-context |
@Cacheable |
(/) | cacheNames |
spring-context |
@CacheEvict |
(/) | cacheNames |
spring-context |
@CachePut |
(/) | cacheNames |
spring-context |
@ComponentScan |
(/) | basePackages |
spring-context |
@ComponentScan.Filter |
(/) | classes |
spring-context |
@ImportResource |
(/) | locations |
spring-context |
@ManagedResource |
(/) | objectName |
spring-context |
@Scope |
(/) | name |
spring-messaging |
@Header |
(/) | name |
spring-messaging |
@Payload |
(/) | expression |
spring-messaging |
@SendToUser |
(/) | destinations |
spring-test |
@ActiveProfiles |
(/) | profiles |
spring-test |
@ContextConfiguration |
(/) | locations |
spring-test |
@Sql |
(/) | scripts |
spring-test |
@TestExecutionListeners |
(/) | listeners |
spring-test |
@TestPropertySource |
(/) | locations |
spring-tx |
@Transactional |
(/) | transactionManager |
spring-web |
@ControllerAdvice |
(/) | basePackages |
spring-web |
@CookieValue |
(/) | name |
spring-web |
@CrossOrigin |
(/) | origins |
spring-web |
@MatrixVariable |
(/) | name |
spring-web |
@RequestHeader |
(/) | name |
spring-web |
@RequestMapping |
(/) | path |
spring-web |
@RequestParam |
(/) | name |
spring-web |
@RequestPart |
(/) | name |
spring-web |
@ResponseStatus |
(/) | code |
spring-web |
@SessionAttributes |
(/) | names |
spring-webmvc-portlet |
@ActionMapping |
(/) | name |
spring-webmvc-portlet |
@RenderMapping |
(/) | windowState |
Rejected Candidates
The following table contains annotations that in fact have a value
attribute (and typically no other attributes) but have been rejected as candidates for receiving aliases.
Note, however, that just because an annotation has been rejected as a candidate for receiving a value-alias, this does not mean that the value
attribute will not be able to be used in composed annotations. On the contrary, once the work for #16138 is complete, there will be an annotation-based mechanism for overriding value
annotation attributes in meta-annotations.
module | Annotation |
---|---|
spring-beans |
@Qualifier |
spring-context |
@Async |
spring-context |
@DependsOn |
spring-context |
@Lazy |
spring-context |
@Profile |
spring-context |
@Validated |
spring-core |
@Order |
spring-messaging |
@DestinationVariable |
spring-messaging |
@MessageExceptionHandler |
spring-messaging |
@MessageMapping |
spring-messaging |
@SendTo |
spring-messaging |
@SubscribeMapping |
spring-test |
@BootstrapWith |
spring-test |
@Repeat |
spring-test |
@Rollback |
spring-test |
@WebAppConfiguration |
spring-webmvc-portlet |
@ResourceMapping |
Affects: 4.0 GA
Issue Links:
- Introduce unified support for declaring and looking up annotation attribute aliases [SPR-11512] #16137 Introduce unified support for declaring and looking up annotation attribute aliases ("depends on")
- Document Spring Annotation Programming Model in the Wiki [SPR-11515] #16140 Document Spring Annotation Programming Model in the Wiki ("is depended on by")
- Allow meta-annotations to override attributes from their parent [SPR-10181] #14814 Allow meta-annotations to override attributes from their parent
- Introduce unified support for declaring and looking up annotation attribute aliases [SPR-11512] #16137 Introduce unified support for declaring and looking up annotation attribute aliases
- Introduce support for explicit annotation attribute overrides [SPR-11513] #16138 Introduce support for explicit annotation attribute overrides
- Support meta-annotation attribute overrides in the TestContext framework [SPR-11038] #15666 Support meta-annotation attribute overrides in the TestContext framework
- Introduce a comprehensive programming model for meta-annotation support [SPR-11511] #16136 Introduce a comprehensive programming model for meta-annotation support
- Regression: AnnotationScopeMetadataResolver fails to resolve VaadinView [SPR-13239] #17830 Regression: AnnotationScopeMetadataResolver fails to resolve VaadinView
- Package tangle in org.springframework.core [SPR-13153] #17744 Package tangle in org.springframework.core
- AnnotationUtils.findAnnotation() should support arbitrary levels of meta-annotations [SPR-11448] #16074 AnnotationUtils.findAnnotation() should support arbitrary levels of meta-annotations
1 votes, 11 watchers