Uses of AnnotationMetadata#getAnnotationAttributes throughout the
framework have been updated to use the new AnnotationAttributes API in
order to take advantage of the more concise, expressive and type-safe
methods there.
All changes are binary compatible to the 3.1.0 public API, save
the exception below.
A minor binary compatibility issue has been introduced in
AbstractCachingConfiguration, AbstractAsyncConfiguration and
AbstractTransactionManagementConfiguration when updating their
protected Map<String, Object> fields representing annotation attributes
to use the new AnnotationAttributes API. This is a negligible breakage,
however, as the likelilhood of users subclassing these types is very
low, the classes have only been in existence for a short time (further
reducing the likelihood), and it is a source-compatible change given
that AnnotationAttributes is assignable to Map<String, Object>.
Background
Spring 3.1 introduced the @ComponentScan annotation, which can accept
an optional array of include and/or exclude @Filter annotations, e.g.
@ComponentScan(
basePackages = "com.acme.app",
includeFilters = { @Filter(MyStereotype.class), ... }
)
@Configuration
public class AppConfig { ... }
@ComponentScan and other annotations related to @Configuration class
processing such as @Import, @ImportResource and the @Enable*
annotations are parsed using reflection in certain code paths, e.g.
when registered directly against AnnotationConfigApplicationContext,
and via ASM in other code paths, e.g. when a @Configuration class is
discovered via an XML bean definition or when included via the
@Import annotation.
The ASM-based approach is designed to avoid premature classloading of
user types and is instrumental in providing tooling support (STS, etc).
Prior to this commit, the ASM-based routines for reading annotation
attributes were unable to recurse into nested annotations, such as in
the @Filter example above. Prior to Spring 3.1 this was not a problem,
because prior to @ComponentScan, there were no cases of nested
annotations in the framework.
This limitation manifested itself in cases where users encounter
the ASM-based annotation parsing code paths AND declare
@ComponentScan annotations with explicit nested @Filter annotations.
In these cases, the 'includeFilters' and 'excludeFilters' attributes
are simply empty where they should be populated, causing the framework
to ignore the filter directives and provide incorrect results from
component scanning.
The purpose of this change then, is to introduce the capability on the
ASM side to recurse into nested annotations and annotation arrays. The
challenge in doing so is that the nested annotations themselves cannot
be realized as annotation instances, so must be represented as a
nested Map (or, as described below, the new AnnotationAttributes type).
Furthermore, the reflection-based annotation parsing must also be
updated to treat nested annotations in a similar fashion; even though
the reflection-based approach has no problem accessing nested
annotations (it just works out of the box), for substitutability
against the AnnotationMetadata SPI, both ASM- and reflection-based
implementations should return the same results in any case. Therefore,
the reflection-based StandardAnnotationMetadata has also been updated
with an optional 'nestedAnnotationsAsMap' constructor argument that is
false by default to preserve compatibility in the rare case that
StandardAnnotationMetadata is being used outside the core framework.
Within the framework, all uses of StandardAnnotationMetadata have been
updated to set this new flag to true, meaning that nested annotation
results will be consistent regardless the parsing approach used.
Spr9031Tests corners this bug and demonstrates that nested @Filter
annotations can be parsed and read in both the ASM- and
reflection-based paths.
Major changes
- AnnotationAttributes has been introduced as a concrete
LinkedHashMap<String, Object> to be used anywhere annotation
attributes are accessed, providing error reporting on attribute
lookup and convenient type-safe access to common annotation types
such as String, String[], boolean, int, and nested annotation and
annotation arrays, with the latter two also returned as
AnnotationAttributes instances.
- AnnotationUtils#getAnnotationAttributes methods now return
AnnotationAttributes instances, even though for binary compatibility
the signatures of these methods have been preserved as returning
Map<String, Object>.
- AnnotationAttributes#forMap provides a convenient mechanism for
adapting any Map<String, Object> into an AnnotationAttributes
instance. In the case that the Map is already actually of
type AnnotationAttributes, it is simply casted and returned.
Otherwise, the map is supplied to the AnnotationAttributes(Map)
constructor and wrapped in common collections style.
- The protected MetadataUtils#attributesFor(Metadata, Class) provides
further convenience in the many locations throughout the
.context.annotation packagage that depend on annotation attribute
introspection.
- ASM-based core.type.classreading package reworked
Specifically, AnnotationAttributesReadingVisitor has been enhanced to
support recursive reading of annotations and annotation arrays, for
example in @ComponentScan's nested array of @Filter annotations,
ensuring that nested AnnotationAttributes objects are populated as
described above.
AnnotationAttributesReadingVisitor has also been refactored for
clarity, being broken up into several additional ASM
AnnotationVisitor implementations. Given that all types are
package-private here, these changes represent no risk to binary
compatibility.
- Reflection-based StandardAnnotationMetadata updated
As described above, the 'nestedAnnotationsAsMap' constructor argument
has been added, and all framework-internal uses of this class have
been updated to set this flag to true.
Issue: SPR-7979, SPR-8719, SPR-9031
Prior to this commit, @Configuration classes included via @Import (or
via automatic registration of nested configuration classes) would
always be registered with a generated bean name, regardless of whether
the user had specified a 'value' indicating a customized bean name, e.g.
@Configuration("myConfig")
public class AppConfig { ... }
Now this bean name is propagated as intended in all cases, meaning that
in the example above, the resulting bean definition of type AppConfig
will be named "myConfig" regardless how it was registered with the
container -- directly against the application context, via component
scanning, via @Import, or via automatic registration of nested
configuration classes.
Issue: SPR-9023
Prior to this change, the spring-cache XSD allowed a 'key-generator'
attribute, but it was not actually parsed by AnnotationDrivenCacheBDP.
This commit adds the parsing logic as originally intended and the test
to prove it.
Issue: SPR-8939
Prior to this change, the caching reference docs referred to
'root.params', whereas the actual naming should be 'root.args'. This
naming was also reflected in the "#p" syntax for specifying method args.
This change updates the documentation to refer to 'root.args' properly
and also adds "#a" syntax for specifying method arguments more
intuitively. Note that "#p" syntax remains in place as an alias for
backward compatibility.
Issue: SPR-8938
Prior to this change, roughly 5% (~300 out of 6000+) of files under the
source tree had CRLF line endings as opposed to the majority which have
LF endings.
This change normalizes these files to LF for consistency going forward.
Command used:
$ git ls-files | xargs file | grep CRLF | cut -d":" -f1 | xargs dos2unix
Issue: SPR-5608
Prior to this change, ScheduledAnnotationBeanPostProcessor found any
@Scheduled methods against the ultimate targetClass for a given bean
and then attempted to invoke that method against the bean instance. In
cases where the bean instance was in fact a JDK proxy, this attempt
would fail because the proxy is not an instance of the target class.
Now SABPP still attempts to find @Scheduled methods against the target
class, but subsequently checks to see if the bean is a JDK proxy, and if
so attempts to find the corresponding method on the proxy itself. If it
cannot be found (e.g. the @Scheduled method was declared only at the
concrete class level), an appropriate exception is thrown, explaining to
the users their options: (a) use proxyTargetClass=true and go with
subclass proxies which won't have this problem, or (b) pull the
@Scheduled method up into an interface.
Issue: SPR-8651
Prior to this change, an assumption was made in
AbstractAutowireCapableBeanFactory that any factory-method would have
zero parameters. This may not be the case in @Bean methods.
We now look for the factory-method by name in a more flexible fashion
that accomodates the possibility of method parameters.
There remains at least one edge cases here where things could still fail,
for example a @Configuration class could have two FactoryBean-returning
methods of the same name, but each with different generic FactoryBean
types and different parameter lists. In this case, the implementation
may infer and return the wrong object type, as it currently returns
the first match for the given factory-method name. The complexity cost
of ensuring that this never happens is not likely worth the trouble
given the very low likelihood of such an arrangement.
Issue: SPR-8762
In order to determine why Ehcache classloading errors occur after
upgrading to 4.2.0.Final.
To demonstrate this error, uncomment the 4.2.0.Final dependency in
ivy.xml and run `ant test` within the .context module.
Prior to this change, to specify two or more annotation include/exclude
filters, one would declare @ComponentScan as follows:
@ComponentScan(basePackages="example.scannable",
useDefaultFilters=false,
includeFilters={
@Filter(MyStereotype.class),
@Filter(MyComponent.class)
})
This was because @Filter's 'value' attribute accepted exactly one
argument.
Now, any given @Filter may accept one or more value arguments, allowing
for more concise @ComponentScan declarations:
@ComponentScan(basePackages="example.scannable",
useDefaultFilters=false,
includeFilters=@Filter({MyStereotype.class, MyComponent.class}))
Supplying multiple arguments in this way assumes that they are the same
type of filter, e.g. ANNOTATION, ASSIGNABLE_TYPE, or CUSTOM. To declare
multiple *different* types of filters, multiple @Filter annotations are
still required, e.g.:
@ComponentScan(
includeFilters={
@Filter(type=ANNOTATION, value=MyStereotype.class),
@Filter(type=ASSIGNABLE_TYPE, value={Foo.class, Bar.class})
})
Note that specifying zero arguments, e.g. @Filter({}) is nonsensical; it
will have no effect on component scanning, but does not raise an error.
Issue: SPR-8881
For clarity, add @Target({}) to definition of @Filter, constraining it
to complex annotation composition only; i.e. it cannot be placed on
any element, only within annotations, e.g.
@ComponentScan(includeFilters=@Filter(...))
is legal, while
@Filter
public class MyType { }
is not.
Also, widen @Retention from SOURCE to RUNTIME, even though it is not
technically necessary, as all parsing of @Filter annotations happens via
ASM, i.e. at the source level. This change is made primarily for
consistency (@ComponentScan's Retention is RUNTIME) and in avoidance of
potential confusion or surprise on the part of those casually browsing
the code.
Prior to this change, a @Configuration classes that @ComponentScan
themselves would result in a ConflictingBeanDefinitionException.
For example:
package com.foo.config;
@Configuration
@ComponentScan("com.foo");
public class AppConfig {
// ...
}
This resulted in a ConflictingBeanDefinitionException that users have
typically worked around in the following fashion:
package com.foo.config;
@Configuration
@ComponentScan(basePackages="com.foo",
excludeFilters=@Filter(value=ANNOTATION_TYPE, type=Configuration.class);
public class AppConfig {
// ...
}
This is obviously more verbose and cumbersome than would be desirable,
and furthermore potentially too constraining as it prohibits the ability
to include other legitimate @Configuration classes via scanning.
The exception was being thrown because of a logic problem in
ClassPathBeanDefinitionScanner. The bean definition for AppConfig gets
registered once by the user (e.g. when constructing an
AnnotationConfigApplicationContext), then again when performing the
component scan for 'com.foo'. Prior to this change,
ClassPathBeanDefinitionScanner's #isCompatible returned false if the new
bean definition was anything other than an AnnotatedBeanDefinition. The
intention of this check is really to see whether the new bean definition
is a *scanned* bean definition, i.e. the result of a component-scanning
operation. If so, then it becomes safe to assume that the original bean
definition is the one that should be kept, as it is the one explicitly
registered by the user.
Therefore, the fix is as simple as narrowing the instanceof check from
AnnotatedBeanDefinition to its ScannedGenericBeanDefinition subtype.
Note that this commit partially reverts changes introduced in SPR-8307
that explicitly caught ConflictingBeanDefinitionExceptions when
processing recursive @ComponentScan definitions, and rethrew as a
"CircularComponentScanException. With the changes in this commit,
such CBDEs will no longer occur, obviating the need for this check and
for this custom exception type altogether.
Issue: SPR-8808, SPR-8307
Equivalent to <context:spring-configured/>.
Also update @EnableLoadTimeWeaving Javadoc and spring-configured XSD
documentation to reflect.
Issue: SPR-7888