提交 e95bd9e2 编写于 作者: P Phillip Webb

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
上级 8917821e
......@@ -16,7 +16,9 @@
package org.springframework.context.annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
......@@ -32,6 +34,7 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
......@@ -44,6 +47,7 @@ import org.springframework.util.ClassUtils;
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @since 2.5
* @see ContextAnnotationAutowireCandidateResolver
* @see CommonAnnotationBeanPostProcessor
......@@ -297,12 +301,40 @@ public class AnnotationConfigUtils {
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annoClass) {
return attributesFor(metadata, annoClass.getName());
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annotationClass) {
return attributesFor(metadata, annotationClass.getName());
}
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annoClassName) {
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) {
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false));
}
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
Class<?> containerClass, Class<?> annotationClass) {
return attributesForRepeatable(metadata, containerClass.getName(), annotationClass.getName());
}
@SuppressWarnings("unchecked")
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
String containerClassName, String annotationClassName) {
Set<AnnotationAttributes> result = new LinkedHashSet<AnnotationAttributes>();
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));
Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
if (container != null && container.containsKey("value")) {
for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
addAttributesIfNotNull(result, containedAttributes);
}
}
return Collections.unmodifiableSet(result);
}
private static void addAttributesIfNotNull(Set<AnnotationAttributes> result,
Map<String, Object> attributes) {
if (attributes != null) {
result.add(AnnotationAttributes.fromMap(attributes));
}
}
}
......@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
......@@ -55,6 +56,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.core.type.AnnotationMetadata;
......@@ -63,6 +65,8 @@ import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
......@@ -112,7 +116,7 @@ class ConfigurationClassParser {
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
private final Stack<PropertySource<?>> propertySources = new Stack<PropertySource<?>>();
private final MultiValueMap<String, PropertySource<?>> propertySources = new LinkedMultiValueMap<String, PropertySource<?>>();
private final ImportStack importStack = new ImportStack();
......@@ -218,9 +222,9 @@ class ConfigurationClassParser {
processMemberClasses(configClass, sourceClass);
// process any @PropertySource annotations
AnnotationAttributes propertySource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(),
org.springframework.context.annotation.PropertySource.class);
if (propertySource != null) {
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
processPropertySource(propertySource);
}
......@@ -301,29 +305,29 @@ class ConfigurationClassParser {
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
String[] locations = propertySource.getStringArray("value");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
int locationCount = locations.length;
if (locationCount == 0) {
throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
}
for (int i = 0; i < locationCount; i++) {
locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
}
ClassLoader classLoader = this.resourceLoader.getClassLoader();
if (!StringUtils.hasText(name)) {
for (String location : locations) {
this.propertySources.push(new ResourcePropertySource(location, classLoader));
}
}
else {
if (locationCount == 1) {
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
for (String location : locations) {
Resource resource = this.resourceLoader.getResource(
this.environment.resolveRequiredPlaceholders(location));
try {
if (!StringUtils.hasText(name) || this.propertySources.containsKey(name)) {
// We need to ensure unique names when the property source will
// ultimately end up in a composite
ResourcePropertySource ps = new ResourcePropertySource(resource);
this.propertySources.add((StringUtils.hasText(name) ? name : ps.getName()), ps);
}
else {
this.propertySources.add(name, new ResourcePropertySource(name, resource));
}
}
else {
CompositePropertySource ps = new CompositePropertySource(name);
for (int i = locations.length - 1; i >= 0; i--) {
ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader));
catch (FileNotFoundException ex) {
if (!ignoreResourceNotFound) {
throw ex;
}
this.propertySources.push(ps);
}
}
}
......@@ -473,10 +477,27 @@ class ConfigurationClassParser {
return this.configurationClasses;
}
public Stack<PropertySource<?>> getPropertySources() {
return this.propertySources;
public List<PropertySource<?>> getPropertySources() {
List<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
for (Map.Entry<String, List<PropertySource<?>>> entry : this.propertySources.entrySet()) {
propertySources.add(0, collatePropertySources(entry.getKey(), entry.getValue()));
}
return propertySources;
}
private PropertySource<?> collatePropertySources(String name,
List<PropertySource<?>> propertySources) {
if (propertySources.size() == 1) {
return propertySources.get(0);
}
CompositePropertySource result = new CompositePropertySource(name);
for (int i = propertySources.size() - 1; i >= 0; i--) {
result.addPropertySource(propertySources.get(i));
}
return result;
}
ImportRegistry getImportRegistry() {
return this.importStack;
}
......
......@@ -21,13 +21,12 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
......@@ -298,7 +297,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
parser.validate();
// Handle any @PropertySource annotations
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
List<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
if (!parsedPropertySources.isEmpty()) {
if (!(this.environment instanceof ConfigurableEnvironment)) {
logger.warn("Ignoring @PropertySource annotations. " +
......@@ -306,8 +305,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
else {
MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
while (!parsedPropertySources.isEmpty()) {
envPropertySources.addLast(parsedPropertySources.pop());
for (PropertySource<?> propertySource : parsedPropertySources) {
envPropertySources.addLast(propertySource);
}
}
}
......
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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,6 +18,7 @@ package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
......@@ -132,7 +133,9 @@ import java.lang.annotation.Target;
* Javadoc for details.
*
* @author Chris Beams
* @author Phillip Webb
* @since 3.1
* @see PropertySources
* @see Configuration
* @see org.springframework.core.env.PropertySource
* @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
......@@ -141,6 +144,7 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
/**
......@@ -166,4 +170,13 @@ public @interface PropertySource {
*/
String[] value();
/**
* Indicate if failure to find the a {@link #value() property resource} should be
* ignored.
* <p>{@code true} is appropriate if the properties file is completely optional.
* Default is {@code false}.
* @since 4.0
*/
boolean ignoreResourceNotFound() default false;
}
/*
* Copyright 2002-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that aggregates several {@link PropertySource} annotations.
*
* <p>Can be used natively, declaring several nested {@link PropertySource} annotations.
* Can also be used in conjunction with Java 8's support for repeatable annotations,
* where {@link PropertySource} can simply be declared several times on the same method,
* implicitly generating this container annotation.
*
* @author Phillip Webb
* @since 4.0
* @see PropertySource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}
......@@ -42,6 +42,7 @@ import java.lang.annotation.Target;
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
* @see Schedules
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
......
......@@ -117,17 +117,8 @@ public class ScheduledAnnotationBeanPostProcessor
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Schedules schedules = AnnotationUtils.getAnnotation(method, Schedules.class);
if (schedules != null) {
for (Scheduled scheduled : schedules.value()) {
processScheduled(scheduled, method, bean);
}
}
else {
Scheduled scheduled = AnnotationUtils.getAnnotation(method, Scheduled.class);
if (scheduled != null) {
processScheduled(scheduled, method, bean);
}
for (Scheduled scheduled : AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, Scheduled.class)) {
processScheduled(scheduled, method, bean);
}
}
});
......
......@@ -16,29 +16,35 @@
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.FileNotFoundException;
import java.util.Iterator;
import javax.inject.Inject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.tests.sample.beans.TestBean;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
* Tests the processing of @PropertySource annotations on @Configuration classes.
*
* @author Chris Beams
* @author Phillip Webb
* @since 3.1
*/
public class PropertySourceAnnotationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void withExplicitName() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
......@@ -149,6 +155,29 @@ public class PropertySourceAnnotationTests {
ctx.refresh();
}
@Test
public void withPropertySources() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithPropertySources.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
// p2 should 'win' as it was registered last
assertThat(ctx.getEnvironment().getProperty("testbean.name"), equalTo("p2TestBean"));
}
@Test
public void withMissingPropertySource() {
thrown.expect(BeanDefinitionStoreException.class);
thrown.expectCause(isA(FileNotFoundException.class));
new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class);
}
@Test
public void withIgnoredPropertySource() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithIgnoredPropertySource.class);
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
}
@Configuration
@PropertySource(value="classpath:${unresolvable}/p1.properties")
......@@ -231,6 +260,33 @@ public class PropertySourceAnnotationTests {
}
@Configuration
@PropertySources({
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
})
static class ConfigWithPropertySources {
}
@Configuration
@PropertySources({
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/missing.properties"),
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
})
static class ConfigWithMissingPropertySource {
}
@Configuration
@PropertySources({
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/missing.properties", ignoreResourceNotFound=true),
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
})
static class ConfigWithIgnoredPropertySource {
}
@Configuration
@PropertySource(value = {})
static class ConfigWithEmptyResourceLocations {
......
......@@ -19,12 +19,18 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* General utility methods for working with annotations, handling bridge methods (which the compiler
......@@ -43,6 +49,7 @@ import org.springframework.util.Assert;
* @author Sam Brannen
* @author Mark Fisher
* @author Chris Beams
* @author Phillip Webb
* @since 2.0
* @see java.lang.reflect.Method#getAnnotations()
* @see java.lang.reflect.Method#getAnnotation(Class)
......@@ -117,6 +124,45 @@ public abstract class AnnotationUtils {
return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
}
/**
* Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
* supplied {@link Method}. Deals with both a single direct annotation and repeating
* annotations nested within a containing annotation.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* @param method the method to look for annotations on
* @param containerAnnotationType the class of the container that holds the annotations
* @param annotationType the annotation class to look for
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @since 4.0
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
return getRepeatableAnnotation((AnnotatedElement) resolvedMethod,
containerAnnotationType, annotationType);
}
/**
* Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
* supplied {@link AnnotatedElement}. Deals with both a single direct annotation and
* repeating annotations nested within a containing annotation.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* @param annotatedElement the element to look for annotations on
* @param containerAnnotationType the class of the container that holds the annotations
* @param annotationType the annotation class to look for
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @since 4.0
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
if (annotatedElement.getAnnotations().length == 0) {
return Collections.emptySet();
}
return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement);
}
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method},
* traversing its super methods if no annotation can be found on the given method itself.
......@@ -521,4 +567,59 @@ public abstract class AnnotationUtils {
}
}
private static class AnnotationCollector<A extends Annotation> {
private final Class<? extends Annotation> containerAnnotationType;
private final Class<A> annotationType;
private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>();
private final Set<A> result = new LinkedHashSet<A>();
public AnnotationCollector(Class<? extends Annotation> containerAnnotationType,
Class<A> annotationType) {
this.containerAnnotationType = containerAnnotationType;
this.annotationType = annotationType;
}
public Set<A> getResult(AnnotatedElement element) {
process(element);
return Collections.unmodifiableSet(this.result);
}
@SuppressWarnings("unchecked")
private void process(AnnotatedElement annotatedElement) {
if (this.visited.add(annotatedElement)) {
for (Annotation annotation : annotatedElement.getAnnotations()) {
if (ObjectUtils.nullSafeEquals(this.annotationType, annotation.annotationType())) {
this.result.add((A) annotation);
}
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, annotation.annotationType())) {
result.addAll(Arrays.asList(getValue(annotation)));
}
else {
process(annotation.annotationType());
}
}
}
}
@SuppressWarnings("unchecked")
private A[] getValue(Annotation annotation) {
try {
Method method = annotation.annotationType().getDeclaredMethod("value");
return (A[]) method.invoke(annotation);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to read value from repeating annotation container "
+ this.containerAnnotationType.getName(), ex);
}
}
}
}
......@@ -18,17 +18,21 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
......@@ -37,6 +41,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
* @author Phillip Webb
*/
public class AnnotationUtilsTests {
......@@ -268,6 +273,19 @@ public class AnnotationUtilsTests {
assertNotNull(order);
}
@Test
public void testGetRepeatableFromMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo");
Set<MyRepeatable> annotions = AnnotationUtils.getRepeatableAnnotation(method,
MyRepeatableContainer.class, MyRepeatable.class);
Set<String> values = new HashSet<String>();
for (MyRepeatable myRepeatable : annotions) {
values.add(myRepeatable.value());
}
assertThat(values, equalTo((Set<String>) new HashSet<String>(
Arrays.asList("a", "b", "c", "meta"))));
}
@Component(value = "meta1")
@Retention(RetentionPolicy.RUNTIME)
......@@ -428,10 +446,46 @@ public class AnnotationUtilsTests {
}
}
public static interface InterfaceWithRepeated {
@MyRepeatable("a")
@MyRepeatableContainer({ @MyRepeatable("b"), @MyRepeatable("c") })
@MyRepeatableMeta
void foo();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Transactional {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface MyRepeatableContainer {
MyRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(MyRepeatableContainer.class)
@interface MyRepeatable {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MyRepeatable("meta")
@interface MyRepeatableMeta {
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册