Skip to content

Commit e95bd9e

Browse files
author
Phillip Webb
committed
Add @PropertySources and ignoreResourceNotFound
Support repeatable @propertysource annotations in Java 8 and add @PropertySources container annotation for Java 6/7. Also add an ignoreResourceNotFound attribute to @propertysource allowing missing property resources to be silently ignored. This commit also introduces some generally useful methods to AnnotationUtils for working with @repeatable annotations. Issue: SPR-8371
1 parent 8917821 commit e95bd9e

File tree

10 files changed

+363
-51
lines changed

10 files changed

+363
-51
lines changed

spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java

+36-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.util.Collections;
1920
import java.util.LinkedHashSet;
21+
import java.util.Map;
2022
import java.util.Set;
2123

2224
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
@@ -32,6 +34,7 @@
3234
import org.springframework.core.annotation.AnnotationAttributes;
3335
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3436
import org.springframework.core.type.AnnotatedTypeMetadata;
37+
import org.springframework.core.type.AnnotationMetadata;
3538
import org.springframework.util.ClassUtils;
3639

3740
/**
@@ -44,6 +47,7 @@
4447
* @author Mark Fisher
4548
* @author Juergen Hoeller
4649
* @author Chris Beams
50+
* @author Phillip Webb
4751
* @since 2.5
4852
* @see ContextAnnotationAutowireCandidateResolver
4953
* @see CommonAnnotationBeanPostProcessor
@@ -297,12 +301,40 @@ static BeanDefinitionHolder applyScopedProxyMode(
297301
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
298302
}
299303

300-
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annoClass) {
301-
return attributesFor(metadata, annoClass.getName());
304+
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annotationClass) {
305+
return attributesFor(metadata, annotationClass.getName());
302306
}
303307

304-
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annoClassName) {
305-
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
308+
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) {
309+
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false));
310+
}
311+
312+
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
313+
Class<?> containerClass, Class<?> annotationClass) {
314+
return attributesForRepeatable(metadata, containerClass.getName(), annotationClass.getName());
315+
}
316+
317+
@SuppressWarnings("unchecked")
318+
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
319+
String containerClassName, String annotationClassName) {
320+
Set<AnnotationAttributes> result = new LinkedHashSet<AnnotationAttributes>();
321+
322+
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));
323+
324+
Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
325+
if (container != null && container.containsKey("value")) {
326+
for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
327+
addAttributesIfNotNull(result, containedAttributes);
328+
}
329+
}
330+
return Collections.unmodifiableSet(result);
331+
}
332+
333+
private static void addAttributesIfNotNull(Set<AnnotationAttributes> result,
334+
Map<String, Object> attributes) {
335+
if (attributes != null) {
336+
result.add(AnnotationAttributes.fromMap(attributes));
337+
}
306338
}
307339

308340
}

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

+44-23
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.io.FileNotFoundException;
1920
import java.io.IOException;
2021
import java.util.ArrayList;
2122
import java.util.Collection;
@@ -55,6 +56,7 @@
5556
import org.springframework.core.env.CompositePropertySource;
5657
import org.springframework.core.env.Environment;
5758
import org.springframework.core.env.PropertySource;
59+
import org.springframework.core.io.Resource;
5860
import org.springframework.core.io.ResourceLoader;
5961
import org.springframework.core.io.support.ResourcePropertySource;
6062
import org.springframework.core.type.AnnotationMetadata;
@@ -63,6 +65,8 @@
6365
import org.springframework.core.type.classreading.MetadataReader;
6466
import org.springframework.core.type.classreading.MetadataReaderFactory;
6567
import org.springframework.core.type.filter.AssignableTypeFilter;
68+
import org.springframework.util.LinkedMultiValueMap;
69+
import org.springframework.util.MultiValueMap;
6670
import org.springframework.util.StringUtils;
6771

6872
/**
@@ -112,7 +116,7 @@ public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder
112116

113117
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
114118

115-
private final Stack<PropertySource<?>> propertySources = new Stack<PropertySource<?>>();
119+
private final MultiValueMap<String, PropertySource<?>> propertySources = new LinkedMultiValueMap<String, PropertySource<?>>();
116120

117121
private final ImportStack importStack = new ImportStack();
118122

@@ -218,9 +222,9 @@ protected final SourceClass doProcessConfigurationClass(ConfigurationClass confi
218222
processMemberClasses(configClass, sourceClass);
219223

220224
// process any @PropertySource annotations
221-
AnnotationAttributes propertySource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(),
222-
org.springframework.context.annotation.PropertySource.class);
223-
if (propertySource != null) {
225+
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
226+
sourceClass.getMetadata(), PropertySources.class,
227+
org.springframework.context.annotation.PropertySource.class)) {
224228
processPropertySource(propertySource);
225229
}
226230

@@ -301,29 +305,29 @@ private void processMemberClasses(ConfigurationClass configClass, SourceClass so
301305
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
302306
String name = propertySource.getString("name");
303307
String[] locations = propertySource.getStringArray("value");
308+
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
304309
int locationCount = locations.length;
305310
if (locationCount == 0) {
306311
throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
307312
}
308-
for (int i = 0; i < locationCount; i++) {
309-
locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
310-
}
311-
ClassLoader classLoader = this.resourceLoader.getClassLoader();
312-
if (!StringUtils.hasText(name)) {
313-
for (String location : locations) {
314-
this.propertySources.push(new ResourcePropertySource(location, classLoader));
315-
}
316-
}
317-
else {
318-
if (locationCount == 1) {
319-
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
313+
for (String location : locations) {
314+
Resource resource = this.resourceLoader.getResource(
315+
this.environment.resolveRequiredPlaceholders(location));
316+
try {
317+
if (!StringUtils.hasText(name) || this.propertySources.containsKey(name)) {
318+
// We need to ensure unique names when the property source will
319+
// ultimately end up in a composite
320+
ResourcePropertySource ps = new ResourcePropertySource(resource);
321+
this.propertySources.add((StringUtils.hasText(name) ? name : ps.getName()), ps);
322+
}
323+
else {
324+
this.propertySources.add(name, new ResourcePropertySource(name, resource));
325+
}
320326
}
321-
else {
322-
CompositePropertySource ps = new CompositePropertySource(name);
323-
for (int i = locations.length - 1; i >= 0; i--) {
324-
ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader));
327+
catch (FileNotFoundException ex) {
328+
if (!ignoreResourceNotFound) {
329+
throw ex;
325330
}
326-
this.propertySources.push(ps);
327331
}
328332
}
329333
}
@@ -473,10 +477,27 @@ public Set<ConfigurationClass> getConfigurationClasses() {
473477
return this.configurationClasses;
474478
}
475479

476-
public Stack<PropertySource<?>> getPropertySources() {
477-
return this.propertySources;
480+
public List<PropertySource<?>> getPropertySources() {
481+
List<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
482+
for (Map.Entry<String, List<PropertySource<?>>> entry : this.propertySources.entrySet()) {
483+
propertySources.add(0, collatePropertySources(entry.getKey(), entry.getValue()));
484+
}
485+
return propertySources;
478486
}
479487

488+
private PropertySource<?> collatePropertySources(String name,
489+
List<PropertySource<?>> propertySources) {
490+
if (propertySources.size() == 1) {
491+
return propertySources.get(0);
492+
}
493+
CompositePropertySource result = new CompositePropertySource(name);
494+
for (int i = propertySources.size() - 1; i >= 0; i--) {
495+
result.addPropertySource(propertySources.get(i));
496+
}
497+
return result;
498+
}
499+
500+
480501
ImportRegistry getImportRegistry() {
481502
return this.importStack;
482503
}

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import java.util.HashSet;
2222
import java.util.LinkedHashMap;
2323
import java.util.LinkedHashSet;
24+
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Set;
26-
import java.util.Stack;
2727

2828
import org.apache.commons.logging.Log;
2929
import org.apache.commons.logging.LogFactory;
30-
3130
import org.springframework.beans.BeansException;
3231
import org.springframework.beans.PropertyValues;
3332
import org.springframework.beans.factory.BeanClassLoaderAware;
@@ -298,16 +297,16 @@ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
298297
parser.validate();
299298

300299
// Handle any @PropertySource annotations
301-
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
300+
List<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
302301
if (!parsedPropertySources.isEmpty()) {
303302
if (!(this.environment instanceof ConfigurableEnvironment)) {
304303
logger.warn("Ignoring @PropertySource annotations. " +
305304
"Reason: Environment must implement ConfigurableEnvironment");
306305
}
307306
else {
308307
MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
309-
while (!parsedPropertySources.isEmpty()) {
310-
envPropertySources.addLast(parsedPropertySources.pop());
308+
for (PropertySource<?> propertySource : parsedPropertySources) {
309+
envPropertySources.addLast(propertySource);
311310
}
312311
}
313312
}

spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Documented;
2020
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Repeatable;
2122
import java.lang.annotation.Retention;
2223
import java.lang.annotation.RetentionPolicy;
2324
import java.lang.annotation.Target;
@@ -132,7 +133,9 @@
132133
* Javadoc for details.
133134
*
134135
* @author Chris Beams
136+
* @author Phillip Webb
135137
* @since 3.1
138+
* @see PropertySources
136139
* @see Configuration
137140
* @see org.springframework.core.env.PropertySource
138141
* @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
@@ -141,6 +144,7 @@
141144
@Target(ElementType.TYPE)
142145
@Retention(RetentionPolicy.RUNTIME)
143146
@Documented
147+
@Repeatable(PropertySources.class)
144148
public @interface PropertySource {
145149

146150
/**
@@ -166,4 +170,13 @@
166170
*/
167171
String[] value();
168172

