Prior to this change, request-scoped components having
@Resource-injected dependencies caused a memory leak in
DefaultListableBeanFactory#dependenciesForBeanMap.
Consider the following example:
@Component
@Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyComponent {
@Resource
private HttpServletRequest request;
// ...
}
The bean name for "MyComponent" will end up being
'scopedTarget.myComponent', which will become a key in
the #dependenciesForBeanMap structure.
On the first request, the injected HttpServletRequest bean will be a
proxy and will internally have a bean name of the form
"$Proxy10@1a3a2a52". This name will be added to the Set value associated
with the 'scopedTarget.myComponent' entry in #dependenciesForBeanMap.
On the second request, the process will repeat, but the injected
HttpServletRequest will be a different proxy instance, thus having a
different identity hex string, e.g. "$Proxy10@5eba06ff". This name will
also be added to the Set value associated with the
'scopedTarget.myComponent' entry in #dependenciesForBeanMap, and this
is the source of the leak: a new entry is added to the set on each
request but should be added only once.
This commit fixes the leak by introducing caching to
CommonAnnotationBeanPostProcessor#ResourceElement similar to that already
present in AutowiredAnnotationBeanPostProcessor#AutowiredFieldElement
and #AutowiredMethodElement. Essentially, each ResourceElement instance
now tracks whether it has been created, caches the ultimate value to be
injected and returns it eagerly if necessary. Besides solving the memory
leak, this has the side effect of avoiding unnecessary proxy creation.
This fix also explains clearly why injection into request-scoped
components using @Autowired never suffered this memory leak: because the
correct caching was already in place. Because @Resource is considerably
less-frequently used than @Autowired, and given that this particular
injection arrangement is relatively infrequent, it becomes
understandable how this bug has been present without being reported
since the introduction of @Resource support in Spring 2.5: developers
were unlikely to encounter it in the first place; and if they did, the
leak was minor enough (adding strings to a Set), that it could
potentially go unnoticed indefinitely depending on request volumes and
available memory.
Issue: SPR-9363
Backport-Issue: SPR-9176
Backport-Commit: f779c199ea
Prior to this change, Spring's @Async annotation support was tied to a
single AsyncTaskExecutor bean, meaning that all methods marked with
@Async were forced to use the same executor. This is an undesirable
limitation, given that certain methods may have different priorities,
etc. This leads to the need to (optionally) qualify which executor
should handle each method.
This is similar to the way that Spring's @Transactional annotation was
originally tied to a single PlatformTransactionManager, but in Spring
3.0 was enhanced to allow for a qualifier via the #value attribute, e.g.
@Transactional(ptm1)
public void m() { ... }
where ptm1 is either the name of a PlatformTransactionManager bean or
a qualifier value associated with a PlatformTransactionManager bean,
e.g. via the <qualifier> element in XML or the @Qualifier annotation.
This commit introduces the same approach to @Async and its relationship
to underlying executor beans. As always, the following syntax remains
supported
@Async
public void m() { ... }
indicating that calls to #m will be delegated to the default executor,
i.e. the executor provided to
<task:annotation-driven executor=.../>
or the executor specified when authoring a @Configuration class that
implements AsyncConfigurer and its #getAsyncExecutor method.
However, it now also possible to qualify which executor should be used
on a method-by-method basis, e.g.
@Async(e1)
public void m() { ... }
indicating that calls to #m will be delegated to the executor bean
named or otherwise qualified as e1. Unlike the default executor
which is specified up front at configuration time as described above,
the e1 executor bean is looked up within the container on the first
execution of #m and then cached in association with that method for the
lifetime of the container.
Class-level use of Async#value behaves as expected, indicating that all
methods within the annotated class should be executed with the named
executor. In the case of both method- and class-level annotations, any
method-level #value overrides any class level #value.
This commit introduces the following major changes:
- Add @Async#value attribute for executor qualification
- Introduce AsyncExecutionAspectSupport as a common base class for
both MethodInterceptor- and AspectJ-based async aspects. This base
class provides common structure for specifying the default executor
(#setExecutor) as well as logic for determining (and caching) which
executor should execute a given method (#determineAsyncExecutor) and
an abstract method to allow subclasses to provide specific strategies
for executor qualification (#getExecutorQualifier).
- Introduce AnnotationAsyncExecutionInterceptor as a specialization of
the existing AsyncExecutionInterceptor to allow for introspection of
the @Async annotation and its #value attribute for a given method.
Note that this new subclass was necessary for packaging reasons -
the original AsyncExecutionInterceptor lives in
org.springframework.aop and therefore does not have visibility to
the @Async annotation in org.springframework.scheduling.annotation.
This new subclass replaces usage of AsyncExecutionInterceptor
throughout the framework, though the latter remains usable and
undeprecated for compatibility with any existing third-party
extensions.
- Add documentation to spring-task-3.2.xsd and reference manual
explaining @Async executor qualification
- Add tests covering all new functionality
Note that the public API of all affected components remains backward-
compatible.
Issue: SPR-9443
Backport-Issue: SPR-6847
Backport-Commit: ed0576c181
In anticipation of substantive changes required to implement @Async
executor qualification, the following updates have been made to the
components and infrastructure supporting @Async functionality:
- Fix trailing whitespace and indentation errors
- Fix generics warnings
- Add Javadoc where missing, update to use {@code} tags, etc.
- Avoid NPE in AopUtils#canApply
- Organize imports to follow conventions
- Remove System.out.println statements from tests
- Correct various punctuation and grammar problems
Issue: SPR-9443
Backport-Issue: SPR-6847
Backport-Commit: 3fb11870d9
Prior to this change, AbstractApplicationContext#setParent replaced the
child context's Environment with the parent's Environment if available.
This has the negative effect of potentially changing the type of the
child context's Environment, and in any case causes property sources
added directly against the child environment to be ignored. This
situation could easily occur if a WebApplicationContext child had a
non-web ApplicationContext set as its parent. In this case the parent
Environment type would (likely) be StandardEnvironment, while the child
Environment type would (likely) be StandardServletEnvironment. By
directly inheriting the parent environment, critical property sources
such as ServletContextPropertySource are lost entirely.
This commit introduces the concept of merging an environment through
the new ConfigurableEnvironment#merge method. Instead of replacing the
child's environment with the parent's,
AbstractApplicationContext#setParent now merges property sources as
well as active and default profile names from the parent into the
child. In this way, distinct environment objects are maintained with
specific types and property sources preserved. See #merge Javadoc for
additional details.
Issue: SPR-9446
Backport-Issue: SPR-9444, SPR-9439
Backport-Commit: 9fcfd7e827
It is now advised that destroyMethod="shutdown" should be used
on @Bean methods returning an ExecutorService.
Backport-Issue: SPR-9280
Backport-Commit: 6da03a61b22696283c2c5c79f8f88b5c36480560
Changes in commit 41ade68b50 introduced
a regression causing all but the first location in the
@PropertySource#value array to be ignored during ${...} placeholder
resolution. This change ensures that all locations are processed and
replaced as expected.
Issue: SPR-9133
Backport-Issue: SPR-9127
Backport-Commit: 4df2a14b13
Previously, a user could specify an empty array of resource locations
to the @PropertySource annotation, which amounts to a meaningless no-op.
ConfigurationClassParser now throws IllegalArgumentException upon
encountering any such misconfiguration.
Prior to this commit, specifying a named @PropertySource with multiple
values would not work as expected. e.g.:
@PropertySource(
name = "ps",
value = { "classpath:a.properties", "classpath:b.properties" })
In this scenario, the implementation would register a.properties with
the name "ps", and subsequently register b.properties with the name
"ps", overwriting the entry for a.properties.
To fix this behavior, a CompositePropertySource type has been introduced
which accepts a single name and a set of PropertySource objects to
iterate over. ConfigurationClassParser's @PropertySource parsing routine
has been updated to use this composite approach when necessary, i.e.
when both an explicit name and more than one location have been
specified.
Note that if no explicit name is specified, the generated property
source names are enough to distinguish the instances and avoid
overwriting each other; this is why the composite wrapper is not used
in these cases.
Issue: SPR-9127
Since the introduction of the AnnotationConfig(Web)ApplicationContext
types in Spring 3.0, it has been possible to specify a custom
bean name generation strategy via the #setBeanNameGenerator methods
available on each of these classes.
If specified, that BeanNameGenerator was delegated to the underlying
AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner. This
meant that any @Configuration classes registered or scanned directly
from the application context, e.g. via #register or #scan methods would
respect the custom name generation strategy as intended.
However, for @Configuration classes picked up via @Import or implicitly
registered due to being nested classes would not be aware of this
strategy, and would rather fall back to a hard-coded default
AnnotationBeanNameGenerator.
This change ensures consistent application of custom BeanNameGenerator
strategies in the following ways:
- Introduction of AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
singleton
If a custom BeanNameGenerator is specified via #setBeanNameGenerator
the AnnotationConfig* application contexts will, in addition to
delegating this object to the underlying reader and scanner, register
it as a singleton bean within the enclosing bean factory having the
constant name mentioned above.
ConfigurationClassPostProcessor now checks for the presence of this
singleton, falling back to a default AnnotationBeanNameGenerator if
not present. This singleton-based approach is necessary because it is
otherwise impossible to parameterize CCPP, given that it is
registered as a BeanDefinitionRegistryPostProcessor bean definition
in AnnotationConfigUtils#registerAnnotationConfigProcessors
- Introduction of ConfigurationClassPostProcessor#setBeanNameGenerator
As detailed in the Javadoc for this new method, this allows for
customizing the BeanNameGenerator via XML by dropping down to direct
registration of CCPP as a <bean> instead of using
<context:annotation-config> to enable @Configuration class
processing.
- Smarter defaulting for @ComponentScan#beanNameGenerator
Previously, @ComponentScan's #beanNameGenerator attribute had a
default value of AnnotationBeanNameGenerator. The value is now the
BeanNameGenerator interface itself, indicating that the scanner
dedicated to processing each @ComponentScan should fall back to an
inherited generator, i.e. the one originally specified against the
application context, or the original default provided by
ConfigurationClassPostProcessor. This means that name generation
strategies will be consistent with a single point of configuration,
but that individual @ComponentScan declarations may still customize
the strategy for the beans that are picked up by that particular
scanning.
Issue: SPR-9124
Prior to this commit, and based on earlier changes supporting SPR-9023,
ConfigurationClassBeanDefinitionReader employed a simplistic strategy
for extracting the 'value' attribute (if any) from @Configuration in
order to determine the bean name for imported and nested configuration
classes. An example case follows:
@Configuration("myConfig")
public class AppConfig { ... }
This approach is too simplistic however, given that it is possible in
'configuration lite' mode to specify a @Component-annotated class with
@Bean methods, e.g.
@Component("myConfig")
public class AppConfig {
@Bean
public Foo foo() { ... }
}
In this case, it's the 'value' attribute of @Component, not
@Configuration, that should be consulted for the bean name. Or indeed if
it were any other stereotype annotation meta-annotated with @Component,
the value attribute should respected.
This kind of sophisticated discovery is exactly what
AnnotationBeanNameGenerator was designed to do, and
ConfigurationClassBeanDefinitionReader now uses it in favor of the
custom approach described above.
To enable this refactoring, nested and imported configuration classes
are no longer registered as GenericBeanDefinition, but rather as
AnnotatedGenericBeanDefinition given that AnnotationBeanNameGenerator
falls back to a generic strategy unless the bean definition in question
is assignable to AnnotatedBeanDefinition.
A new constructor accepting AnnotationMetadata
has been added to AnnotatedGenericBeanDefinition in order to support
the ASM-based approach in use by configuration class processing. Javadoc
has been updated for both AnnotatedGenericBeanDefinition and its now
very similar cousin ScannedGenericBeanDefinition to make clear the
semantics and intention of these two variants.
Issue: SPR-9023
Prior to this commit, an infinite recursion would occur if a
@Configuration class were nested within its superclass, e.g.
abstract class Parent {
@Configuration
static class Child extends Parent { ... }
}
This is because the processing of the nested class automatically
checks the superclass hierarchy for certain reasons, and each
superclass is in turn checked for nested @Configuration classes.
The ConfigurationClassParser implementation now prevents this by
keeping track of known superclasses, i.e. once a superclass has been
processed, it is never again checked for nested classes, etc.
Issue: SPR-8955
Eclipse allows autoboxing on type inference; Sun javac does not. This
means that variables assigned from calls to
AnnotationAttributes#getNumber should consistently use object wrappers
as opposed to number primitives. There was only one such instance
anyway, and has now been updated accordingly.
- Drop 'expectedType' parameter from #getClass and #getEnum methods and
rely on compiler inference based on type of assigned variable, e.g.
public @interface Example {
Color color();
Class<? extends UserType> userType();
int order() default 0;
}
AnnotationAttributes example =
AnnotationUtils.getAnnotationAttributes(Example.class, true, true);
Color color = example.getEnum("color");
Class<? extends UserType> userType = example.getClass("userType");
or in cases where there is no variable assignment (and thus no
inference possible), use explicit generic type, e.g.
bean.setColor(example.<Color>getEnum("color"));
- Rename #get{Int=>Number} and update return type from int to
<N extends Number>, allowing invocations such as:
int order = example.getNumber("order");
These changes reduce the overall number of methods exposed by
AnnotationAttributes, while at the same time providing comprehensive
access to all possible annotation attribute types -- that is, instead of
requiring explicit #getInt, #getFloat, #getDouble methods, the
single #getNumber method is capabable of handling them all, and without
any casting required. And the obvious additional benefit is more concise
invocation as no redundant 'expectedType' parameters are required.
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