173+
/**
174+
* Indicate if failure to find the a {@link #value() property resource} should be
175+
* ignored.
176+
* <p>{@code true} is appropriate if the properties file is completely optional.
177+
* Default is {@code false}.
178+
* @since 4.0
179+
*/
180+
boolean ignoreResourceNotFound() default false;
181+
169182
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Container annotation that aggregates several {@link PropertySource} annotations.
27+
*
28+
* <p>Can be used natively, declaring several nested {@link PropertySource} annotations.
29+
* Can also be used in conjunction with Java 8's support for repeatable annotations,
30+
* where {@link PropertySource} can simply be declared several times on the same method,
31+
* implicitly generating this container annotation.
32+
*
33+
* @author Phillip Webb
34+
* @since 4.0
35+
* @see PropertySource
36+
*/
37+
@Target(ElementType.TYPE)
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Documented
40+
public @interface PropertySources {
41+
42+
PropertySource[] value();
43+
44+
}

spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @since 3.0
4343
* @see EnableScheduling
4444
* @see ScheduledAnnotationBeanPostProcessor
45+
* @see Schedules
4546
*/
4647
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
4748
@Retention(RetentionPolicy.RUNTIME)

spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

+2-11
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,8 @@ public Object postProcessAfterInitialization(final Object bean, String beanName)
117117
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
118118
@Override
119119
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
120-
Schedules schedules = AnnotationUtils.getAnnotation(method, Schedules.class);
121-
if (schedules != null) {
122-
for (Scheduled scheduled : schedules.value()) {
123-
processScheduled(scheduled, method, bean);
124-
}
125-
}
126-
else {
127-
Scheduled scheduled = AnnotationUtils.getAnnotation(method, Scheduled.class);
128-
if (scheduled != null) {
129-
processScheduled(scheduled, method, bean);
130-
}
120+
for (Scheduled scheduled : AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, Scheduled.class)) {
121+
processScheduled(scheduled, method, bean);
131122
}
132123
}
133124
});

0 commit comments

Comments
 (0)