summaryrefslogtreecommitdiff
path: root/spring-context/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'spring-context/src/main/java')
-rw-r--r--spring-context/src/main/java/org/springframework/cache/Cache.java45
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java21
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java29
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java47
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/Caching.java5
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java8
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java6
-rw-r--r--spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java135
-rw-r--r--spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java119
-rw-r--r--spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java67
-rw-r--r--spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java9
-rw-r--r--spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java60
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java43
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java216
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java61
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java194
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java (renamed from spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java)30
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java5
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java50
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java65
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java4
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java10
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java13
-rw-r--r--spring-context/src/main/java/org/springframework/cache/support/package-info.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java16
-rw-r--r--spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java4
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java12
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java10
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java4
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java13
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java22
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java11
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java44
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java44
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java8
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java81
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java9
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java25
-rw-r--r--spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java26
-rw-r--r--spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java6
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java21
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java19
-rw-r--r--spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java7
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java16
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java40
-rw-r--r--spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java3
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java28
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java208
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java25
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java2
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java5
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java9
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java138
-rw-r--r--spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java148
-rw-r--r--spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java6
-rw-r--r--spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java9
-rw-r--r--spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java18
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java5
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java9
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java7
-rw-r--r--spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java6
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java33
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java24
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java4
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java4
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java30
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java2
-rw-r--r--spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java11
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java6
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java18
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java28
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java129
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java5
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java17
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java47
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java49
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java161
-rw-r--r--spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java26
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java3
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java6
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java2
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java2
-rw-r--r--spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java4
-rw-r--r--spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java42
-rw-r--r--spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java73
100 files changed, 2170 insertions, 921 deletions
diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java
index 3e8a7978..ebd26020 100644
--- a/spring-context/src/main/java/org/springframework/cache/Cache.java
+++ b/spring-context/src/main/java/org/springframework/cache/Cache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,6 +16,8 @@
package org.springframework.cache;
+import java.util.concurrent.Callable;
+
/**
* Interface that defines common cache operations.
*
@@ -25,6 +27,7 @@ package org.springframework.cache;
*
* @author Costin Leau
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1
*/
public interface Cache {
@@ -35,7 +38,7 @@ public interface Cache {
String getName();
/**
- * Return the the underlying native cache provider.
+ * Return the underlying native cache provider.
*/
Object getNativeCache();
@@ -74,6 +77,23 @@ public interface Cache {
<T> T get(Object key, Class<T> type);
/**
+ * Return the value to which this cache maps the specified key, obtaining
+ * that value from {@code valueLoader} if necessary. This method provides
+ * a simple substitute for the conventional "if cached, return; otherwise
+ * create, cache and return" pattern.
+ * <p>If possible, implementations should ensure that the loading operation
+ * is synchronized so that the specified {@code valueLoader} is only called
+ * once in case of concurrent access on the same key.
+ * <p>If the {@code valueLoader} throws an exception, it is wrapped in
+ * a {@link ValueRetrievalException}
+ * @param key the key whose associated value is to be returned
+ * @return the value to which this cache maps the specified key
+ * @throws ValueRetrievalException if the {@code valueLoader} throws an exception
+ * @since 4.3
+ */
+ <T> T get(Object key, Callable<T> valueLoader);
+
+ /**
* Associate the specified value with the specified key in this cache.
* <p>If the cache previously contained a mapping for this key, the old
* value is replaced by the specified value.
@@ -133,4 +153,25 @@ public interface Cache {
Object get();
}
+
+ /**
+ * Wrapper exception to be thrown from {@link #get(Object, Callable)}
+ * in case of the value loader callback failing with an exception.
+ * @since 4.3
+ */
+ @SuppressWarnings("serial")
+ class ValueRetrievalException extends RuntimeException {
+
+ private final Object key;
+
+ public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
+ super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
+ this.key = key;
+ }
+
+ public Object getKey() {
+ return this.key;
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
index a2d0c2f3..f12d2119 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -29,6 +29,9 @@ import org.springframework.core.annotation.AliasFor;
* Annotation indicating that a method (or all methods on a class) triggers a
* {@link org.springframework.cache.Cache#evict(Object) cache evict} operation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Stephane Nicoll
* @author Sam Brannen
@@ -62,18 +65,18 @@ public @interface CacheEvict {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been set.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation, which
* can only be used if {@link #beforeInvocation()} is {@code false}.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -108,16 +111,16 @@ public @interface CacheEvict {
* Spring Expression Language (SpEL) expression used for making the cache
* eviction operation conditional.
* <p>Default is {@code ""}, meaning the cache eviction is always performed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
index bcea5494..cd48e410 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -33,6 +33,9 @@ import org.springframework.core.annotation.AliasFor;
* does not cause the advised method to be skipped. Rather, it always causes the
* method to be invoked and its result to be stored in the associated cache.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
@@ -67,17 +70,17 @@ public @interface CachePut {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been set.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -112,16 +115,16 @@ public @interface CachePut {
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
* <p>Default is {@code ""}, meaning the method result is always cached.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -132,17 +135,17 @@ public @interface CachePut {
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
* @since 3.2
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
index a5d336a6..225ada51 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -22,6 +22,7 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.concurrent.Callable;
import org.springframework.core.annotation.AliasFor;
@@ -39,6 +40,9 @@ import org.springframework.core.annotation.AliasFor;
* <p>If no value is found in the cache for the computed key, the target method
* will be invoked and the returned value stored in the associated cache.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
@@ -73,16 +77,16 @@ public @interface Cacheable {
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
* unless a custom {@link #keyGenerator} has been configured.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -117,16 +121,16 @@ public @interface Cacheable {
* Spring Expression Language (SpEL) expression used for making the method
* caching conditional.
* <p>Default is {@code ""}, meaning the method result is always cached.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
*/
@@ -137,21 +141,38 @@ public @interface Cacheable {
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
- * <p>The SpEL expression evaluates again a dedicated context that provides the
+ * <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation.</li>
- * <li>{@code #root.method}, {@code #root.target} and {@code #root.caches} for a
- * reference to the {@link java.lang.reflect.Method method}, target object and
+ * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
+ * references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
* <li>Shortcuts for the method name ({@code #root.methodName}) and target class
* ({@code #root.targetClass}) are also available.
* <li>Method arguments can be accessed by index. For instance the second argument
- * can be access via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
+ * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
* can also be accessed by name if that information is available.</li>
* </ul>
* @since 3.2
*/
String unless() default "";
+ /**
+ * Synchronize the invocation of the underlying method if several threads are
+ * attempting to load a value for the same key. The synchronization leads to
+ * a couple of limitations:
+ * <ol>
+ * <li>{@link #unless()} is not supported</li>
+ * <li>Only one cache may be specified</li>
+ * <li>No other cache-related operation can be combined</li>
+ * </ol>
+ * This is effectively a hint and the actual cache provider that you are
+ * using may not support it in a synchronized fashion. Check your provider
+ * documentation for more details on the actual semantics.
+ * @since 4.3
+ * @see org.springframework.cache.Cache#get(Object, Callable)
+ */
+ boolean sync() default false;
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
index 6802fd5d..08a4482f 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -26,6 +26,9 @@ import java.lang.annotation.Target;
/**
* Group annotation for multiple cache annotations (of different or the same type).
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Costin Leau
* @author Chris Beams
* @since 3.1
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
index f99b40d7..f7f6fa4e 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -52,7 +52,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
private static final boolean jsr107Present = ClassUtils.isPresent(
"javax.cache.Cache", CachingConfigurationSelector.class.getClassLoader());
- private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ private static final boolean jcacheImplPresent = ClassUtils.isPresent(
PROXY_JCACHE_CONFIGURATION_CLASS, CachingConfigurationSelector.class.getClassLoader());
@@ -81,7 +81,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
List<String> result = new ArrayList<String>();
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
- if (jsr107Present && jCacheImplPresent) {
+ if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return result.toArray(new String[result.size()]);
@@ -94,7 +94,7 @@ public class CachingConfigurationSelector extends AdviceModeImportSelector<Enabl
private String[] getAspectJImports() {
List<String> result = new ArrayList<String>();
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
- if (jsr107Present && jCacheImplPresent) {
+ if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return result.toArray(new String[result.size()]);
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
index 511e4de6..0ec602b9 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
@@ -47,7 +47,7 @@ import org.springframework.core.Ordered;
* public CacheManager cacheManager() {
* // configure and return an implementation of Spring's CacheManager SPI
* SimpleCacheManager cacheManager = new SimpleCacheManager();
- * cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ * cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
* return cacheManager;
* }
* }</pre>
@@ -99,7 +99,7 @@ import org.springframework.core.Ordered;
* <p>For those that wish to establish a more direct relationship between
* {@code @EnableCaching} and the exact cache manager bean to be used,
* the {@link CachingConfigurer} callback interface may be implemented.
- * Notice the the {@code @Override}-annotated methods below:
+ * Notice the {@code @Override}-annotated methods below:
*
* <pre class="code">
* &#064;Configuration
@@ -117,7 +117,7 @@ import org.springframework.core.Ordered;
* public CacheManager cacheManager() {
* // configure and return an implementation of Spring's CacheManager SPI
* SimpleCacheManager cacheManager = new SimpleCacheManager();
- * cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
+ * cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
* return cacheManager;
* }
*
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
index d6992bd2..d58bd133 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
@@ -27,6 +27,7 @@ import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -61,29 +62,29 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
- Collection<Cacheable> cacheables = getAnnotations(ae, Cacheable.class);
- if (cacheables != null) {
+ Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
+ if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
- Collection<CacheEvict> evicts = getAnnotations(ae, CacheEvict.class);
- if (evicts != null) {
+ Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
+ if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
- Collection<CachePut> puts = getAnnotations(ae, CachePut.class);
- if (puts != null) {
+ Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
+ if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
- Collection<Caching> cachings = getAnnotations(ae, Caching.class);
- if (cachings != null) {
+ Collection<Caching> cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
+ if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
@@ -101,55 +102,59 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
}
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
- CacheableOperation op = new CacheableOperation();
-
- op.setCacheNames(cacheable.cacheNames());
- op.setCondition(cacheable.condition());
- op.setUnless(cacheable.unless());
- op.setKey(cacheable.key());
- op.setKeyGenerator(cacheable.keyGenerator());
- op.setCacheManager(cacheable.cacheManager());
- op.setCacheResolver(cacheable.cacheResolver());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CacheableOperation.Builder builder = new CacheableOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cacheable.cacheNames());
+ builder.setCondition(cacheable.condition());
+ builder.setUnless(cacheable.unless());
+ builder.setKey(cacheable.key());
+ builder.setKeyGenerator(cacheable.keyGenerator());
+ builder.setCacheManager(cacheable.cacheManager());
+ builder.setCacheResolver(cacheable.cacheResolver());
+ builder.setSync(cacheable.sync());
+
+ defaultConfig.applyDefault(builder);
+ CacheableOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
- CacheEvictOperation op = new CacheEvictOperation();
-
- op.setCacheNames(cacheEvict.cacheNames());
- op.setCondition(cacheEvict.condition());
- op.setKey(cacheEvict.key());
- op.setKeyGenerator(cacheEvict.keyGenerator());
- op.setCacheManager(cacheEvict.cacheManager());
- op.setCacheResolver(cacheEvict.cacheResolver());
- op.setCacheWide(cacheEvict.allEntries());
- op.setBeforeInvocation(cacheEvict.beforeInvocation());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cacheEvict.cacheNames());
+ builder.setCondition(cacheEvict.condition());
+ builder.setKey(cacheEvict.key());
+ builder.setKeyGenerator(cacheEvict.keyGenerator());
+ builder.setCacheManager(cacheEvict.cacheManager());
+ builder.setCacheResolver(cacheEvict.cacheResolver());
+ builder.setCacheWide(cacheEvict.allEntries());
+ builder.setBeforeInvocation(cacheEvict.beforeInvocation());
+
+ defaultConfig.applyDefault(builder);
+ CacheEvictOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
CacheOperation parsePutAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
- CachePutOperation op = new CachePutOperation();
-
- op.setCacheNames(cachePut.cacheNames());
- op.setCondition(cachePut.condition());
- op.setUnless(cachePut.unless());
- op.setKey(cachePut.key());
- op.setKeyGenerator(cachePut.keyGenerator());
- op.setCacheManager(cachePut.cacheManager());
- op.setCacheResolver(cachePut.cacheResolver());
- op.setName(ae.toString());
-
- defaultConfig.applyDefault(op);
+ CachePutOperation.Builder builder = new CachePutOperation.Builder();
+
+ builder.setName(ae.toString());
+ builder.setCacheNames(cachePut.cacheNames());
+ builder.setCondition(cachePut.condition());
+ builder.setUnless(cachePut.unless());
+ builder.setKey(cachePut.key());
+ builder.setKeyGenerator(cachePut.keyGenerator());
+ builder.setCacheManager(cachePut.cacheManager());
+ builder.setCacheResolver(cachePut.cacheResolver());
+
+ defaultConfig.applyDefault(builder);
+ CachePutOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
@@ -197,26 +202,6 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return new DefaultCacheConfig();
}
- private <A extends Annotation> Collection<A> getAnnotations(AnnotatedElement ae, Class<A> annotationType) {
- Collection<A> anns = new ArrayList<A>(1);
-
- // look at raw annotation
- A ann = ae.getAnnotation(annotationType);
- if (ann != null) {
- anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
- }
-
- // scan meta-annotations
- for (Annotation metaAnn : ae.getAnnotations()) {
- ann = metaAnn.annotationType().getAnnotation(annotationType);
- if (ann != null) {
- anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
- }
- }
-
- return (!anns.isEmpty() ? anns : null);
- }
-
/**
* Validates the specified {@link CacheOperation}.
* <p>Throws an {@link IllegalStateException} if the state of the operation is
@@ -277,26 +262,26 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
}
/**
- * Apply the defaults to the specified {@link CacheOperation}.
- * @param operation the operation to update
+ * Apply the defaults to the specified {@link CacheOperation.Builder}.
+ * @param builder the operation builder to update
*/
- public void applyDefault(CacheOperation operation) {
- if (operation.getCacheNames().isEmpty() && this.cacheNames != null) {
- operation.setCacheNames(this.cacheNames);
+ public void applyDefault(CacheOperation.Builder builder) {
+ if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
+ builder.setCacheNames(this.cacheNames);
}
- if (!StringUtils.hasText(operation.getKey()) && !StringUtils.hasText(operation.getKeyGenerator()) &&
+ if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
StringUtils.hasText(this.keyGenerator)) {
- operation.setKeyGenerator(this.keyGenerator);
+ builder.setKeyGenerator(this.keyGenerator);
}
- if (StringUtils.hasText(operation.getCacheManager()) || StringUtils.hasText(operation.getCacheResolver())) {
+ if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
// One of these is set so we should not inherit anything
}
else if (StringUtils.hasText(this.cacheResolver)) {
- operation.setCacheResolver(this.cacheResolver);
+ builder.setCacheResolver(this.cacheResolver);
}
else if (StringUtils.hasText(this.cacheManager)) {
- operation.setCacheManager(this.cacheManager);
+ builder.setCacheManager(this.cacheManager);
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
index 4942e14c..df41bf5f 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,10 +16,15 @@
package org.springframework.cache.concurrent;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.cache.support.AbstractValueAdaptingCache;
+import org.springframework.core.serializer.support.SerializationDelegate;
import org.springframework.util.Assert;
/**
@@ -37,6 +42,7 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 3.1
*/
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
@@ -45,6 +51,8 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final ConcurrentMap<Object, Object> store;
+ private final SerializationDelegate serialization;
+
/**
* Create a new ConcurrentMapCache with the specified name.
@@ -73,14 +81,44 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
* (adapting them to an internal null holder value)
*/
public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {
+ this(name, store, allowNullValues, null);
+ }
+
+ /**
+ * Create a new ConcurrentMapCache with the specified name and the
+ * given internal {@link ConcurrentMap} to use. If the
+ * {@link SerializationDelegate} is specified,
+ * {@link #isStoreByValue() store-by-value} is enabled
+ * @param name the name of the cache
+ * @param store the ConcurrentMap to use as an internal store
+ * @param allowNullValues whether to allow {@code null} values
+ * (adapting them to an internal null holder value)
+ * @param serialization the {@link SerializationDelegate} to use
+ * to serialize cache entry or {@code null} to store the reference
+ * @since 4.3
+ */
+ protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store,
+ boolean allowNullValues, SerializationDelegate serialization) {
+
super(allowNullValues);
Assert.notNull(name, "Name must not be null");
Assert.notNull(store, "Store must not be null");
this.name = name;
this.store = store;
+ this.serialization = serialization;
}
+ /**
+ * Return whether this cache stores a copy of each entry ({@code true}) or
+ * a reference ({@code false}, default). If store by value is enabled, each
+ * entry in the cache must be serializable.
+ * @since 4.3
+ */
+ public final boolean isStoreByValue() {
+ return (this.serialization != null);
+ }
+
@Override
public final String getName() {
return this.name;
@@ -96,6 +134,30 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
return this.store.get(key);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ if (this.store.containsKey(key)) {
+ return (T) get(key).get();
+ }
+ else {
+ synchronized (this.store) {
+ if (this.store.containsKey(key)) {
+ return (T) get(key).get();
+ }
+ T value;
+ try {
+ value = valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex);
+ }
+ put(key, value);
+ return value;
+ }
+ }
+ }
+
@Override
public void put(Object key, Object value) {
this.store.put(key, toStoreValue(value));
@@ -117,4 +179,59 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
this.store.clear();
}
+ @Override
+ protected Object toStoreValue(Object userValue) {
+ Object storeValue = super.toStoreValue(userValue);
+ if (this.serialization != null) {
+ try {
+ return serializeValue(storeValue);
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Failed to serialize cache value '"
+ + userValue + "'. Does it implement Serializable?", ex);
+ }
+ }
+ else {
+ return storeValue;
+ }
+ }
+
+ private Object serializeValue(Object storeValue) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ this.serialization.serialize(storeValue, out);
+ return out.toByteArray();
+ }
+ finally {
+ out.close();
+ }
+ }
+
+ @Override
+ protected Object fromStoreValue(Object storeValue) {
+ if (this.serialization != null) {
+ try {
+ return super.fromStoreValue(deserializeValue(storeValue));
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Failed to deserialize cache value '" +
+ storeValue + "'", ex);
+ }
+ }
+ else {
+ return super.fromStoreValue(storeValue);
+ }
+
+ }
+
+ private Object deserializeValue(Object storeValue) throws IOException {
+ ByteArrayInputStream in = new ByteArrayInputStream((byte[]) storeValue);
+ try {
+ return this.serialization.deserialize(in);
+ }
+ finally {
+ in.close();
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
index 14c6956b..cce957b3 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,8 +23,10 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
+import org.springframework.core.serializer.support.SerializationDelegate;
/**
* {@link CacheManager} implementation that lazily builds {@link ConcurrentMapCache}
@@ -35,14 +37,16 @@ import org.springframework.cache.CacheManager;
* <p>Note: This is by no means a sophisticated CacheManager; it comes with no
* cache configuration options. However, it may be useful for testing or simple
* caching scenarios. For advanced local caching needs, consider
- * {@link org.springframework.cache.guava.GuavaCacheManager} or
- * {@link org.springframework.cache.ehcache.EhCacheCacheManager}.
+ * {@link org.springframework.cache.jcache.JCacheCacheManager},
+ * {@link org.springframework.cache.ehcache.EhCacheCacheManager},
+ * {@link org.springframework.cache.caffeine.CaffeineCacheManager} or
+ * {@link org.springframework.cache.guava.GuavaCacheManager}.
*
* @author Juergen Hoeller
* @since 3.1
* @see ConcurrentMapCache
*/
-public class ConcurrentMapCacheManager implements CacheManager {
+public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
@@ -50,6 +54,10 @@ public class ConcurrentMapCacheManager implements CacheManager {
private boolean allowNullValues = true;
+ private boolean storeByValue = false;
+
+ private SerializationDelegate serialization;
+
/**
* Construct a dynamic ConcurrentMapCacheManager,
@@ -98,9 +106,7 @@ public class ConcurrentMapCacheManager implements CacheManager {
if (allowNullValues != this.allowNullValues) {
this.allowNullValues = allowNullValues;
// Need to recreate all Cache instances with the new null-value configuration...
- for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
- entry.setValue(createConcurrentMapCache(entry.getKey()));
- }
+ recreateCaches();
}
}
@@ -112,6 +118,42 @@ public class ConcurrentMapCacheManager implements CacheManager {
return this.allowNullValues;
}
+ /**
+ * Specify whether this cache manager stores a copy of each entry ({@code true}
+ * or the reference ({@code false} for all of its caches.
+ * <p>Default is "false" so that the value itself is stored and no serializable
+ * contract is required on cached values.
+ * <p>Note: A change of the store-by-value setting will reset all existing caches,
+ * if any, to reconfigure them with the new store-by-value requirement.
+ * @since 4.3
+ */
+ public void setStoreByValue(boolean storeByValue) {
+ if (storeByValue != this.storeByValue) {
+ this.storeByValue = storeByValue;
+ // Need to recreate all Cache instances with the new store-by-value configuration...
+ recreateCaches();
+ }
+ }
+
+ /**
+ * Return whether this cache manager stores a copy of each entry or
+ * a reference for all its caches. If store by value is enabled, any
+ * cache entry must be serializable.
+ * @since 4.3
+ */
+ public boolean isStoreByValue() {
+ return this.storeByValue;
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.serialization = new SerializationDelegate(classLoader);
+ // Need to recreate all Cache instances with new ClassLoader in store-by-value mode...
+ if (isStoreByValue()) {
+ recreateCaches();
+ }
+ }
+
@Override
public Collection<String> getCacheNames() {
@@ -133,13 +175,22 @@ public class ConcurrentMapCacheManager implements CacheManager {
return cache;
}
+ private void recreateCaches() {
+ for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
+ entry.setValue(createConcurrentMapCache(entry.getKey()));
+ }
+ }
+
/**
* Create a new ConcurrentMapCache instance for the specified cache name.
* @param name the name of the cache
* @return the ConcurrentMapCache (or a decorator thereof)
*/
protected Cache createConcurrentMapCache(String name) {
- return new ConcurrentMapCache(name, isAllowNullValues());
+ SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
+ return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
+ isAllowNullValues(), actualSerialization);
+
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
index 4ea5ccad..d9f90dc2 100644
--- a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -60,11 +60,10 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private static final String JCACHE_ASPECT_CLASS_NAME =
"org.springframework.cache.aspectj.JCacheCacheAspect";
-
private static final boolean jsr107Present = ClassUtils.isPresent(
"javax.cache.Cache", AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
- private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ private static final boolean jcacheImplPresent = ClassUtils.isPresent(
"org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource",
AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
@@ -91,7 +90,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private void registerCacheAspect(Element element, ParserContext parserContext) {
SpringCachingConfigurer.registerCacheAspect(element, parserContext);
- if (jsr107Present && jCacheImplPresent) { // Register JCache aspect
+ if (jsr107Present && jcacheImplPresent) {
JCacheCachingConfigurer.registerCacheAspect(element, parserContext);
}
}
@@ -99,7 +98,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private void registerCacheAdvisor(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
SpringCachingConfigurer.registerCacheAdvisor(element, parserContext);
- if (jsr107Present && jCacheImplPresent) { // Register JCache advisor
+ if (jsr107Present && jcacheImplPresent) {
JCacheCachingConfigurer.registerCacheAdvisor(element, parserContext);
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
index 07bcea27..f4a626a7 100644
--- a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -107,15 +107,17 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
- op.setUnless(getAttributeValue(opElement, "unless", ""));
+ CacheableOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CacheableOperation.Builder());
+ builder.setUnless(getAttributeValue(opElement, "unless", ""));
+ builder.setSync(Boolean.valueOf(getAttributeValue(opElement, "sync", "false")));
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
@@ -124,16 +126,17 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CacheEvictOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
+ CacheEvictOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CacheEvictOperation.Builder());
String wide = opElement.getAttribute("all-entries");
if (StringUtils.hasText(wide)) {
- op.setCacheWide(Boolean.valueOf(wide.trim()));
+ builder.setCacheWide(Boolean.valueOf(wide.trim()));
}
String after = opElement.getAttribute("before-invocation");
if (StringUtils.hasText(after)) {
- op.setBeforeInvocation(Boolean.valueOf(after.trim()));
+ builder.setBeforeInvocation(Boolean.valueOf(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
@@ -141,7 +144,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
@@ -150,15 +153,16 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
- CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
- op.setUnless(getAttributeValue(opElement, "unless", ""));
+ CachePutOperation.Builder builder = prop.merge(opElement,
+ parserContext.getReaderContext(), new CachePutOperation.Builder());
+ builder.setUnless(getAttributeValue(opElement, "unless", ""));
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
- col.add(op);
+ col.add(builder.build());
}
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
@@ -196,45 +200,45 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
Props(Element root) {
String defaultCache = root.getAttribute("cache");
- key = root.getAttribute("key");
- keyGenerator = root.getAttribute("key-generator");
- cacheManager = root.getAttribute("cache-manager");
- condition = root.getAttribute("condition");
- method = root.getAttribute(METHOD_ATTRIBUTE);
+ this.key = root.getAttribute("key");
+ this.keyGenerator = root.getAttribute("key-generator");
+ this.cacheManager = root.getAttribute("cache-manager");
+ this.condition = root.getAttribute("condition");
+ this.method = root.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(defaultCache)) {
- caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
+ this.caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
}
}
- <T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
+ <T extends CacheOperation.Builder> T merge(Element element, ReaderContext readerCtx, T builder) {
String cache = element.getAttribute("cache");
// sanity check
- String[] localCaches = caches;
+ String[] localCaches = this.caches;
if (StringUtils.hasText(cache)) {
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
}
else {
- if (caches == null) {
- readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
+ if (this.caches == null) {
+ readerCtx.error("No cache specified for " + element.getNodeName(), element);
}
}
- op.setCacheNames(localCaches);
+ builder.setCacheNames(localCaches);
- op.setKey(getAttributeValue(element, "key", this.key));
- op.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
- op.setCacheManager(getAttributeValue(element, "cache-manager", this.cacheManager));
- op.setCondition(getAttributeValue(element, "condition", this.condition));
+ builder.setKey(getAttributeValue(element, "key", this.key));
+ builder.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
+ builder.setCacheManager(getAttributeValue(element, "cache-manager", this.cacheManager));
+ builder.setCondition(getAttributeValue(element, "condition", this.condition));
- if (StringUtils.hasText(op.getKey()) && StringUtils.hasText(op.getKeyGenerator())) {
+ if (StringUtils.hasText(builder.getKey()) && StringUtils.hasText(builder.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache advice configuration on '"
+ element.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
- return op;
+ return builder;
}
String merge(Element element, ReaderContext readerCtx) {
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
index d383b281..88d0aec5 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -26,27 +26,24 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
/**
- * Abstract implementation of {@link CacheOperation} that caches
- * attributes for methods and implements a fallback policy: 1. specific
- * target method; 2. target class; 3. declaring method; 4. declaring
- * class/interface.
+ * Abstract implementation of {@link CacheOperation} that caches attributes
+ * for methods and implements a fallback policy: 1. specific target method;
+ * 2. target class; 3. declaring method; 4. declaring class/interface.
*
* <p>Defaults to using the target class's caching attribute if none is
- * associated with the target method. Any caching attribute associated
- * with the target method completely overrides a class caching attribute.
- * If none found on the target class, the interface that the invoked
- * method has been called through (in case of a JDK proxy) will be
- * checked.
+ * associated with the target method. Any caching attribute associated with
+ * the target method completely overrides a class caching attribute.
+ * If none found on the target class, the interface that the invoked method
+ * has been called through (in case of a JDK proxy) will be checked.
*
- * <p>This implementation caches attributes by method after they are
- * first used. If it is ever desirable to allow dynamic changing of
- * cacheable attributes (which is very unlikely), caching could be made
- * configurable.
+ * <p>This implementation caches attributes by method after they are first
+ * used. If it is ever desirable to allow dynamic changing of cacheable
+ * attributes (which is very unlikely), caching could be made configurable.
*
* @author Costin Leau
* @author Juergen Hoeller
@@ -69,7 +66,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
protected final Log logger = LogFactory.getLog(getClass());
/**
- * Cache of CacheOperations, keyed by {@link AnnotatedElementKey}.
+ * Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
@@ -117,7 +114,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, Class<?> targetClass) {
- return new AnnotatedElementKey(method, targetClass);
+ return new MethodClassKey(method, targetClass);
}
private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
@@ -140,19 +137,23 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
- if (opDef != null) {
+ if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
- // Fall back is to look at the original method.
+ // Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
- // Last fall back is the class of the original method.
- return findCacheOperations(method.getDeclaringClass());
+ // Last fallback is the class of the original method.
+ opDef = findCacheOperations(method.getDeclaringClass());
+ if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
+ return opDef;
+ }
}
+
return null;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
index 6dd21c1d..2de8bd8f 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,12 +23,16 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -36,11 +40,10 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
-import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
+import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -76,17 +79,26 @@ import org.springframework.util.StringUtils;
* @since 3.1
*/
public abstract class CacheAspectSupport extends AbstractCacheInvoker
- implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware {
+ implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
+
+ private static Class<?> javaUtilOptionalClass = null;
+
+ static {
+ try {
+ javaUtilOptionalClass =
+ ClassUtils.forName("java.util.Optional", CacheAspectSupport.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 8 not available - Optional references simply not supported then.
+ }
+ }
protected final Log logger = LogFactory.getLog(getClass());
- /**
- * Cache of CacheOperationMetadata, keyed by {@link CacheOperationCacheKey}.
- */
private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache =
new ConcurrentHashMap<CacheOperationCacheKey, CacheOperationMetadata>(1024);
- private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
+ private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();
private CacheOperationSource cacheOperationSource;
@@ -94,7 +106,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private CacheResolver cacheResolver;
- private ApplicationContext applicationContext;
+ private BeanFactory beanFactory;
private boolean initialized = false;
@@ -163,12 +175,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return this.cacheResolver;
}
+ /**
+ * Set the containing {@link BeanFactory} for {@link CacheManager} and other
+ * service lookups.
+ * @since 4.3
+ */
@Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * @deprecated as of 4.3, in favor of {@link #setBeanFactory}
+ */
+ @Deprecated
public void setApplicationContext(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
+ this.beanFactory = applicationContext;
}
+ @Override
public void afterPropertiesSet() {
Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect.");
@@ -180,7 +206,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
try {
- setCacheManager(this.applicationContext.getBean(CacheManager.class));
+ setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
@@ -273,7 +299,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* Return a bean with the specified name and type. Used to resolve services that
* are referenced by name in a {@link CacheOperation}.
* @param beanName the name of the bean, as defined by the operation
- * @param expectedType type type for the bean
+ * @param expectedType type for the bean
* @return the bean matching that name
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
* @see CacheOperation#keyGenerator
@@ -281,7 +307,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* @see CacheOperation#cacheResolver
*/
protected <T> T getBean(String beanName, Class<T> expectedType) {
- return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext, expectedType, beanName);
+ return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
}
/**
@@ -293,13 +319,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
- // check whether aspect is enabled
- // to cope with cases where the AJ is pulled in automatically
+ // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
- return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass));
+ return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
@@ -328,9 +353,37 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return targetClass;
}
- private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
+ private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
+ // Special handling of synchronized invocation
+ if (contexts.isSynchronized()) {
+ CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
+ if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
+ Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
+ Cache cache = context.getCaches().iterator().next();
+ try {
+ return cache.get(key, new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ return invokeOperation(invoker);
+ }
+ });
+ }
+ catch (Cache.ValueRetrievalException ex) {
+ // The invoker wraps any Throwable in a ThrowableWrapper instance so we
+ // can just make sure that one bubbles up the stack.
+ throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
+ }
+ }
+ else {
+ // No caching required, only call the underlying method
+ return invokeOperation(invoker);
+ }
+ }
+
+
// Process any early evictions
- processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);
+ processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
+ CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
@@ -338,42 +391,56 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
- collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests);
+ collectPutRequests(contexts.get(CacheableOperation.class),
+ CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
- Cache.ValueWrapper result = null;
+ Object cacheValue;
+ Object returnValue;
- // If there are no put requests, just use the cache hit
- if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
- result = cacheHit;
+ if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
+ // If there are no put requests, just use the cache hit
+ cacheValue = cacheHit.get();
+ if (method.getReturnType() == javaUtilOptionalClass &&
+ (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
+ returnValue = OptionalUnwrapper.wrap(cacheValue);
+ }
+ else {
+ returnValue = cacheValue;
+ }
}
-
- // Invoke the method if don't have a cache hit
- if (result == null) {
- result = new SimpleValueWrapper(invokeOperation(invoker));
+ else {
+ // Invoke the method if we don't have a cache hit
+ returnValue = invokeOperation(invoker);
+ if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
+ cacheValue = OptionalUnwrapper.unwrap(returnValue);
+ }
+ else {
+ cacheValue = returnValue;
+ }
}
// Collect any explicit @CachePuts
- collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests);
+ collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
- cachePutRequest.apply(result.get());
+ cachePutRequest.apply(cacheValue);
}
// Process any late evictions
- processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());
+ processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
- return result.get();
+ return returnValue;
}
private boolean hasCachePut(CacheOperationContexts contexts) {
- // Evaluate the conditions *without* the result object because we don't have it yet.
+ // Evaluate the conditions *without* the result object because we don't have it yet...
Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
Collection<CacheOperationContext> excluded = new ArrayList<CacheOperationContext>();
for (CacheOperationContext context : cachePutContexts) {
try {
- if (!context.isConditionPassing(ExpressionEvaluator.RESULT_UNAVAILABLE)) {
+ if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) {
excluded.add(context);
}
}
@@ -425,7 +492,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
* or {@code null} if none is found
*/
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
- Object result = ExpressionEvaluator.NO_RESULT;
+ Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
@@ -501,18 +568,57 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();
+ private final boolean sync;
+
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
for (CacheOperation operation : operations) {
this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
}
+ this.sync = determineSyncFlag(method);
}
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.<CacheOperationContext>emptyList());
}
+
+ public boolean isSynchronized() {
+ return this.sync;
+ }
+
+ private boolean determineSyncFlag(Method method) {
+ List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
+ if (cacheOperationContexts == null) { // no @Cacheable operation at all
+ return false;
+ }
+ boolean syncEnabled = false;
+ for (CacheOperationContext cacheOperationContext : cacheOperationContexts) {
+ if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
+ syncEnabled = true;
+ break;
+ }
+ }
+ if (syncEnabled) {
+ if (this.contexts.size() > 1) {
+ throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
+ }
+ if (cacheOperationContexts.size() > 1) {
+ throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
+ }
+ CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
+ CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
+ if (cacheOperationContext.getCaches().size() > 1) {
+ throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
+ }
+ if (StringUtils.hasText(operation.getUnless())) {
+ throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
+ }
+ return true;
+ }
+ return false;
+ }
}
@@ -635,8 +741,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
private EvaluationContext createEvaluationContext(Object result) {
- return evaluator.createEvaluationContext(
- this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
+ return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args,
+ this.target, this.metadata.targetClass, result, beanFactory);
}
protected Collection<? extends Cache> getCaches() {
@@ -678,7 +784,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
- private static class CacheOperationCacheKey {
+ private static final class CacheOperationCacheKey implements Comparable<CacheOperationCacheKey> {
private final CacheOperation cacheOperation;
@@ -706,6 +812,42 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
public int hashCode() {
return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode());
}
+
+ @Override
+ public String toString() {
+ return this.cacheOperation + " on " + this.methodCacheKey;
+ }
+
+ @Override
+ public int compareTo(CacheOperationCacheKey other) {
+ int result = this.cacheOperation.getName().compareTo(other.cacheOperation.getName());
+ if (result == 0) {
+ result = this.methodCacheKey.compareTo(other.methodCacheKey);
+ }
+ return result;
+ }
+ }
+
+
+ /**
+ * Inner class to avoid a hard dependency on Java 8.
+ */
+ @UsesJava8
+ private static class OptionalUnwrapper {
+
+ public static Object unwrap(Object optionalObject) {
+ Optional<?> optional = (Optional<?>) optionalObject;
+ if (!optional.isPresent()) {
+ return null;
+ }
+ Object result = optional.get();
+ Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported");
+ return result;
+ }
+
+ public static Object wrap(Object value) {
+ return Optional.ofNullable(value);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
index 6bb937a5..5f5e0dc0 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,38 +20,65 @@ package org.springframework.cache.interceptor;
* Class describing a cache 'evict' operation.
*
* @author Costin Leau
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CacheEvictOperation extends CacheOperation {
- private boolean cacheWide = false;
+ private final boolean cacheWide;
- private boolean beforeInvocation = false;
+ private final boolean beforeInvocation;
- public void setCacheWide(boolean cacheWide) {
- this.cacheWide = cacheWide;
+ /**
+ * @since 4.3
+ */
+ public CacheEvictOperation(CacheEvictOperation.Builder b) {
+ super(b);
+ this.cacheWide = b.cacheWide;
+ this.beforeInvocation = b.beforeInvocation;
}
+
public boolean isCacheWide() {
return this.cacheWide;
}
- public void setBeforeInvocation(boolean beforeInvocation) {
- this.beforeInvocation = beforeInvocation;
- }
-
public boolean isBeforeInvocation() {
return this.beforeInvocation;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(",");
- sb.append(this.cacheWide);
- sb.append(",");
- sb.append(this.beforeInvocation);
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private boolean cacheWide = false;
+
+ private boolean beforeInvocation = false;
+
+ public void setCacheWide(boolean cacheWide) {
+ this.cacheWide = cacheWide;
+ }
+
+ public void setBeforeInvocation(boolean beforeInvocation) {
+ this.beforeInvocation = beforeInvocation;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(",");
+ sb.append(this.cacheWide);
+ sb.append(",");
+ sb.append(this.beforeInvocation);
+ return sb;
+ }
+
+ public CacheEvictOperation build() {
+ return new CacheEvictOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
index 6264a040..271b59f1 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -27,45 +27,45 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Stephane Nicoll
+ * @author Marcin Kamionowski
* @since 3.1
*/
public abstract class CacheOperation implements BasicOperation {
- private String name = "";
+ private final String name;
- private Set<String> cacheNames = Collections.emptySet();
+ private final Set<String> cacheNames;
- private String key = "";
+ private final String key;
- private String keyGenerator = "";
+ private final String keyGenerator;
- private String cacheManager = "";
+ private final String cacheManager;
- private String cacheResolver = "";
+ private final String cacheResolver;
- private String condition = "";
+ private final String condition;
+ private final String toString;
- public void setName(String name) {
- Assert.hasText(name);
- this.name = name;
- }
- public String getName() {
- return this.name;
+ /**
+ * @since 4.3
+ */
+ protected CacheOperation(Builder b) {
+ this.name = b.name;
+ this.cacheNames = b.cacheNames;
+ this.key = b.key;
+ this.keyGenerator = b.keyGenerator;
+ this.cacheManager = b.cacheManager;
+ this.cacheResolver = b.cacheResolver;
+ this.condition = b.condition;
+ this.toString = b.getOperationDescription().toString();
}
- public void setCacheName(String cacheName) {
- Assert.hasText(cacheName);
- this.cacheNames = Collections.singleton(cacheName);
- }
- public void setCacheNames(String... cacheNames) {
- this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
- for (String cacheName : cacheNames) {
- Assert.hasText(cacheName, "Cache name must be non-null if specified");
- this.cacheNames.add(cacheName);
- }
+ public String getName() {
+ return this.name;
}
@Override
@@ -73,47 +73,22 @@ public abstract class CacheOperation implements BasicOperation {
return this.cacheNames;
}
- public void setKey(String key) {
- Assert.notNull(key);
- this.key = key;
- }
-
public String getKey() {
return this.key;
}
- public void setKeyGenerator(String keyGenerator) {
- Assert.notNull(keyGenerator);
- this.keyGenerator = keyGenerator;
- }
-
public String getKeyGenerator() {
return this.keyGenerator;
}
- public void setCacheManager(String cacheManager) {
- Assert.notNull(cacheManager);
- this.cacheManager = cacheManager;
- }
-
public String getCacheManager() {
return this.cacheManager;
}
- public void setCacheResolver(String cacheResolver) {
- Assert.notNull(cacheManager);
- this.cacheResolver = cacheResolver;
- }
-
public String getCacheResolver() {
return this.cacheResolver;
}
- public void setCondition(String condition) {
- Assert.notNull(condition);
- this.condition = condition;
- }
-
public String getCondition() {
return this.condition;
}
@@ -139,29 +114,116 @@ public abstract class CacheOperation implements BasicOperation {
/**
* Return an identifying description for this cache operation.
- * <p>Has to be overridden in subclasses for correct {@code equals}
- * and {@code hashCode} behavior. Alternatively, {@link #equals}
- * and {@link #hashCode} can be overridden themselves.
+ * <p>Returned value is produced by calling {@link Builder#getOperationDescription()}
+ * during object construction. This method is used in {@link #hashCode} and
+ * {@link #equals}.
+ * @see Builder#getOperationDescription()
*/
@Override
- public String toString() {
- return getOperationDescription().toString();
+ public final String toString() {
+ return this.toString;
}
+
/**
- * Return an identifying description for this caching operation.
- * <p>Available to subclasses, for inclusion in their {@code toString()} result.
+ * @since 4.3
*/
- protected StringBuilder getOperationDescription() {
- StringBuilder result = new StringBuilder(getClass().getSimpleName());
- result.append("[").append(this.name);
- result.append("] caches=").append(this.cacheNames);
- result.append(" | key='").append(this.key);
- result.append("' | keyGenerator='").append(this.keyGenerator);
- result.append("' | cacheManager='").append(this.cacheManager);
- result.append("' | cacheResolver='").append(this.cacheResolver);
- result.append("' | condition='").append(this.condition).append("'");
- return result;
+ public abstract static class Builder {
+
+ private String name = "";
+
+ private Set<String> cacheNames = Collections.emptySet();
+
+ private String key = "";
+
+ private String keyGenerator = "";
+
+ private String cacheManager = "";
+
+ private String cacheResolver = "";
+
+ private String condition = "";
+
+ public void setName(String name) {
+ Assert.hasText(name);
+ this.name = name;
+ }
+
+ public void setCacheName(String cacheName) {
+ Assert.hasText(cacheName);
+ this.cacheNames = Collections.singleton(cacheName);
+ }
+
+ public void setCacheNames(String... cacheNames) {
+ this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
+ for (String cacheName : cacheNames) {
+ Assert.hasText(cacheName, "Cache name must be non-null if specified");
+ this.cacheNames.add(cacheName);
+ }
+ }
+
+ public Set<String> getCacheNames() {
+ return this.cacheNames;
+ }
+
+ public void setKey(String key) {
+ Assert.notNull(key);
+ this.key = key;
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public String getKeyGenerator() {
+ return this.keyGenerator;
+ }
+
+ public String getCacheManager() {
+ return this.cacheManager;
+ }
+
+ public String getCacheResolver() {
+ return this.cacheResolver;
+ }
+
+ public void setKeyGenerator(String keyGenerator) {
+ Assert.notNull(keyGenerator);
+ this.keyGenerator = keyGenerator;
+ }
+
+ public void setCacheManager(String cacheManager) {
+ Assert.notNull(cacheManager);
+ this.cacheManager = cacheManager;
+ }
+
+ public void setCacheResolver(String cacheResolver) {
+ Assert.notNull(this.cacheManager);
+ this.cacheResolver = cacheResolver;
+ }
+
+ public void setCondition(String condition) {
+ Assert.notNull(condition);
+ this.condition = condition;
+ }
+
+ /**
+ * Return an identifying description for this caching operation.
+ * <p>Available to subclasses, for inclusion in their {@code toString()} result.
+ */
+ protected StringBuilder getOperationDescription() {
+ StringBuilder result = new StringBuilder(getClass().getSimpleName());
+ result.append("[").append(this.name);
+ result.append("] caches=").append(this.cacheNames);
+ result.append(" | key='").append(this.key);
+ result.append("' | keyGenerator='").append(this.keyGenerator);
+ result.append("' | cacheManager='").append(this.cacheManager);
+ result.append("' | cacheResolver='").append(this.cacheResolver);
+ result.append("' | condition='").append(this.condition).append("'");
+ return result;
+ }
+
+ public abstract CacheOperation build();
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java
index 88ed1484..9111bab0 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -22,11 +22,11 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.cache.Cache;
import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.CachedExpressionEvaluator;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -43,7 +43,7 @@ import org.springframework.expression.Expression;
* @author Stephane Nicoll
* @since 3.1
*/
-class ExpressionEvaluator extends CachedExpressionEvaluator {
+class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {
/**
* Indicate that there is no result variable.
@@ -60,8 +60,6 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
*/
public static final String RESULT_VARIABLE = "result";
- // shared param discoverer since it caches data internally
- private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
@@ -75,12 +73,12 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
/**
* Create an {@link EvaluationContext} without a return value.
- * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object)
+ * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object, BeanFactory)
*/
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
- Method method, Object[] args, Object target, Class<?> targetClass) {
+ Method method, Object[] args, Object target, Class<?> targetClass, BeanFactory beanFactory) {
- return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT);
+ return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT, beanFactory);
}
/**
@@ -95,19 +93,23 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
* @return the evaluation context
*/
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
- Method method, Object[] args, Object target, Class<?> targetClass, Object result) {
+ Method method, Object[] args, Object target, Class<?> targetClass, Object result,
+ BeanFactory beanFactory) {
- CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches,
- method, args, target, targetClass);
+ CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
+ caches, method, args, target, targetClass);
Method targetMethod = getTargetMethod(targetClass, method);
- CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject,
- targetMethod, args, this.paramNameDiscoverer);
+ CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
+ rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
return evaluationContext;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
index 8901ba19..3529438d 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
@@ -30,9 +30,8 @@ package org.springframework.cache.interceptor;
public interface CacheOperationInvoker {
/**
- * Invoke the cache operation defined by this instance. Wraps any
- * exception that is thrown during the invocation in a
- * {@link ThrowableWrapper}.
+ * Invoke the cache operation defined by this instance. Wraps any exception
+ * that is thrown during the invocation in a {@link ThrowableWrapper}.
* @return the result of the operation
* @throws ThrowableWrapper if an error occurred while invoking the operation
*/
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
index e6a61b0a..01cf15f1 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -21,27 +21,51 @@ package org.springframework.cache.interceptor;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CachePutOperation extends CacheOperation {
- private String unless;
+ private final String unless;
- public String getUnless() {
- return unless;
+ /**
+ * @since 4.3
+ */
+ public CachePutOperation(CachePutOperation.Builder b) {
+ super(b);
+ this.unless = b.unless;
}
- public void setUnless(String unless) {
- this.unless = unless;
+
+ public String getUnless() {
+ return this.unless;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(" | unless='");
- sb.append(this.unless);
- sb.append("'");
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private String unless;
+
+ public void setUnless(String unless) {
+ this.unless = unless;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(" | unless='");
+ sb.append(this.unless);
+ sb.append("'");
+ return sb;
+ }
+
+ public CachePutOperation build() {
+ return new CachePutOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
index f9375a9a..c3414e4d 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -21,27 +21,68 @@ package org.springframework.cache.interceptor;
*
* @author Costin Leau
* @author Phillip Webb
+ * @author Marcin Kamionowski
* @since 3.1
*/
public class CacheableOperation extends CacheOperation {
- private String unless;
+ private final String unless;
+
+ private final boolean sync;
+
+
+ /**
+ * @since 4.3
+ */
+ public CacheableOperation(CacheableOperation.Builder b) {
+ super(b);
+ this.unless = b.unless;
+ this.sync = b.sync;
+ }
public String getUnless() {
- return unless;
+ return this.unless;
}
- public void setUnless(String unless) {
- this.unless = unless;
+ public boolean isSync() {
+ return this.sync;
}
- @Override
- protected StringBuilder getOperationDescription() {
- StringBuilder sb = super.getOperationDescription();
- sb.append(" | unless='");
- sb.append(this.unless);
- sb.append("'");
- return sb;
+
+ /**
+ * @since 4.3
+ */
+ public static class Builder extends CacheOperation.Builder {
+
+ private String unless;
+
+ private boolean sync;
+
+ public void setUnless(String unless) {
+ this.unless = unless;
+ }
+
+ public void setSync(boolean sync) {
+ this.sync = sync;
+ }
+
+ @Override
+ protected StringBuilder getOperationDescription() {
+ StringBuilder sb = super.getOperationDescription();
+ sb.append(" | unless='");
+ sb.append(this.unless);
+ sb.append("'");
+ sb.append(" | sync='");
+ sb.append(this.sync);
+ sb.append("'");
+ return sb;
+ }
+
+ @Override
+ public CacheableOperation build() {
+ return new CacheableOperation(this);
+ }
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
index 51e95c15..f75fd2e8 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -51,7 +51,7 @@ public class NamedCacheResolver extends AbstractCacheResolver {
@Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
- return cacheNames;
+ return this.cacheNames;
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
index b9304539..e6573045 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -57,7 +57,7 @@ public class SimpleKey implements Serializable {
@Override
public final int hashCode() {
- return hashCode;
+ return this.hashCode;
}
@Override
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
index b6f52d61..6f7fdde3 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -37,6 +37,6 @@ class VariableNotAvailableException extends EvaluationException {
public String getName() {
- return name;
+ return this.name;
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
index 87ab3798..85a1168f 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
@@ -131,7 +131,9 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
/**
* Dynamically register an additional Cache with this manager.
* @param cache the Cache to register
+ * @deprecated as of Spring 4.3, in favor of {@link #getMissingCache(String)}
*/
+ @Deprecated
protected final void addCache(Cache cache) {
String name = cache.getName();
synchronized (this.cacheMap) {
@@ -171,10 +173,10 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
/**
* Return a missing cache with the specified {@code name} or {@code null} if
* such cache does not exist or could not be created on the fly.
- * <p>Some caches may be created at runtime in the native provided. If a lookup
- * by name does not yield any result, a subclass gets a chance to register
- * such a cache at runtime. The returned cache will be automatically added to
- * this instance.
+ * <p>Some caches may be created at runtime if the native provider supports
+ * it. If a lookup by name does not yield any result, a subclass gets a chance
+ * to register such a cache at runtime. The returned cache will be automatically
+ * added to this instance.
* @param name the name of the cache to retrieve
* @return the missing cache or {@code null} if no such cache exists or could be
* created
diff --git a/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
index 0b486804..c27f59e1 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/NoOpCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -100,6 +101,16 @@ public class NoOpCacheManager implements CacheManager {
}
@Override
+ public <T> T get(Object key, Callable<T> valueLoader) {
+ try {
+ return valueLoader.call();
+ }
+ catch (Exception ex) {
+ throw new ValueRetrievalException(key, valueLoader, ex);
+ }
+ }
+
+ @Override
public String getName() {
return this.name;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/support/package-info.java b/spring-context/src/main/java/org/springframework/cache/support/package-info.java
index 38c95daf..cf3f5d4f 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/package-info.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/package-info.java
@@ -1,5 +1,5 @@
/**
- * Support classes for the the org.springframework.cache package.
+ * Support classes for the org.springframework.cache package.
* Provides abstract classes for cache managers and caches.
*/
package org.springframework.cache.support;
diff --git a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
index 09048cbe..7cfeb9e2 100644
--- a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
+import org.springframework.core.io.ProtocolResolver;
/**
* SPI interface to be implemented by most if not all application contexts.
@@ -113,9 +114,9 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
* Add a new BeanFactoryPostProcessor that will get applied to the internal
* bean factory of this application context on refresh, before any of the
* bean definitions get evaluated. To be invoked during context configuration.
- * @param beanFactoryPostProcessor the factory processor to register
+ * @param postProcessor the factory processor to register
*/
- void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor);
+ void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
/**
* Add a new ApplicationListener that will be notified on context events
@@ -130,6 +131,15 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
void addApplicationListener(ApplicationListener<?> listener);
/**
+ * Register the given protocol resolver with this application context,
+ * allowing for additional resource protocols to be handled.
+ * <p>Any such resolver will be invoked ahead of this context's standard
+ * resolution rules. It may therefore also override any default rules.
+ * @since 4.3
+ */
+ void addProtocolResolver(ProtocolResolver resolver);
+
+ /**
* Load or refresh the persistent representation of the configuration,
* which might an XML file, properties file, or relational database schema.
* <p>As this is a startup method, it should destroy already created singletons
diff --git a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
index 98d25eaa..a3345c2c 100644
--- a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
+++ b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -29,7 +29,9 @@ import org.springframework.util.StringValueResolver;
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0.3
- * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue(String)
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanExpressionResolver()
+ * @see org.springframework.beans.factory.config.EmbeddedValueResolver
*/
public interface EmbeddedValueResolverAware extends Aware {
diff --git a/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java b/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
index 57df5694..619e334a 100644
--- a/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
+++ b/spring-context/src/main/java/org/springframework/context/access/ContextSingletonBeanFactoryLocator.java
@@ -70,7 +70,7 @@ public class ContextSingletonBeanFactoryLocator extends SingletonBeanFactoryLoca
}
/**
- * Returns an instance which uses the the specified selector, as the name of the
+ * Returns an instance which uses the specified selector, as the name of the
* definition file(s). In the case of a name with a Spring "classpath*:" prefix,
* or with no prefix, which is treated the same, the current thread's context class
* loader's {@code getResources} method will be called with this value to get
@@ -112,7 +112,7 @@ public class ContextSingletonBeanFactoryLocator extends SingletonBeanFactoryLoca
/**
- * Constructor which uses the the specified name as the resource name
+ * Constructor which uses the specified name as the resource name
* of the definition file(s).
* @param resourceLocation the Spring resource location to use
* (either a URL or a "classpath:" / "classpath*:" pseudo URL)
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
index 780f8be0..4c16bb0e 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -127,15 +127,13 @@ public class AnnotatedBeanDefinitionReader {
registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
}
- public void registerBean(Class<?> annotatedClass,
- @SuppressWarnings("unchecked") Class<? extends Annotation>... qualifiers) {
-
+ @SuppressWarnings("unchecked")
+ public void registerBean(Class<?> annotatedClass, Class<? extends Annotation>... qualifiers) {
registerBean(annotatedClass, null, qualifiers);
}
- public void registerBean(Class<?> annotatedClass, String name,
- @SuppressWarnings("unchecked") Class<? extends Annotation>... qualifiers) {
-
+ @SuppressWarnings("unchecked")
+ public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
index d7d86e96..ddbf312a 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -79,9 +79,10 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
- AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);
+ AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
+ annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
- metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource()));
+ metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
index c73fcc7a..5ec21e9c 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -27,6 +27,7 @@ import org.springframework.core.type.AnnotationMetadata;
* as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see EnableAspectJAutoProxy
*/
@@ -43,11 +44,14 @@ class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
- AnnotationAttributes enableAJAutoProxy =
+ AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
- if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
+ if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
+ if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
+ AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
index cc5c911c..58b54814 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -79,7 +79,7 @@ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
logger.warn(String.format("%s was imported but no annotations were found " +
"having both 'mode' and 'proxyTargetClass' attributes of type " +
"AdviceMode and boolean respectively. This means that auto proxy " +
- "creator registration and configuration may not have occured as " +
+ "creator registration and configuration may not have occurred as " +
"intended, and components may not be proxied as expected. Check to " +
"ensure that %s has been @Import'ed on the same class where these " +
"annotations are declared; otherwise remove the import of %s " +
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
index 626a328f..b28ab049 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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,22 +18,19 @@ package org.springframework.context.annotation;
import java.lang.reflect.Method;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
/**
* Utilities for processing {@link Bean}-annotated methods.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
class BeanAnnotationHelper {
- /**
- * Return whether the given method is directly or indirectly annotated with
- * the {@link Bean} annotation.
- */
public static boolean isBeanAnnotated(Method method) {
- return (AnnotationUtils.findAnnotation(method, Bean.class) != null);
+ return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}
public static String determineBeanNameFor(Method beanMethod) {
@@ -41,7 +38,7 @@ class BeanAnnotationHelper {
String beanName = beanMethod.getName();
// Check to see if the user has explicitly set a custom bean name...
- Bean bean = AnnotationUtils.findAnnotation(beanMethod, Bean.class);
+ Bean bean = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Bean.class);
if (bean != null && bean.name().length > 0) {
beanName = bean.name()[0];
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
index b811d52d..8e90baa5 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
@@ -92,7 +92,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* implementations.
* <p>If given a plain {@code BeanDefinitionRegistry}, the default {@code ResourceLoader}
* will be a {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
- * <p>If the the passed-in bean factory also implements {@link EnvironmentCapable} its
+ * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
* environment will be used by this reader. Otherwise, the reader will initialize and
* use a {@link org.springframework.core.env.StandardEnvironment}. All
* {@code ApplicationContext} implementations are {@code EnvironmentCapable}, while
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
index f621ea24..c3dbc027 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.BridgeMethodResolver;
@@ -68,6 +69,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
@@ -140,6 +142,9 @@ import org.springframework.util.StringUtils;
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
+ // Common Annotations 1.1 Resource.lookup() available? Not present on JDK 6...
+ private static final Method lookupAttribute = ClassUtils.getMethodIfAvailable(Resource.class, "lookup");
+
private static Class<? extends Annotation> webServiceRefClass = null;
private static Class<? extends Annotation> ejbRefClass = null;
@@ -178,6 +183,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private transient BeanFactory beanFactory;
+ private transient StringValueResolver embeddedValueResolver;
+
private transient final Map<String, InjectionMetadata> injectionMetadataCache =
new ConcurrentHashMap<String, InjectionMetadata>(256);
@@ -271,12 +278,15 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
@Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ public void setBeanFactory(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
if (this.resourceFactory == null) {
this.resourceFactory = beanFactory;
}
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
+ }
}
@@ -592,8 +602,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
- else if (beanFactory instanceof ConfigurableBeanFactory){
- resourceName = ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(resourceName);
+ else if (embeddedValueResolver != null) {
+ resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (resourceType != null && Object.class != resourceType) {
checkResourceType(resourceType);
@@ -604,7 +614,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
this.name = resourceName;
this.lookupType = resourceType;
- this.mappedName = resource.mappedName();
+ String lookupValue = (lookupAttribute != null ?
+ (String) ReflectionUtils.invokeMethod(lookupAttribute, resource) : null);
+ this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
index f7ad662c..4d0d9464 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
@@ -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;
@@ -54,6 +55,7 @@ import org.springframework.core.type.filter.TypeFilter;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
+@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
@@ -187,6 +189,15 @@ public @interface ComponentScan {
* </table>
* <p>When multiple classes are specified, <em>OR</em> logic is applied
* &mdash; for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
+ * <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the
+ * following {@link org.springframework.beans.factory.Aware Aware} interfaces, and
+ * their respective methods will be called prior to {@link TypeFilter#match match}:
+ * <ul>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
+ * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
+ * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
+ * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
+ * </ul>
* <p>Specifying zero classes is permitted but will have no effect on component
* scanning.
* @since 4.2
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
index 1119012b..247c5838 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -25,10 +25,17 @@ import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.Aware;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
@@ -115,7 +122,7 @@ class ComponentScanAnnotationParser {
}
Set<String> basePackages = new LinkedHashSet<String>();
- String[] basePackagesArray = componentScan.getAliasedStringArray("basePackages", ComponentScan.class, declaringClass);
+ String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
@@ -141,11 +148,11 @@ class ComponentScanAnnotationParser {
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
FilterType filterType = filterAttributes.getEnum("type");
- for (Class<?> filterClass : filterAttributes.getAliasedClassArray("classes", ComponentScan.Filter.class, null)) {
+ for (Class<?> filterClass : filterAttributes.getClassArray("classes")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
- "An error occured while processing a @ComponentScan ANNOTATION type filter: ");
+ "An error occurred while processing a @ComponentScan ANNOTATION type filter: ");
@SuppressWarnings("unchecked")
Class<Annotation> annotationType = (Class<Annotation>) filterClass;
typeFilters.add(new AnnotationTypeFilter(annotationType));
@@ -155,8 +162,10 @@ class ComponentScanAnnotationParser {
break;
case CUSTOM:
Assert.isAssignable(TypeFilter.class, filterClass,
- "An error occured while processing a @ComponentScan CUSTOM type filter: ");
- typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
+ "An error occurred while processing a @ComponentScan CUSTOM type filter: ");
+ TypeFilter filter = BeanUtils.instantiateClass(filterClass, TypeFilter.class);
+ invokeAwareMethods(filter);
+ typeFilters.add(filter);
break;
default:
throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
@@ -179,4 +188,27 @@ class ComponentScanAnnotationParser {
return typeFilters;
}
+ /**
+ * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
+ * {@link BeanFactoryAware} contracts if implemented by the given {@code filter}.
+ */
+ private void invokeAwareMethods(TypeFilter filter) {
+ if (filter instanceof Aware) {
+ if (filter instanceof EnvironmentAware) {
+ ((EnvironmentAware) filter).setEnvironment(this.environment);
+ }
+ if (filter instanceof ResourceLoaderAware) {
+ ((ResourceLoaderAware) filter).setResourceLoader(this.resourceLoader);
+ }
+ if (filter instanceof BeanClassLoaderAware) {
+ ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
+ ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
+ this.resourceLoader.getClassLoader());
+ ((BeanClassLoaderAware) filter).setBeanClassLoader(classLoader);
+ }
+ if (filter instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
+ ((BeanFactoryAware) filter).setBeanFactory((BeanFactory) this.registry);
+ }
+ }
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java
new file mode 100644
index 00000000..c9498556
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2015 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 ComponentScan} annotations.
+ *
+ * <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
+ * Can also be used in conjunction with Java 8's support for repeatable annotations,
+ * where {@link ComponentScan} can simply be declared several times on the same method,
+ * implicitly generating this container annotation.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ComponentScan
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface ComponentScans {
+
+ ComponentScan[] value();
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
index 5cb5c0f3..7fa587fa 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -103,7 +103,7 @@ final class ConfigurationClass {
public ConfigurationClass(Class<?> clazz, String beanName) {
Assert.hasText(beanName, "Bean name must not be null");
this.metadata = new StandardAnnotationMetadata(clazz, true);
- this.resource = new DescriptiveResource(clazz.toString());
+ this.resource = new DescriptiveResource(clazz.getName());
this.beanName = beanName;
}
@@ -117,7 +117,7 @@ final class ConfigurationClass {
*/
public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
this.metadata = new StandardAnnotationMetadata(clazz, true);
- this.resource = new DescriptiveResource(clazz.toString());
+ this.resource = new DescriptiveResource(clazz.getName());
this.importedBy.add(importedBy);
}
@@ -233,7 +233,7 @@ final class ConfigurationClass {
@Override
public String toString() {
- return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource;
+ return "ConfigurationClass: beanName '" + this.beanName + "', " + this.resource;
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
index 9031872e..522acf33 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
@@ -236,7 +236,7 @@ class ConfigurationClassBeanDefinitionReader {
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
- beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
+ beanDef.setScope(attributes.getString("value"));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
index 4aea07f9..63497ff9 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
@@ -45,7 +45,7 @@ import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cglib.transform.ClassEmitterTransformer;
import org.springframework.cglib.transform.TransformingClassGenerator;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.objenesis.ObjenesisException;
import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.Assert;
@@ -311,7 +311,7 @@ class ConfigurationClassEnhancer {
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// Determine whether this bean is a scoped-proxy
- Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class);
+ Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
@@ -466,7 +466,7 @@ class ConfigurationClassEnhancer {
try {
fbProxy = fbClass.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate enhanced FactoryBean using Objenesis, " +
"and regular FactoryBean instantiation via default constructor fails as well", ex);
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
index 023371b4..46d98634 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
@@ -66,6 +66,9 @@ import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
@@ -102,6 +105,8 @@ import org.springframework.util.StringUtils;
*/
class ConfigurationClassParser {
+ private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
+
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
@Override
@@ -177,7 +182,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
@@ -261,15 +266,18 @@ class ConfigurationClassParser {
}
// Process any @ComponentScan annotations
- AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class);
- if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
- // The config class is annotated with @ComponentScan -> perform the scan immediately
- Set<BeanDefinitionHolder> scannedBeanDefinitions =
- this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
- // Check the set of scanned definitions for any further config classes and parse recursively if necessary
- for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
- if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
- parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
+ Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
+ sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
+ if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
+ for (AnnotationAttributes componentScan : componentScans) {
+ // The config class is annotated with @ComponentScan -> perform the scan immediately
+ Set<BeanDefinitionHolder> scannedBeanDefinitions =
+ this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
+ // Check the set of scanned definitions for any further config classes and parse recursively if necessary
+ for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
+ if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
+ parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
+ }
}
}
}
@@ -279,8 +287,9 @@ class ConfigurationClassParser {
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
- AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
- String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
+ AnnotationAttributes importResource =
+ AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
+ String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
@@ -357,16 +366,26 @@ class ConfigurationClassParser {
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
+ if (!StringUtils.hasLength(name)) {
+ name = null;
+ }
+ String encoding = propertySource.getString("encoding");
+ if (!StringUtils.hasLength(encoding)) {
+ encoding = null;
+ }
String[] locations = propertySource.getStringArray("value");
- boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
+ boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
+
+ Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
+ PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
+ DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
+
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
- ResourcePropertySource rps = (StringUtils.hasText(name) ?
- new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
- addPropertySource(rps);
+ addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException ex) {
// from resolveRequiredPlaceholders
@@ -383,21 +402,23 @@ class ConfigurationClassParser {
}
}
- private void addPropertySource(ResourcePropertySource propertySource) {
+ private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name);
+ PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
+ ((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
- ((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName());
+ ((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
- composite.addPropertySource(propertySource.withResourceName());
+ composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
@@ -463,7 +484,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
@@ -477,7 +498,7 @@ class ConfigurationClassParser {
return;
}
- if (checkForCircularImports && this.importStack.contains(configClass)) {
+ if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
@@ -520,7 +541,7 @@ class ConfigurationClassParser {
catch (BeanDefinitionStoreException ex) {
throw ex;
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
@@ -530,6 +551,20 @@ class ConfigurationClassParser {
}
}
+ private boolean isChainedImportOnStack(ConfigurationClass configClass) {
+ if (this.importStack.contains(configClass)) {
+ String configClassName = configClass.getMetadata().getClassName();
+ AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
+ while (importingClass != null) {
+ if (configClassName.equals(importingClass.getClassName())) {
+ return true;
+ }
+ importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
+ }
+ }
+ return false;
+ }
+
/**
* Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
* {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
@@ -836,7 +871,7 @@ class ConfigurationClassParser {
private SourceClass getRelated(String className) throws IOException {
if (this.source instanceof Class<?>) {
try {
- Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
+ Class<?> clazz = ((Class<?>) this.source).getClassLoader().loadClass(className);
return asSourceClass(clazz);
}
catch (ClassNotFoundException ex) {
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java b/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
index e2d652c8..bb82665f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/EnableAspectJAutoProxy.java
@@ -102,6 +102,7 @@ import java.lang.annotation.Target;
* }</pre>
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see org.aspectj.lang.annotation.Aspect
*/
@@ -117,4 +118,12 @@ public @interface EnableAspectJAutoProxy {
*/
boolean proxyTargetClass() default false;
+ /**
+ * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
+ * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
+ * Off by default, i.e. no guarantees that {@code AopContext} access will work.
+ * @since 4.3.1
+ */
+ boolean exposeProxy() default false;
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
index 6795d617..1dce3d41 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java
@@ -33,7 +33,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #registerBeanDefinitions}:
* <ul>
- * <li>{@link org.springframework.context.EnvironmentAware}</li>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
index b98719f9..c684426f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
@@ -27,7 +27,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #selectImports}:
* <ul>
- * <li>{@link org.springframework.context.EnvironmentAware}</li>
+ * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
index 908f70a7..e4237110 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.core.io.support.PropertySourceFactory;
+
/**
* Annotation providing a convenient and declarative mechanism for adding a
* {@link org.springframework.core.env.PropertySource PropertySource} to Spring's
@@ -138,6 +140,7 @@ import java.lang.annotation.Target;
* javadocs for details.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1
* @see PropertySources
@@ -153,9 +156,8 @@ import java.lang.annotation.Target;
public @interface PropertySource {
/**
- * Indicate the name of this property source. If omitted, a name
- * will be generated based on the description of the underlying
- * resource.
+ * Indicate the name of this property source. If omitted, a name will
+ * be generated based on the description of the underlying resource.
* @see org.springframework.core.env.PropertySource#getName()
* @see org.springframework.core.io.Resource#getDescription()
*/
@@ -184,4 +186,19 @@ public @interface PropertySource {
*/
boolean ignoreResourceNotFound() default false;
+ /**
+ * A specific character encoding for the given resources, e.g. "UTF-8".
+ * @since 4.3
+ */
+ String encoding() default "";
+
+ /**
+ * Specify a custom {@link PropertySourceFactory}, if any.
+ * <p>By default, a default factory for standard resource files will be used.
+ * @since 4.3
+ * @see org.springframework.core.io.support.DefaultPropertySourceFactory
+ * @see org.springframework.core.io.support.ResourcePropertySource
+ */
+ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
index 5fb8af96..ae717b09 100644
--- a/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,6 +19,7 @@ package org.springframework.context.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -36,15 +37,22 @@ import org.springframework.util.ClassUtils;
*/
class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
- private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class";
+ /**
+ * The bean name of the internally managed AspectJ weaving enabler.
+ * @since 4.3.1
+ */
+ public static final String ASPECTJ_WEAVING_ENABLER_BEAN_NAME =
+ "org.springframework.context.config.internalAspectJWeavingEnabler";
- private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving";
+ private static final String ASPECTJ_WEAVING_ENABLER_CLASS_NAME =
+ "org.springframework.context.weaving.AspectJWeavingEnabler";
private static final String DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME =
"org.springframework.context.weaving.DefaultContextLoadTimeWeaver";
- private static final String ASPECTJ_WEAVING_ENABLER_CLASS_NAME =
- "org.springframework.context.weaving.AspectJWeavingEnabler";
+ private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class";
+
+ private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving";
@Override
@@ -65,9 +73,11 @@ class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionPar
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (isAspectJWeavingEnabled(element.getAttribute(ASPECTJ_WEAVING_ATTRIBUTE), parserContext)) {
- RootBeanDefinition weavingEnablerDef = new RootBeanDefinition();
- weavingEnablerDef.setBeanClassName(ASPECTJ_WEAVING_ENABLER_CLASS_NAME);
- parserContext.getReaderContext().registerWithGeneratedName(weavingEnablerDef);
+ if (!parserContext.getRegistry().containsBeanDefinition(ASPECTJ_WEAVING_ENABLER_BEAN_NAME)) {
+ RootBeanDefinition def = new RootBeanDefinition(ASPECTJ_WEAVING_ENABLER_CLASS_NAME);
+ parserContext.registerBeanComponent(
+ new BeanComponentDefinition(def, ASPECTJ_WEAVING_ENABLER_BEAN_NAME));
+ }
if (isBeanConfigurerAspectEnabled(parserContext.getReaderContext().getBeanClassLoader())) {
new SpringConfiguredBeanDefinitionParser().parse(element, parserContext);
diff --git a/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
index f2801597..cb38e6b0 100644
--- a/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -69,7 +69,9 @@ class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBea
if (element.hasAttribute("value-separator")) {
builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
}
-
+ if (element.hasAttribute("trim-values")) {
+ builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
+ }
if (element.hasAttribute("null-value")) {
builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
}
diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
index 0eb68e9e..b7dd9186 100644
--- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
+++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -286,7 +286,7 @@ public abstract class AbstractApplicationEventMulticaster
/**
* Cache key for ListenerRetrievers, based on event type and source type.
*/
- private static class ListenerCacheKey {
+ private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
private final ResolvableType eventType;
@@ -311,6 +311,23 @@ public abstract class AbstractApplicationEventMulticaster
public int hashCode() {
return (ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType));
}
+
+ @Override
+ public String toString() {
+ return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType.getName() + "]";
+ }
+
+ @Override
+ public int compareTo(ListenerCacheKey other) {
+ int result = 0;
+ if (this.eventType != null) {
+ result = this.eventType.toString().compareTo(other.eventType.toString());
+ }
+ if (result == 0 && this.sourceType != null) {
+ result = this.sourceType.getName().compareTo(other.sourceType.getName());
+ }
+ return result;
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
index 243d1642..a57eb0c6 100644
--- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
+++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -35,7 +35,6 @@ import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
@@ -207,14 +206,14 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
if (StringUtils.hasText(condition)) {
Assert.notNull(this.evaluator, "EventExpressionEvaluator must no be null");
EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(
- event, this.targetClass, this.method, args);
+ event, this.targetClass, this.method, args, this.applicationContext);
return this.evaluator.condition(condition, this.methodKey, evaluationContext);
}
return true;
}
protected <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
- return AnnotationUtils.findAnnotation(this.method, annotationType);
+ return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
}
/**
diff --git a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
index 9bf4fcf6..8cb00fe8 100644
--- a/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -21,12 +21,12 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.expression.AnnotatedElementKey;
+import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -40,23 +40,26 @@ import org.springframework.expression.Expression;
*/
class EventExpressionEvaluator extends CachedExpressionEvaluator {
- // shared param discoverer since it caches data internally
- private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
-
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<AnnotatedElementKey, Method>(64);
+
/**
* Create the suitable {@link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
- Method method, Object[] args) {
+ Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
- return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
+ MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
+ root, targetMethod, args, getParameterNameDiscoverer());
+ if (beanFactory != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
+ }
+ return evaluationContext;
}
/**
diff --git a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
index e24cac8d..7a95f172 100644
--- a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java
@@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyUtils;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -38,8 +39,8 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.MethodIntrospector;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -131,7 +132,7 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
new MethodIntrospector.MetadataLookup<EventListener>() {
@Override
public EventListener inspect(Method method) {
- return AnnotationUtils.findAnnotation(method, EventListener.class);
+ return AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class);
}
});
}
@@ -152,7 +153,7 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
- Method methodToUse = MethodIntrospector.selectInvocableMethod(
+ Method methodToUse = AopUtils.selectInvocableMethod(
method, this.applicationContext.getType(beanName));
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
diff --git a/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
index 8f7f88a9..50812c11 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/AnnotatedElementKey.java
@@ -30,7 +30,7 @@ import org.springframework.util.ObjectUtils;
* @since 4.2
* @see CachedExpressionEvaluator
*/
-public final class AnnotatedElementKey {
+public final class AnnotatedElementKey implements Comparable<AnnotatedElementKey> {
private final AnnotatedElement element;
@@ -66,4 +66,18 @@ public final class AnnotatedElementKey {
return this.element.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0);
}
+ @Override
+ public String toString() {
+ return this.element + (this.targetClass != null ? " on " + this.targetClass : "");
+ }
+
+ @Override
+ public int compareTo(AnnotatedElementKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0 && this.targetClass != null) {
+ result = this.targetClass.getName().compareTo(other.targetClass.getName());
+ }
+ return result;
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
index 4bb20b28..d530db80 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/CachedExpressionEvaluator.java
@@ -18,6 +18,8 @@ package org.springframework.context.expression;
import java.util.Map;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
@@ -35,12 +37,14 @@ public abstract class CachedExpressionEvaluator {
private final SpelExpressionParser parser;
+ private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+
/**
* Create a new instance with the specified {@link SpelExpressionParser}.
*/
protected CachedExpressionEvaluator(SpelExpressionParser parser) {
- Assert.notNull(parser, "Parser must not be null");
+ Assert.notNull(parser, "SpelExpressionParser must not be null");
this.parser = parser;
}
@@ -59,6 +63,14 @@ public abstract class CachedExpressionEvaluator {
return this.parser;
}
+ /**
+ * Return a shared parameter name discoverer which caches data internally.
+ * @since 4.3
+ */
+ protected ParameterNameDiscoverer getParameterNameDiscoverer() {
+ return this.parameterNameDiscoverer;
+ }
+
/**
* Return the {@link Expression} for the specified SpEL value
@@ -84,14 +96,14 @@ public abstract class CachedExpressionEvaluator {
}
- protected static class ExpressionKey {
+ protected static class ExpressionKey implements Comparable<ExpressionKey> {
- private final AnnotatedElementKey key;
+ private final AnnotatedElementKey element;
private final String expression;
- protected ExpressionKey(AnnotatedElementKey key, String expression) {
- this.key = key;
+ protected ExpressionKey(AnnotatedElementKey element, String expression) {
+ this.element = element;
this.expression = expression;
}
@@ -104,13 +116,27 @@ public abstract class CachedExpressionEvaluator {
return false;
}
ExpressionKey otherKey = (ExpressionKey) other;
- return (this.key.equals(otherKey.key) &&
+ return (this.element.equals(otherKey.element) &&
ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
}
@Override
public int hashCode() {
- return this.key.hashCode() + (this.expression != null ? this.expression.hashCode() * 29 : 0);
+ return this.element.hashCode() + (this.expression != null ? this.expression.hashCode() * 29 : 0);
+ }
+
+ @Override
+ public String toString() {
+ return this.element + (this.expression != null ? " with expression \"" + this.expression : "\"");
+ }
+
+ @Override
+ public int compareTo(ExpressionKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0 && this.expression != null) {
+ result = this.expression.compareTo(other.expression);
+ }
+ return result;
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java b/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
index b6d783b2..6c564535 100644
--- a/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java
@@ -34,6 +34,7 @@ import org.springframework.util.ObjectUtils;
* </ol>
*
* @author Stephane Nicoll
+ * @author Sergey Podgurskiy
* @since 4.2
*/
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
@@ -89,7 +90,7 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext {
String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method);
// save parameter names (if discovered)
if (parameterNames != null) {
- for (int i = 0; i < parameterNames.length; i++) {
+ for (int i = 0; i < this.args.length; i++) {
setVariable(parameterNames[i], this.args[i]);
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
index d21d2817..2e462c01 100644
--- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -78,6 +78,7 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringValueResolver;
/**
* Abstract implementation of the {@link org.springframework.context.ApplicationContext}
@@ -461,8 +462,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
@Override
- public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) {
- this.beanFactoryPostProcessors.add(beanFactoryPostProcessor);
+ public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {
+ Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null");
+ this.beanFactoryPostProcessors.add(postProcessor);
}
@@ -476,6 +478,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
+ Assert.notNull(listener, "ApplicationListener must not be null");
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
@@ -676,6 +679,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
+
+ // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
+ // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
+ if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
+ beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
+ beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
+ }
}
/**
@@ -823,6 +833,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
+ // Register a default embedded value resolver if no bean post-processor
+ // (such as a PropertyPlaceholderConfigurer bean) registered any before:
+ // at this point, primarily for resolution in annotation attribute values.
+ if (!beanFactory.hasEmbeddedValueResolver()) {
+ beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
+ @Override
+ public String resolveStringValue(String strVal) {
+ return getEnvironment().resolvePlaceholders(strVal);
+ }
+ });
+ }
+
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java
new file mode 100644
index 00000000..404af2eb
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2002-2016 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.support;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Abstract base class for {@code MessageSource} implementations based on
+ * resource bundle conventions, such as {@link ResourceBundleMessageSource}
+ * and {@link ReloadableResourceBundleMessageSource}. Provides common
+ * configuration methods and corresponding semantic definitions.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ResourceBundleMessageSource
+ * @see ReloadableResourceBundleMessageSource
+ */
+public abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource {
+
+ private final Set<String> basenameSet = new LinkedHashSet<String>(4);
+
+ private String defaultEncoding;
+
+ private boolean fallbackToSystemLocale = true;
+
+ private long cacheMillis = -1;
+
+
+ /**
+ * Set a single basename, following the basic ResourceBundle convention
+ * of not specifying file extension or language codes. The resource location
+ * format is up to the specific {@code MessageSource} implementation.
+ * <p>Regular and XMl properties files are supported: e.g. "messages" will find
+ * a "messages.properties", "messages_en.properties" etc arrangement as well
+ * as "messages.xml", "messages_en.xml" etc.
+ * @param basename the single basename
+ * @see #setBasenames
+ * @see org.springframework.core.io.ResourceEditor
+ * @see java.util.ResourceBundle
+ */
+ public void setBasename(String basename) {
+ setBasenames(basename);
+ }
+
+ /**
+ * Set an array of basenames, each following the basic ResourceBundle convention
+ * of not specifying file extension or language codes. The resource location
+ * format is up to the specific {@code MessageSource} implementation.
+ * <p>Regular and XMl properties files are supported: e.g. "messages" will find
+ * a "messages.properties", "messages_en.properties" etc arrangement as well
+ * as "messages.xml", "messages_en.xml" etc.
+ * <p>The associated resource bundles will be checked sequentially when resolving
+ * a message code. Note that message definitions in a <i>previous</i> resource
+ * bundle will override ones in a later bundle, due to the sequential lookup.
+ * <p>Note: In contrast to {@link #addBasenames}, this replaces existing entries
+ * with the given names and can therefore also be used to reset the configuration.
+ * @param basenames an array of basenames
+ * @see #setBasename
+ * @see java.util.ResourceBundle
+ */
+ public void setBasenames(String... basenames) {
+ this.basenameSet.clear();
+ addBasenames(basenames);
+ }
+
+ /**
+ * Add the specified basenames to the existing basename configuration.
+ * <p>Note: If a given basename already exists, the position of its entry
+ * will remain as in the original set. New entries will be added at the
+ * end of the list, to be searched after existing basenames.
+ * @since 4.3
+ * @see #setBasenames
+ * @see java.util.ResourceBundle
+ */
+ public void addBasenames(String... basenames) {
+ if (!ObjectUtils.isEmpty(basenames)) {
+ for (String basename : basenames) {
+ Assert.hasText(basename, "Basename must not be empty");
+ this.basenameSet.add(basename.trim());
+ }
+ }
+ }
+
+ /**
+ * Return this {@code MessageSource}'s basename set, containing entries
+ * in the order of registration.
+ * <p>Calling code may introspect this set as well as add or remove entries.
+ * @since 4.3
+ * @see #addBasenames
+ */
+ public Set<String> getBasenameSet() {
+ return this.basenameSet;
+ }
+
+ /**
+ * Set the default charset to use for parsing properties files.
+ * Used if no file-specific charset is specified for a file.
+ * <p>Default is none, using the {@code java.util.Properties}
+ * default encoding: ISO-8859-1.
+ * <p>Only applies to classic properties files, not to XML files.
+ * @param defaultEncoding the default charset
+ */
+ public void setDefaultEncoding(String defaultEncoding) {
+ this.defaultEncoding = defaultEncoding;
+ }
+
+ /**
+ * Return the default charset to use for parsing properties files, if any.
+ * @since 4.3
+ */
+ protected String getDefaultEncoding() {
+ return this.defaultEncoding;
+ }
+
+ /**
+ * Set whether to fall back to the system Locale if no files for a specific
+ * Locale have been found. Default is "true"; if this is turned off, the only
+ * fallback will be the default file (e.g. "messages.properties" for
+ * basename "messages").
+ * <p>Falling back to the system Locale is the default behavior of
+ * {@code java.util.ResourceBundle}. However, this is often not desirable
+ * in an application server environment, where the system Locale is not relevant
+ * to the application at all: set this flag to "false" in such a scenario.
+ */
+ public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
+ this.fallbackToSystemLocale = fallbackToSystemLocale;
+ }
+
+ /**
+ * Return whether to fall back to the system Locale if no files for a specific
+ * Locale have been found.
+ * @since 4.3
+ */
+ protected boolean isFallbackToSystemLocale() {
+ return this.fallbackToSystemLocale;
+ }
+
+ /**
+ * Set the number of seconds to cache loaded properties files.
+ * <ul>
+ * <li>Default is "-1", indicating to cache forever (just like
+ * {@code java.util.ResourceBundle}).
+ * <li>A positive number will cache loaded properties files for the given
+ * number of seconds. This is essentially the interval between refresh checks.
+ * Note that a refresh attempt will first check the last-modified timestamp
+ * of the file before actually reloading it; so if files don't change, this
+ * interval can be set rather low, as refresh attempts will not actually reload.
+ * <li>A value of "0" will check the last-modified timestamp of the file on
+ * every message access. <b>Do not use this in a production environment!</b>
+ * </ul>
+ * <p><b>Note that depending on your ClassLoader, expiration might not work reliably
+ * since the ClassLoader may hold on to a cached version of the bundle file.</b>
+ * Prefer {@link ReloadableResourceBundleMessageSource} over
+ * {@link ResourceBundleMessageSource} in such a scenario, in combination with
+ * a non-classpath location.
+ */
+ public void setCacheSeconds(int cacheSeconds) {
+ this.cacheMillis = (cacheSeconds * 1000);
+ }
+
+ /**
+ * Set the number of milliseconds to cache loaded properties files.
+ * Note that it is common to set seconds instead: {@link #setCacheSeconds}.
+ * <ul>
+ * <li>Default is "-1", indicating to cache forever (just like
+ * {@code java.util.ResourceBundle}).
+ * <li>A positive number will cache loaded properties files for the given
+ * number of milliseconds. This is essentially the interval between refresh checks.
+ * Note that a refresh attempt will first check the last-modified timestamp
+ * of the file before actually reloading it; so if files don't change, this
+ * interval can be set rather low, as refresh attempts will not actually reload.
+ * <li>A value of "0" will check the last-modified timestamp of the file on
+ * every message access. <b>Do not use this in a production environment!</b>
+ * </ul>
+ * @since 4.3
+ * @see #setCacheSeconds
+ */
+ public void setCacheMillis(long cacheMillis) {
+ this.cacheMillis = cacheMillis;
+ }
+
+ /**
+ * Return the number of milliseconds to cache loaded properties files.
+ * @since 4.3
+ */
+ protected long getCacheMillis() {
+ return this.cacheMillis;
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
index 2a521b3b..c3f3dc54 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,7 +23,7 @@ import java.security.PrivilegedAction;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ConfigurableApplicationContext;
@@ -61,12 +61,15 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
+ private final StringValueResolver embeddedValueResolver;
+
/**
* Create a new ApplicationContextAwareProcessor for the given context.
*/
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
+ this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
@@ -103,8 +106,7 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
- ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
- new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
+ ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
@@ -126,19 +128,4 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
return bean;
}
-
- private static class EmbeddedValueResolver implements StringValueResolver {
-
- private final ConfigurableBeanFactory beanFactory;
-
- public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
- this.beanFactory = beanFactory;
- }
-
- @Override
- public String resolveStringValue(String strVal) {
- return this.beanFactory.resolveEmbeddedValue(strVal);
- }
- }
-
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
index 9419e14b..521d273f 100644
--- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
@@ -292,7 +292,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
* <p>The default implementation checks for the {@link Phased} interface.
* Can be overridden to apply other/further policies.
* @param bean the bean to introspect
- * @return the phase an an integer value. The suggested default is 0.
+ * @return the phase an integer value. The suggested default is 0.
* @see Phased
* @see SmartLifecycle
*/
diff --git a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
index 70fd2e83..0557b6bd 100644
--- a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
+++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java
@@ -409,6 +409,11 @@ class PostProcessorRegistrationDelegate {
multicaster.removeApplicationListenerBean(beanName);
}
}
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return (bean instanceof ApplicationListener);
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
index 577eb93f..4a12a31e 100644
--- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -167,9 +167,12 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
StringValueResolver valueResolver = new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
- String resolved = ignoreUnresolvablePlaceholders ?
+ String resolved = (ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
- propertyResolver.resolveRequiredPlaceholders(strVal);
+ propertyResolver.resolveRequiredPlaceholders(strVal));
+ if (trimValues) {
+ resolved = resolved.trim();
+ }
return (resolved.equals(nullValue) ? null : resolved);
}
};
diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
index 73de21f4..92fecb1a 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
@@ -33,7 +33,6 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.Assert;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;
import org.springframework.util.StringUtils;
@@ -58,14 +57,14 @@ import org.springframework.util.StringUtils;
* prefix, resources can still be loaded from the classpath, but "cacheSeconds" values
* other than "-1" (caching forever) might not work reliably in this case.
*
- * <p>For a typical web application, message files could be placed into {@code WEB-INF}:
- * e.g. a "WEB-INF/messages" basename would fine a "WEB-INF/messages.properties",
+ * <p>For a typical web application, message files could be placed in {@code WEB-INF}:
+ * e.g. a "WEB-INF/messages" basename would find a "WEB-INF/messages.properties",
* "WEB-INF/messages_en.properties" etc arrangement as well as "WEB-INF/messages.xml",
* "WEB-INF/messages_en.xml" etc. Note that message definitions in a <i>previous</i>
* resource bundle will override ones in a later bundle, due to sequential lookup.
* <p>This MessageSource can easily be used outside of an
- * {@link org.springframework.context.ApplicationContext}: It will use a
+ * {@link org.springframework.context.ApplicationContext}: it will use a
* {@link org.springframework.core.io.DefaultResourceLoader} as default,
* simply getting overridden with the ApplicationContext's resource loader
* if running in a context. It does not have any other specific dependencies.
@@ -85,23 +84,15 @@ import org.springframework.util.StringUtils;
* @see ResourceBundleMessageSource
* @see java.util.ResourceBundle
*/
-public class ReloadableResourceBundleMessageSource extends AbstractMessageSource implements ResourceLoaderAware {
+public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements ResourceLoaderAware {
private static final String PROPERTIES_SUFFIX = ".properties";
private static final String XML_SUFFIX = ".xml";
- private String[] basenames = new String[0];
-
- private String defaultEncoding;
-
private Properties fileEncodings;
- private boolean fallbackToSystemLocale = true;
-
- private long cacheMillis = -1;
-
private boolean concurrentRefresh = true;
private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
@@ -122,71 +113,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
/**
- * Set a single basename, following the basic ResourceBundle convention of
- * not specifying file extension or language codes, but in contrast to
- * {@link ResourceBundleMessageSource} referring to a Spring resource location:
- * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
- * "WEB-INF/messages_en.properties", etc.
- * <p>XML properties files are also supported: .g. "WEB-INF/messages" will find
- * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well.
- * @param basename the single basename
- * @see #setBasenames
- * @see org.springframework.core.io.ResourceEditor
- * @see java.util.ResourceBundle
- */
- public void setBasename(String basename) {
- setBasenames(basename);
- }
-
- /**
- * Set an array of basenames, each following the basic ResourceBundle convention
- * of not specifying file extension or language codes, but in contrast to
- * {@link ResourceBundleMessageSource} referring to a Spring resource location:
- * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
- * "WEB-INF/messages_en.properties", etc.
- * <p>XML properties files are also supported: .g. "WEB-INF/messages" will find
- * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well.
- * <p>The associated resource bundles will be checked sequentially when resolving
- * a message code. Note that message definitions in a <i>previous</i> resource
- * bundle will override ones in a later bundle, due to the sequential lookup.
- * @param basenames an array of basenames
- * @see #setBasename
- * @see java.util.ResourceBundle
- */
- public void setBasenames(String... basenames) {
- if (basenames != null) {
- this.basenames = new String[basenames.length];
- for (int i = 0; i < basenames.length; i++) {
- String basename = basenames[i];
- Assert.hasText(basename, "Basename must not be empty");
- this.basenames[i] = basename.trim();
- }
- }
- else {
- this.basenames = new String[0];
- }
- }
-
- /**
- * Set the default charset to use for parsing properties files.
- * Used if no file-specific charset is specified for a file.
- * <p>Default is none, using the {@code java.util.Properties}
- * default encoding: ISO-8859-1.
- * <p>Only applies to classic properties files, not to XML files.
- * @param defaultEncoding the default charset
- * @see #setFileEncodings
- * @see org.springframework.util.PropertiesPersister#load
- */
- public void setDefaultEncoding(String defaultEncoding) {
- this.defaultEncoding = defaultEncoding;
- }
-
- /**
* Set per-file charsets to use for parsing properties files.
* <p>Only applies to classic properties files, not to XML files.
* @param fileEncodings Properties with filenames as keys and charset
* names as values. Filenames have to match the basename syntax,
- * with optional locale-specific appendices: e.g. "WEB-INF/messages"
+ * with optional locale-specific components: e.g. "WEB-INF/messages"
* or "WEB-INF/messages_en".
* @see #setBasenames
* @see org.springframework.util.PropertiesPersister#load
@@ -196,43 +127,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
/**
- * Set whether to fall back to the system Locale if no files for a specific
- * Locale have been found. Default is "true"; if this is turned off, the only
- * fallback will be the default file (e.g. "messages.properties" for
- * basename "messages").
- * <p>Falling back to the system Locale is the default behavior of
- * {@code java.util.ResourceBundle}. However, this is often not desirable
- * in an application server environment, where the system Locale is not relevant
- * to the application at all: Set this flag to "false" in such a scenario.
- */
- public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
- this.fallbackToSystemLocale = fallbackToSystemLocale;
- }
-
- /**
- * Set the number of seconds to cache loaded properties files.
- * <ul>
- * <li>Default is "-1", indicating to cache forever (just like
- * {@code java.util.ResourceBundle}).
- * <li>A positive number will cache loaded properties files for the given
- * number of seconds. This is essentially the interval between refresh checks.
- * Note that a refresh attempt will first check the last-modified timestamp
- * of the file before actually reloading it; so if files don't change, this
- * interval can be set rather low, as refresh attempts will not actually reload.
- * <li>A value of "0" will check the last-modified timestamp of the file on
- * every message access. <b>Do not use this in a production environment!</b>
- * </ul>
- */
- public void setCacheSeconds(int cacheSeconds) {
- this.cacheMillis = (cacheSeconds * 1000);
- }
-
- /**
* Specify whether to allow for concurrent refresh behavior, i.e. one thread
* locked in a refresh attempt for a specific cached properties file whereas
* other threads keep returning the old properties for the time being, until
* the refresh attempt has completed.
- * <p>Default is "true": This behavior is new as of Spring Framework 4.1,
+ * <p>Default is "true": this behavior is new as of Spring Framework 4.1,
* minimizing contention between threads. If you prefer the old behavior,
* i.e. to fully block on refresh, switch this flag to "false".
* @since 4.1
@@ -273,7 +172,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
*/
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
- if (this.cacheMillis < 0) {
+ if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
String result = propHolder.getProperty(code);
if (result != null) {
@@ -281,7 +180,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
}
else {
- for (String basename : this.basenames) {
+ for (String basename : getBasenameSet()) {
List<String> filenames = calculateAllFilenames(basename, locale);
for (String filename : filenames) {
PropertiesHolder propHolder = getProperties(filename);
@@ -301,7 +200,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
*/
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
- if (this.cacheMillis < 0) {
+ if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
MessageFormat result = propHolder.getMessageFormat(code, locale);
if (result != null) {
@@ -309,7 +208,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
}
else {
- for (String basename : this.basenames) {
+ for (String basename : getBasenameSet()) {
List<String> filenames = calculateAllFilenames(basename, locale);
for (String filename : filenames) {
PropertiesHolder propHolder = getProperties(filename);
@@ -339,8 +238,9 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
Properties mergedProps = newProperties();
mergedHolder = new PropertiesHolder(mergedProps, -1);
- for (int i = this.basenames.length - 1; i >= 0; i--) {
- List<String> filenames = calculateAllFilenames(this.basenames[i], locale);
+ String[] basenames = StringUtils.toStringArray(getBasenameSet());
+ for (int i = basenames.length - 1; i >= 0; i--) {
+ List<String> filenames = calculateAllFilenames(basenames[i], locale);
for (int j = filenames.size() - 1; j >= 0; j--) {
String filename = filenames.get(j);
PropertiesHolder propHolder = getProperties(filename);
@@ -376,7 +276,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
}
List<String> filenames = new ArrayList<String>(7);
filenames.addAll(calculateFilenamesForLocale(basename, locale));
- if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
+ if (isFallbackToSystemLocale() && !locale.equals(Locale.getDefault())) {
List<String> fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
for (String fallbackFilename : fallbackFilenames) {
if (!filenames.contains(fallbackFilename)) {
@@ -447,7 +347,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
if (propHolder != null) {
originalTimestamp = propHolder.getRefreshTimestamp();
- if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - this.cacheMillis) {
+ if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - getCacheMillis()) {
// Up to date
return propHolder;
}
@@ -492,7 +392,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
* @param propHolder the current PropertiesHolder for the bundle
*/
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
- long refreshTimestamp = (this.cacheMillis < 0 ? -1 : System.currentTimeMillis());
+ long refreshTimestamp = (getCacheMillis() < 0 ? -1 : System.currentTimeMillis());
Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
if (!resource.exists()) {
@@ -501,7 +401,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
if (resource.exists()) {
long fileTimestamp = -1;
- if (this.cacheMillis >= 0) {
+ if (getCacheMillis() >= 0) {
// Last-modified timestamp of file will just be read if caching with timeout.
try {
fileTimestamp = resource.lastModified();
@@ -571,7 +471,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
encoding = this.fileEncodings.getProperty(filename);
}
if (encoding == null) {
- encoding = this.defaultEncoding;
+ encoding = getDefaultEncoding();
}
if (encoding != null) {
if (logger.isDebugEnabled()) {
@@ -631,7 +531,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
@Override
public String toString() {
- return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
+ return getClass().getName() + ": basenames=" + getBasenameSet();
}
diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
index 1f623674..d7dce2b5 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
@@ -32,11 +32,10 @@ import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
+import java.util.Set;
import org.springframework.beans.factory.BeanClassLoaderAware;
-import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
-import org.springframework.util.StringUtils;
/**
* {@link org.springframework.context.MessageSource} implementation that
@@ -64,15 +63,7 @@ import org.springframework.util.StringUtils;
* @see java.util.ResourceBundle
* @see java.text.MessageFormat
*/
-public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware {
-
- private String[] basenames = new String[0];
-
- private String defaultEncoding = "ISO-8859-1";
-
- private boolean fallbackToSystemLocale = true;
-
- private long cacheMillis = -1;
+public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements BeanClassLoaderAware {
private ClassLoader bundleClassLoader;
@@ -101,102 +92,6 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
/**
- * Set a single basename, following {@link java.util.ResourceBundle} conventions:
- * essentially, a fully-qualified classpath location. If it doesn't contain a
- * package qualifier (such as {@code org.mypackage}), it will be resolved
- * from the classpath root.
- * <p>Messages will normally be held in the "/lib" or "/classes" directory of
- * a web application's WAR structure. They can also be held in jar files on
- * the class path.
- * <p>Note that ResourceBundle names are effectively classpath locations: As a
- * consequence, the JDK's standard ResourceBundle treats dots as package separators.
- * This means that "test.theme" is effectively equivalent to "test/theme",
- * just like it is for programmatic {@code java.util.ResourceBundle} usage.
- * @see #setBasenames
- * @see java.util.ResourceBundle#getBundle(String)
- */
- public void setBasename(String basename) {
- setBasenames(basename);
- }
-
- /**
- * Set an array of basenames, each following {@link java.util.ResourceBundle}
- * conventions: essentially, a fully-qualified classpath location. If it
- * doesn't contain a package qualifier (such as {@code org.mypackage}),
- * it will be resolved from the classpath root.
- * <p>The associated resource bundles will be checked sequentially
- * when resolving a message code. Note that message definitions in a
- * <i>previous</i> resource bundle will override ones in a later bundle,
- * due to the sequential lookup.
- * <p>Note that ResourceBundle names are effectively classpath locations: As a
- * consequence, the JDK's standard ResourceBundle treats dots as package separators.
- * This means that "test.theme" is effectively equivalent to "test/theme",
- * just like it is for programmatic {@code java.util.ResourceBundle} usage.
- * @see #setBasename
- * @see java.util.ResourceBundle#getBundle(String)
- */
- public void setBasenames(String... basenames) {
- if (basenames != null) {
- this.basenames = new String[basenames.length];
- for (int i = 0; i < basenames.length; i++) {
- String basename = basenames[i];
- Assert.hasText(basename, "Basename must not be empty");
- this.basenames[i] = basename.trim();
- }
- }
- else {
- this.basenames = new String[0];
- }
- }
-
- /**
- * Set the default charset to use for parsing resource bundle files.
- * <p>Default is the {@code java.util.ResourceBundle} default encoding:
- * ISO-8859-1.
- * @since 3.1.3
- */
- public void setDefaultEncoding(String defaultEncoding) {
- this.defaultEncoding = defaultEncoding;
- }
-
- /**
- * Set whether to fall back to the system Locale if no files for a specific
- * Locale have been found. Default is "true"; if this is turned off, the only
- * fallback will be the default file (e.g. "messages.properties" for
- * basename "messages").
- * <p>Falling back to the system Locale is the default behavior of
- * {@code java.util.ResourceBundle}. However, this is often not desirable
- * in an application server environment, where the system Locale is not relevant
- * to the application at all: Set this flag to "false" in such a scenario.
- * @since 3.1.3
- */
- public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
- this.fallbackToSystemLocale = fallbackToSystemLocale;
- }
-
- /**
- * Set the number of seconds to cache loaded resource bundle files.
- * <ul>
- * <li>Default is "-1", indicating to cache forever.
- * <li>A positive number will expire resource bundles after the given
- * number of seconds. This is essentially the interval between refresh checks.
- * Note that a refresh attempt will first check the last-modified timestamp
- * of the file before actually reloading it; so if files don't change, this
- * interval can be set rather low, as refresh attempts will not actually reload.
- * <li>A value of "0" will check the last-modified timestamp of the file on
- * every message access. <b>Do not use this in a production environment!</b>
- * <li><b>Note that depending on your ClassLoader, expiration might not work reliably
- * since the ClassLoader may hold on to a cached version of the bundle file.</b>
- * Consider {@link ReloadableResourceBundleMessageSource} in combination
- * with resource bundle files in a non-classpath location.
- * </ul>
- * @since 3.1.3
- */
- public void setCacheSeconds(int cacheSeconds) {
- this.cacheMillis = (cacheSeconds * 1000);
- }
-
- /**
* Set the ClassLoader to load resource bundles with.
* <p>Default is the containing BeanFactory's
* {@link org.springframework.beans.factory.BeanClassLoaderAware bean ClassLoader},
@@ -229,14 +124,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
- String result = null;
- for (int i = 0; result == null && i < this.basenames.length; i++) {
- ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
+ Set<String> basenames = getBasenameSet();
+ for (String basename : basenames) {
+ ResourceBundle bundle = getResourceBundle(basename, locale);
if (bundle != null) {
- result = getStringOrNull(bundle, code);
+ String result = getStringOrNull(bundle, code);
+ if (result != null) {
+ return result;
+ }
}
}
- return result;
+ return null;
}
/**
@@ -245,14 +143,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
- MessageFormat messageFormat = null;
- for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
- ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
+ Set<String> basenames = getBasenameSet();
+ for (String basename : basenames) {
+ ResourceBundle bundle = getResourceBundle(basename, locale);
if (bundle != null) {
- messageFormat = getMessageFormat(bundle, code, locale);
+ MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
+ if (messageFormat != null) {
+ return messageFormat;
+ }
}
}
- return messageFormat;
+ return null;
}
@@ -265,7 +166,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
* found for the given basename and Locale
*/
protected ResourceBundle getResourceBundle(String basename, Locale locale) {
- if (this.cacheMillis >= 0) {
+ if (getCacheMillis() >= 0) {
// Fresh ResourceBundle.getBundle call in order to let ResourceBundle
// do its native caching, at the expense of more extensive lookup steps.
return doGetBundle(basename, locale);
@@ -404,7 +305,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
*/
@Override
public String toString() {
- return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
+ return getClass().getName() + ": basenames=" + getBasenameSet();
}
@@ -453,8 +354,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
throw (IOException) ex.getException();
}
if (stream != null) {
+ String encoding = getDefaultEncoding();
+ if (encoding == null) {
+ encoding = "ISO-8859-1";
+ }
try {
- return loadBundle(new InputStreamReader(stream, defaultEncoding));
+ return loadBundle(new InputStreamReader(stream, encoding));
}
finally {
stream.close();
@@ -472,11 +377,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
@Override
public Locale getFallbackLocale(String baseName, Locale locale) {
- return (fallbackToSystemLocale ? super.getFallbackLocale(baseName, locale) : null);
+ return (isFallbackToSystemLocale() ? super.getFallbackLocale(baseName, locale) : null);
}
@Override
public long getTimeToLive(String baseName, Locale locale) {
+ long cacheMillis = getCacheMillis();
return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale));
}
diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
index c889df80..3c84ff39 100644
--- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
+++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
@@ -75,6 +75,12 @@ public @interface DateTimeFormat {
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a custom
* date time pattern not represented by a style or ISO format.
+ * <p>Note: This pattern follows the original {@link java.text.SimpleDateFormat} style,
+ * as also supported by Joda-Time, with strict parsing semantics towards overflows
+ * (e.g. rejecting a Feb 29 value for a non-leap-year). As a consequence, 'yy'
+ * characters indicate a year in the traditional style, not a "year-of-era" as in the
+ * {@link java.time.format.DateTimeFormatter} specification (i.e. 'yy' turns into 'uu'
+ * when going through that {@code DateTimeFormatter} with strict resolution mode).
*/
String pattern() default "";
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
index ca895d85..fb4f50b3 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.format.datetime.standard;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
+import java.time.format.ResolverStyle;
import java.util.TimeZone;
import org.springframework.format.annotation.DateTimeFormat.ISO;
@@ -174,7 +175,11 @@ public class DateTimeFormatterFactory {
public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
DateTimeFormatter dateTimeFormatter = null;
if (StringUtils.hasLength(this.pattern)) {
- dateTimeFormatter = DateTimeFormatter.ofPattern(this.pattern);
+ // Using strict parsing to align with Joda-Time and standard DateFormat behavior:
+ // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected.
+ // However, with strict parsing, a year digit needs to be specified as 'u'...
+ String patternToUse = this.pattern.replace("yy", "uu");
+ dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
}
else if (this.iso != null && this.iso != ISO.NONE) {
switch (this.iso) {
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
index ee8d039a..9063f82d 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.format.datetime.standard;
import java.text.ParseException;
import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.springframework.format.Formatter;
@@ -26,18 +27,29 @@ import org.springframework.lang.UsesJava8;
/**
* {@link Formatter} implementation for a JSR-310 {@link java.time.Instant},
* following JSR-310's parsing rules for an Instant (that is, not using a
- * configurable {@link java.time.format.DateTimeFormatter}).
+ * configurable {@link java.time.format.DateTimeFormatter}): accepting the
+ * default {@code ISO_INSTANT} format as well as {@code RFC_1123_DATE_TIME}
+ * (which is commonly used for HTTP date header values), as of Spring 4.3.
*
* @author Juergen Hoeller
* @since 4.0
* @see java.time.Instant#parse
+ * @see java.time.format.DateTimeFormatter#ISO_INSTANT
+ * @see java.time.format.DateTimeFormatter#RFC_1123_DATE_TIME
*/
@UsesJava8
public class InstantFormatter implements Formatter<Instant> {
@Override
public Instant parse(String text, Locale locale) throws ParseException {
- return Instant.parse(text);
+ if (text.length() > 0 && Character.isDigit(text.charAt(0))) {
+ // assuming UTC instant a la "2007-12-03T10:15:30.00Z"
+ return Instant.parse(text);
+ }
+ else {
+ // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
+ return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
+ }
}
@Override
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
index f4fa2cc6..ac807eee 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaver.java
@@ -22,6 +22,8 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.core.DecoratingClassLoader;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -129,7 +131,10 @@ public class ReflectiveLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
if (this.getThrowawayClassLoaderMethod != null) {
- return (ClassLoader) ReflectionUtils.invokeMethod(this.getThrowawayClassLoaderMethod, this.classLoader);
+ ClassLoader target = (ClassLoader)
+ ReflectionUtils.invokeMethod(this.getThrowawayClassLoaderMethod, this.classLoader);
+ return (target instanceof DecoratingClassLoader ? target :
+ new OverridingClassLoader(this.classLoader, target));
}
else {
return new SimpleThrowawayClassLoader(this.classLoader);
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
index 81222f3f..663a7175 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/glassfish/GlassFishLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,6 +20,7 @@ import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -109,7 +110,7 @@ public class GlassFishLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
try {
- return (ClassLoader) this.copyMethod.invoke(this.classLoader);
+ return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
}
catch (InvocationTargetException ex) {
throw new IllegalStateException("GlassFish copy method threw exception", ex.getCause());
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
index 1ef36c95..fe0f7980 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,13 +20,14 @@ import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
- * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation for Tomcat's
- * new {@link org.apache.tomcat.InstrumentableClassLoader InstrumentableClassLoader}.
+ * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation
+ * for Tomcat's new {@code org.apache.tomcat.InstrumentableClassLoader}.
* Also capable of handling Spring's TomcatInstrumentableClassLoader when encountered.
*
* @author Juergen Hoeller
@@ -103,7 +104,7 @@ public class TomcatLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
try {
- return (ClassLoader) this.copyMethod.invoke(this.classLoader);
+ return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
}
catch (InvocationTargetException ex) {
throw new IllegalStateException("Tomcat copy method threw exception", ex.getCause());
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
index 46622173..f4ebb47a 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/weblogic/WebLogicLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.instrument.classloading.weblogic;
import java.lang.instrument.ClassFileTransformer;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -70,6 +71,8 @@ public class WebLogicLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
- return this.classLoader.getThrowawayClassLoader();
+ return new OverridingClassLoader(this.classLoader.getClassLoader(),
+ this.classLoader.getThrowawayClassLoader());
}
+
}
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
index 55c6f863..aeed6929 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/websphere/WebSphereLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.instrument.classloading.websphere;
import java.lang.instrument.ClassFileTransformer;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -67,7 +68,8 @@ public class WebSphereLoadTimeWeaver implements LoadTimeWeaver {
@Override
public ClassLoader getThrowawayClassLoader() {
- return this.classLoader.getThrowawayClassLoader();
+ return new OverridingClassLoader(this.classLoader.getClassLoader(),
+ this.classLoader.getThrowawayClassLoader());
}
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
index fb59a362..9d7c6777 100644
--- a/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/jmx/access/MBeanClientInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -63,6 +63,7 @@ import org.springframework.jmx.support.JmxUtils;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} that routes calls to an
@@ -609,7 +610,7 @@ public class MBeanClientInterceptor
* Simple wrapper class around a method name and its signature.
* Used as the key when caching methods.
*/
- private static class MethodCacheKey {
+ private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
private final String name;
@@ -628,7 +629,7 @@ public class MBeanClientInterceptor
@Override
public boolean equals(Object other) {
- if (other == this) {
+ if (this == other) {
return true;
}
MethodCacheKey otherKey = (MethodCacheKey) other;
@@ -639,6 +640,32 @@ public class MBeanClientInterceptor
public int hashCode() {
return this.name.hashCode();
}
+
+ @Override
+ public String toString() {
+ return this.name + "(" + StringUtils.arrayToCommaDelimitedString(this.parameterTypes) + ")";
+ }
+
+ @Override
+ public int compareTo(MethodCacheKey other) {
+ int result = this.name.compareTo(other.name);
+ if (result != 0) {
+ return result;
+ }
+ if (this.parameterTypes.length < other.parameterTypes.length) {
+ return -1;
+ }
+ if (this.parameterTypes.length > other.parameterTypes.length) {
+ return 1;
+ }
+ for (int i = 0; i < this.parameterTypes.length; i++) {
+ result = this.parameterTypes[i].getName().compareTo(other.parameterTypes[i].getName());
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
}
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
index 6fc0013e..b9eb86ca 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/MBeanExporter.java
@@ -570,7 +570,7 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* should be exposed to the {@code MBeanServer}. Specifically, if the
* supplied {@code mapValue} is the name of a bean that is configured
* for lazy initialization, then a proxy to the resource is registered with
- * the {@code MBeanServer} so that the the lazy load behavior is
+ * the {@code MBeanServer} so that the lazy load behavior is
* honored. If the bean is already an MBean then it will be registered
* directly with the {@code MBeanServer} without any intervention. For
* all other beans or bean names, the resource itself is registered with
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
index 0a802724..23d0d3a5 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,6 +19,7 @@ package org.springframework.jmx.export.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Set;
@@ -27,6 +28,7 @@ import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.jmx.export.metadata.InvalidMetadataException;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
@@ -39,6 +41,7 @@ import org.springframework.util.StringValueResolver;
* @author Rob Harrop
* @author Juergen Hoeller
* @author Jennifer Hickey
+ * @author Stephane Nicoll
* @since 1.2
* @see ManagedResource
* @see ManagedAttribute
@@ -50,25 +53,24 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
@Override
- public void setBeanFactory(final BeanFactory beanFactory) {
+ public void setBeanFactory(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableBeanFactory) {
- // Not using EmbeddedValueResolverAware in order to avoid a spring-context dependency:
- // ConfigurableBeanFactory and its resolveEmbeddedValue live in the spring-beans module.
- this.embeddedValueResolver = new StringValueResolver() {
- @Override
- public String resolveStringValue(String strVal) {
- return ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(strVal);
- }
- };
+ this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
}
}
+
@Override
public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
ManagedResource ann = AnnotationUtils.findAnnotation(beanClass, ManagedResource.class);
if (ann == null) {
return null;
}
+ Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(ManagedResource.class, beanClass);
+ Class<?> target = (declaringClass != null && !declaringClass.isInterface() ? declaringClass : beanClass);
+ if (!Modifier.isPublic(target.getModifiers())) {
+ throw new InvalidMetadataException("@ManagedResource class '" + target.getName() + "' must be public");
+ }
org.springframework.jmx.export.metadata.ManagedResource managedResource = new org.springframework.jmx.export.metadata.ManagedResource();
AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
return managedResource;
@@ -133,7 +135,7 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
if (ann == null) {
return null;
}
- T bean = BeanUtils.instantiate(beanClass);
+ T bean = BeanUtils.instantiateClass(beanClass);
AnnotationBeanUtils.copyPropertiesToBean(ann, bean);
return bean;
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java b/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
index 7c2f2aba..42bedaa4 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedNotifications.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
/**
* Type-level annotation that indicates JMX notifications emitted by a bean,
- * containing multiple {@link ManagedNotification ManagedNotifications}
+ * containing multiple {@link ManagedNotification ManagedNotifications}.
*
* @author Rob Harrop
* @since 2.0
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
index 3df5350d..30f5da76 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssembler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -72,7 +72,7 @@ public class MethodNameBasedMBeanInfoAssembler extends AbstractConfigurableMBean
* @param methodNames an array of method names indicating the methods to use
* @see #setMethodMappings
*/
- public void setManagedMethods(String[] methodNames) {
+ public void setManagedMethods(String... methodNames) {
this.managedMethods = new HashSet<String>(Arrays.asList(methodNames));
}
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java b/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
index 7db52fb8..96626f07 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/naming/IdentityNamingStrategy.java
@@ -26,7 +26,7 @@ import org.springframework.util.ObjectUtils;
/**
* An implementation of the {@code ObjectNamingStrategy} interface that
- * creates a name based on the the identity of a given instance.
+ * creates a name based on the identity of a given instance.
*
* <p>The resulting {@code ObjectName} will be in the form
* <i>package</i>:class=<i>class name</i>,hashCode=<i>identity hash (in hex)</i>
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java b/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
index 0f4ed459..5cb99a88 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/naming/KeyNamingStrategy.java
@@ -70,7 +70,7 @@ public class KeyNamingStrategy implements ObjectNamingStrategy, InitializingBean
/**
* Stores the result of merging the {@code mappings} {@code Properties}
- * with the the properties stored in the resources defined by {@code mappingLocations}.
+ * with the properties stored in the resources defined by {@code mappingLocations}.
*/
private Properties mergedMappings;
diff --git a/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java b/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
index 5a4d05a7..48d8eb7f 100644
--- a/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/jmx/support/ConnectorServerFactoryBean.java
@@ -138,7 +138,7 @@ public class ConnectorServerFactoryBean extends MBeanRegistrationSupport
* the {@code JMXConnectorServer} will be started in a separate thread.
* If the {@code daemon} flag is set to {@code true}, that thread will be
* started as a daemon thread.
- * @throws JMException if a problem occured when registering the connector server
+ * @throws JMException if a problem occurred when registering the connector server
* with the {@code MBeanServer}
* @throws IOException if there is a problem starting the connector server
*/
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java b/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
index 2aa25520..53aaebb7 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiLocatorDelegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,6 +19,8 @@ package org.springframework.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
+import org.springframework.core.SpringProperties;
+
/**
* {@link JndiLocatorSupport} subclass with public lookup methods,
* for convenient use as a delegate.
@@ -28,6 +30,29 @@ import javax.naming.NamingException;
*/
public class JndiLocatorDelegate extends JndiLocatorSupport {
+ /**
+ * System property that instructs Spring to ignore a default JNDI environment, i.e.
+ * to always return {@code false} from {@link #isDefaultJndiEnvironmentAvailable()}.
+ * <p>The default is "false", allowing for regular default JNDI access e.g. in
+ * {@link JndiPropertySource}. Switching this flag to {@code true} is an optimization
+ * for scenarios where nothing is ever to be found for such JNDI fallback searches
+ * to begin with, avoiding the repeated JNDI lookup overhead.
+ * <p>Note that this flag just affects JNDI fallback searches, not explicitly configured
+ * JNDI lookups such as for a {@code DataSource} or some other environment resource.
+ * The flag literally just affects code which attempts JNDI searches based on the
+ * {@code JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()} check: in particular,
+ * {@code StandardServletEnvironment} and {@code StandardPortletEnvironment}.
+ * @since 4.3
+ * @see #isDefaultJndiEnvironmentAvailable()
+ * @see JndiPropertySource
+ */
+ public static final String IGNORE_JNDI_PROPERTY_NAME = "spring.jndi.ignore";
+
+
+ private static final boolean shouldIgnoreDefaultJndiEnvironment =
+ SpringProperties.getFlag(IGNORE_JNDI_PROPERTY_NAME);
+
+
@Override
public Object lookup(String jndiName) throws NamingException {
return super.lookup(jndiName);
@@ -57,6 +82,9 @@ public class JndiLocatorDelegate extends JndiLocatorSupport {
* {@code false} if not
*/
public static boolean isDefaultJndiEnvironmentAvailable() {
+ if (shouldIgnoreDefaultJndiEnvironment) {
+ return false;
+ }
try {
new InitialContext().getEnvironment();
return true;
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
index b86838bd..dc22f0fb 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiObjectFactoryBean.java
@@ -219,7 +219,7 @@ public class JndiObjectFactoryBean extends JndiObjectLocator
}
/**
- * Lookup variant that that returns the specified "defaultObject"
+ * Lookup variant that returns the specified "defaultObject"
* (if any) in case of lookup failure.
* @return the located object, or the "defaultObject" as fallback
* @throws NamingException in case of lookup failure without fallback
diff --git a/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java b/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
index b33fc6d8..e2c40f34 100644
--- a/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
+++ b/spring-context/src/main/java/org/springframework/jndi/JndiPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -78,6 +78,15 @@ public class JndiPropertySource extends PropertySource<JndiLocatorDelegate> {
*/
@Override
public Object getProperty(String name) {
+ if (getSource().isResourceRef() && name.indexOf(':') != -1) {
+ // We're in resource-ref (prefixing with "java:comp/env") mode. Let's not bother
+ // with property names with a colon it since they're probably just containing a
+ // default value clause, very unlikely to match including the colon part even in
+ // a textual property source, and effectively never meant to match that way in
+ // JNDI where a colon indicates a separator between JNDI scheme and actual name.
+ return null;
+ }
+
try {
Object value = this.source.lookup(name);
if (logger.isDebugEnabled()) {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
index 117de48d..7c23ad64 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
@@ -21,7 +21,7 @@ import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncExecutionInterceptor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
/**
* Specialization of {@link AsyncExecutionInterceptor} that delegates method execution to
@@ -78,9 +78,9 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
protected String getExecutorQualifier(Method method) {
// Maintainer's note: changes made here should also be made in
// AnnotationAsyncExecutionAspect#getExecutorQualifier
- Async async = AnnotationUtils.findAnnotation(method, Async.class);
+ Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
if (async == null) {
- async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
+ async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
}
return (async != null ? async.value() : null);
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
index 6d67ac3e..fdd33a61 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java
@@ -103,18 +103,18 @@ public class AsyncResult<V> implements ListenableFuture<V> {
@Override
public void addCallback(SuccessCallback<? super V> successCallback, FailureCallback failureCallback) {
- if (this.executionException != null) {
- Throwable cause = this.executionException.getCause();
- failureCallback.onFailure(cause != null ? cause : this.executionException);
- }
- else {
- try {
- successCallback.onSuccess(this.value);
+ try {
+ if (this.executionException != null) {
+ Throwable cause = this.executionException.getCause();
+ failureCallback.onFailure(cause != null ? cause : this.executionException);
}
- catch (Throwable ex) {
- failureCallback.onFailure(ex);
+ else {
+ successCallback.onSuccess(this.value);
}
}
+ catch (Throwable ex) {
+ // Ignore
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index d46c7386..045dcd8a 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -24,18 +24,22 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Annotation that marks a method to be scheduled. Exactly one of the
- * {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
+ * An annotation that marks a method to be scheduled. Exactly one of
+ * the {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}
* attributes must be specified.
*
- * <p>The annotated method must expect no arguments and have a
- * {@code void} return type.
+ * <p>The annotated method must expect no arguments. It will typically have
+ * a {@code void} return type; if not, the returned value will be ignored
+ * when called through the scheduler.
*
* <p>Processing of {@code @Scheduled} annotations is performed by
* registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
* done manually or, more conveniently, through the {@code <task:annotation-driven/>}
* element or @{@link EnableScheduling} annotation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em> with attribute overrides.
+ *
* @author Mark Fisher
* @author Dave Syer
* @author Chris Beams
@@ -72,28 +76,30 @@ public @interface Scheduled {
String zone() default "";
/**
- * Execute the annotated method with a fixed period between the end
- * of the last invocation and the start of the next.
+ * Execute the annotated method with a fixed period in milliseconds between the
+ * end of the last invocation and the start of the next.
* @return the delay in milliseconds
*/
long fixedDelay() default -1;
/**
- * Execute the annotated method with a fixed period between the end
- * of the last invocation and the start of the next.
+ * Execute the annotated method with a fixed period in milliseconds between the
+ * end of the last invocation and the start of the next.
* @return the delay in milliseconds as a String value, e.g. a placeholder
* @since 3.2.2
*/
String fixedDelayString() default "";
/**
- * Execute the annotated method with a fixed period between invocations.
+ * Execute the annotated method with a fixed period in milliseconds between
+ * invocations.
* @return the period in milliseconds
*/
long fixedRate() default -1;
/**
- * Execute the annotated method with a fixed period between invocations.
+ * Execute the annotated method with a fixed period in milliseconds between
+ * invocations.
* @return the period in milliseconds as a String value, e.g. a placeholder
* @since 3.2.2
*/
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
index bacd01b5..89d35299 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -17,8 +17,9 @@
package org.springframework.scheduling.annotation;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
@@ -36,7 +37,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
@@ -44,16 +45,16 @@ import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.IntervalTask;
+import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
-import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -83,14 +84,15 @@ import org.springframework.util.StringValueResolver;
* @see org.springframework.scheduling.config.ScheduledTaskRegistrar
* @see AsyncAnnotationBeanPostProcessor
*/
-public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
- EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
+public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
+ Ordered, EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
/**
* The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".
* <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context.
+ * @since 4.2
*/
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
@@ -110,6 +112,9 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
private final Set<Class<?>> nonAnnotatedClasses =
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
+ private final Map<Object, Set<ScheduledTask>> scheduledTasks =
+ new ConcurrentHashMap<Object, Set<ScheduledTask>>(16);
+
@Override
public int getOrder() {
@@ -120,6 +125,12 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
* Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
* the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
* to be wrapped as a TaskScheduler.
+ * <p>If not specified, default scheduler resolution will apply: searching for a
+ * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}
+ * bean named "taskScheduler" otherwise; the same lookup will also be performed for
+ * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,
+ * a local single-threaded default scheduler will be created within the registrar.
+ * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
public void setScheduler(Object scheduler) {
this.scheduler = scheduler;
@@ -197,10 +208,13 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class));
}
catch (NoSuchBeanDefinitionException ex2) {
- throw new IllegalStateException("More than one TaskScheduler bean exists within the context, and " +
- "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "+
- "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
- "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex);
+ if (logger.isInfoEnabled()) {
+ logger.info("More than one TaskScheduler bean exists within the context, and " +
+ "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
+ "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
+ "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
+ ex.getBeanNamesFound());
+ }
}
}
catch (NoSuchBeanDefinitionException ex) {
@@ -210,14 +224,24 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class));
}
catch (NoUniqueBeanDefinitionException ex2) {
- throw new IllegalStateException("More than one ScheduledExecutorService bean exists within " +
- "the context. Mark one of them as primary; or implement the SchedulingConfigurer " +
- "interface and call ScheduledTaskRegistrar#setScheduler explicitly within the " +
- "configureTasks() callback.", ex);
+ try {
+ this.registrar.setScheduler(
+ this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, ScheduledExecutorService.class));
+ }
+ catch (NoSuchBeanDefinitionException ex3) {
+ if (logger.isInfoEnabled()) {
+ logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
+ "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
+ "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
+ "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
+ ex2.getBeanNamesFound());
+ }
+ }
}
catch (NoSuchBeanDefinitionException ex2) {
- logger.debug("Could not find default ScheduledExecutorService bean", ex);
+ logger.debug("Could not find default ScheduledExecutorService bean", ex2);
// Giving up -> falling back to default scheduler within the registrar...
+ logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
@@ -240,7 +264,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
public Set<Scheduled> inspect(Method method) {
Set<Scheduled> scheduledMethods =
- AnnotationUtils.getRepeatableAnnotations(method, Scheduled.class, Schedules.class);
+ AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
@@ -269,44 +293,21 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
- Assert.isTrue(void.class == method.getReturnType(),
- "Only void-returning methods may be annotated with @Scheduled");
Assert.isTrue(method.getParameterTypes().length == 0,
"Only no-arg methods may be annotated with @Scheduled");
- if (AopUtils.isJdkDynamicProxy(bean)) {
- try {
- // Found a @Scheduled method on the target class for this JDK proxy ->
- // is it also present on the proxy itself?
- method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
- }
- catch (SecurityException ex) {
- ReflectionUtils.handleReflectionException(ex);
- }
- catch (NoSuchMethodException ex) {
- throw new IllegalStateException(String.format(
- "@Scheduled method '%s' found on bean target class '%s' but not " +
- "found in any interface(s) for a dynamic proxy. Either pull the " +
- "method up to a declared interface or switch to subclass (CGLIB) " +
- "proxies by setting proxy-target-class/proxyTargetClass to 'true'.",
- method.getName(), method.getDeclaringClass().getSimpleName()));
- }
- }
- else if (AopUtils.isCglibProxy(bean)) {
- // Common problem: private methods end up in the proxy instance, not getting delegated.
- if (Modifier.isPrivate(method.getModifiers())) {
- throw new IllegalStateException(String.format(
- "@Scheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " +
- "be delegated to target bean. Switch its visibility to package or protected.",
- method.getName(), method.getDeclaringClass().getSimpleName()));
- }
- }
-
- Runnable runnable = new ScheduledMethodRunnable(bean, method);
+ Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
+ Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
+ Set<ScheduledTask> tasks = this.scheduledTasks.get(bean);
+ if (tasks == null) {
+ tasks = new LinkedHashSet<ScheduledTask>(4);
+ this.scheduledTasks.put(bean, tasks);
+ }
+
// Determine initial delay
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
@@ -341,7 +342,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
else {
timeZone = TimeZone.getDefault();
}
- this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
+ tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
// At this point we don't need to differentiate between initial delay set or not anymore
@@ -354,7 +355,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
+ tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
@@ -370,7 +371,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
}
- this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
+ tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
// Check fixed rate
@@ -378,7 +379,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
- this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
+ tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
@@ -394,7 +395,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
}
- this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
+ tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
// Check whether we had any attribute set
@@ -408,7 +409,29 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
+ public void postProcessBeforeDestruction(Object bean, String beanName) {
+ Set<ScheduledTask> tasks = this.scheduledTasks.remove(bean);
+ if (tasks != null) {
+ for (ScheduledTask task : tasks) {
+ task.cancel();
+ }
+ }
+ }
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return this.scheduledTasks.containsKey(bean);
+ }
+
+ @Override
public void destroy() {
+ Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
+ for (Set<ScheduledTask> tasks : allTasks) {
+ for (ScheduledTask task : tasks) {
+ task.cancel();
+ }
+ }
+ this.scheduledTasks.clear();
this.registrar.destroy();
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
index 3cba4186..d27877ae 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Schedules.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -30,6 +30,9 @@ import java.lang.annotation.Target;
* where {@link Scheduled} can simply be declared several times on the same method,
* implicitly generating this container annotation.
*
+ * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
+ * <em>composed annotations</em>.
+ *
* @author Juergen Hoeller
* @since 4.0
* @see Scheduled
diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
index e9f2d255..b888092d 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -26,6 +26,7 @@ import javax.enterprise.concurrent.ManagedExecutors;
import javax.enterprise.concurrent.ManagedTask;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.support.TaskExecutorAdapter;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.scheduling.SchedulingTaskExecutor;
@@ -127,6 +128,20 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
return this.concurrentExecutor;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.adaptedExecutor.setTaskDecorator(taskDecorator);
+ }
+
@Override
public void execute(Runnable task) {
diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
index 6cd5ccf9..3391276b 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -30,6 +30,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.util.Assert;
@@ -82,6 +83,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
private boolean allowCoreThreadTimeOut = false;
+ private TaskDecorator taskDecorator;
+
private ThreadPoolExecutor threadPoolExecutor;
@@ -177,15 +180,51 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
}
+ /**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+ /**
+ * Note: This method exposes an {@link ExecutorService} to its base class
+ * but stores the actual {@link ThreadPoolExecutor} handle internally.
+ * Do not override this method for replacing the executor, rather just for
+ * decorating its {@code ExecutorService} handle or storing custom state.
+ */
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
- ThreadPoolExecutor executor = new ThreadPoolExecutor(
- this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
- queue, threadFactory, rejectedExecutionHandler);
+
+ ThreadPoolExecutor executor;
+ if (this.taskDecorator != null) {
+ executor = new ThreadPoolExecutor(
+ this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
+ queue, threadFactory, rejectedExecutionHandler) {
+ @Override
+ public void execute(Runnable command) {
+ super.execute(taskDecorator.decorate(command));
+ }
+ };
+ }
+ else {
+ executor = new ThreadPoolExecutor(
+ this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
+ queue, threadFactory, rejectedExecutionHandler);
+
+ }
+
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java
new file mode 100644
index 00000000..dffdb3e2
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2016 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.scheduling.config;
+
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * A representation of a scheduled task,
+ * used as a return value for scheduling methods.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see ScheduledTaskRegistrar#scheduleTriggerTask
+ * @see ScheduledTaskRegistrar#scheduleFixedRateTask
+ */
+public final class ScheduledTask {
+
+ volatile ScheduledFuture<?> future;
+
+
+ ScheduledTask() {
+ }
+
+
+ /**
+ * Trigger cancellation of this scheduled task.
+ */
+ public void cancel() {
+ ScheduledFuture<?> future = this.future;
+ if (future != null) {
+ future.cancel(true);
+ }
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
index 1cc43ca7..d7fe6be3 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,13 +19,13 @@ package org.springframework.scheduling.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@@ -67,7 +67,9 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
private List<IntervalTask> fixedDelayTasks;
- private final Set<ScheduledFuture<?>> scheduledFutures = new LinkedHashSet<ScheduledFuture<?>>();
+ private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<Task, ScheduledTask>(16);
+
+ private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<ScheduledTask>(16);
/**
@@ -228,6 +230,7 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
Collections.<IntervalTask>emptyList());
}
+
/**
* Add a Runnable task to be triggered per the given {@link Trigger}.
* @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
@@ -306,6 +309,7 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
this.fixedDelayTasks.add(task);
}
+
/**
* Return whether this {@code ScheduledTaskRegistrar} has any tasks registered.
* @since 3.2
@@ -331,56 +335,155 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean
* #setTaskScheduler(TaskScheduler) task scheduler}.
*/
protected void scheduleTasks() {
- long now = System.currentTimeMillis();
-
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
- this.scheduledFutures.add(this.taskScheduler.schedule(
- task.getRunnable(), task.getTrigger()));
+ addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
- this.scheduledFutures.add(this.taskScheduler.schedule(
- task.getRunnable(), task.getTrigger()));
+ addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
- if (task.getInitialDelay() > 0) {
- Date startTime = new Date(now + task.getInitialDelay());
- this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
- task.getRunnable(), startTime, task.getInterval()));
- }
- else {
- this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
- task.getRunnable(), task.getInterval()));
- }
+ addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
- if (task.getInitialDelay() > 0) {
- Date startTime = new Date(now + task.getInitialDelay());
- this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
- task.getRunnable(), startTime, task.getInterval()));
- }
- else {
- this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
- task.getRunnable(), task.getInterval()));
- }
+ addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
+ private void addScheduledTask(ScheduledTask task) {
+ if (task != null) {
+ this.scheduledTasks.add(task);
+ }
+ }
+
+
+ /**
+ * Schedule the specified trigger task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * @since 4.3
+ */
+ public ScheduledTask scheduleTriggerTask(TriggerTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
+ }
+ else {
+ addTriggerTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified cron task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleCronTask(CronTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
+ }
+ else {
+ addCronTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified fixed-rate task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleFixedRateTask(IntervalTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
+ scheduledTask.future =
+ this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
+ }
+ else {
+ scheduledTask.future =
+ this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
+ }
+ }
+ else {
+ addFixedRateTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+ /**
+ * Schedule the specified fixed-delay task, either right away if possible
+ * or on initialization of the scheduler.
+ * @return a handle to the scheduled task, allowing to cancel it
+ * (or {@code null} if processing a previously registered task)
+ * @since 4.3
+ */
+ public ScheduledTask scheduleFixedDelayTask(IntervalTask task) {
+ ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
+ boolean newTask = false;
+ if (scheduledTask == null) {
+ scheduledTask = new ScheduledTask();
+ newTask = true;
+ }
+ if (this.taskScheduler != null) {
+ if (task.getInitialDelay() > 0) {
+ Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
+ scheduledTask.future =
+ this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());
+ }
+ else {
+ scheduledTask.future =
+ this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval());
+ }
+ }
+ else {
+ addFixedDelayTask(task);
+ this.unresolvedTasks.put(task, scheduledTask);
+ }
+ return (newTask ? scheduledTask : null);
+ }
+
+
@Override
public void destroy() {
- for (ScheduledFuture<?> future : this.scheduledFutures) {
- future.cancel(true);
+ for (ScheduledTask task : this.scheduledTasks) {
+ task.cancel();
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
index f53894c7..c7b356fd 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -262,7 +262,7 @@ public class CronSequenceGenerator {
*/
private void parse(String expression) throws IllegalArgumentException {
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
- if (fields.length != 6) {
+ if (!areValidCronFields(fields)) {
throw new IllegalArgumentException(String.format(
"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
}
@@ -379,10 +379,32 @@ public class CronSequenceGenerator {
throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
+ if (result[0] > result[1]) {
+ throw new IllegalArgumentException("Invalid inverted range: '" + field +
+ "' in expression \"" + this.expression + "\"");
+ }
return result;
}
+ /**
+ * Determine whether the specified expression represents a valid cron pattern.
+ * <p>Specifically, this method verifies that the expression contains six
+ * fields separated by single spaces.
+ * @param expression the expression to evaluate
+ * @return {@code true} if the given expression is a valid cron expression
+ * @since 4.3
+ */
+ public static boolean isValidExpression(String expression) {
+ String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
+ return areValidCronFields(fields);
+ }
+
+ private static boolean areValidCronFields(String[] fields) {
+ return (fields != null && fields.length == 6);
+ }
+
+
@Override
public boolean equals(Object other) {
if (this == other) {
diff --git a/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java b/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
index 5fb732dc..57d18c2f 100644
--- a/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
+++ b/spring-context/src/main/java/org/springframework/scripting/bsh/BshScriptUtils.java
@@ -94,8 +94,7 @@ public abstract class BshScriptUtils {
return clazz.newInstance();
}
catch (Throwable ex) {
- throw new IllegalStateException("Could not instantiate script class [" +
- clazz.getName() + "]. Root cause is " + ex);
+ throw new IllegalStateException("Could not instantiate script class: " + clazz.getName(), ex);
}
}
else {
diff --git a/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java b/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
index b5452624..f967a3a6 100644
--- a/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
+++ b/spring-context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,7 +28,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @since 2.5
*/
-public class ScriptingDefaultsParser implements BeanDefinitionParser {
+class ScriptingDefaultsParser implements BeanDefinitionParser {
private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
@@ -41,7 +41,7 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser {
LangNamespaceUtils.registerScriptFactoryPostProcessorIfNecessary(parserContext.getRegistry());
String refreshCheckDelay = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
if (StringUtils.hasText(refreshCheckDelay)) {
- bd.getPropertyValues().add("defaultRefreshCheckDelay", new Long(refreshCheckDelay));
+ bd.getPropertyValues().add("defaultRefreshCheckDelay", Long.valueOf(refreshCheckDelay));
}
String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
if (StringUtils.hasText(proxyTargetClass)) {
diff --git a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
index 439f3c1e..fdc2bb83 100644
--- a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
+++ b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java
@@ -266,7 +266,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
}
catch (InstantiationException ex) {
throw new ScriptCompilationException(
- scriptSource, "Could not instantiate Groovy script class: " + scriptClass.getName(), ex);
+ scriptSource, "Unable to instantiate Groovy script class: " + scriptClass.getName(), ex);
}
catch (IllegalAccessException ex) {
throw new ScriptCompilationException(
diff --git a/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java b/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
index d91573d0..51ff49e1 100644
--- a/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
@@ -381,7 +381,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* If the {@link BeanDefinition} has a
* {@link org.springframework.core.AttributeAccessor metadata attribute}
* under the key {@link #REFRESH_CHECK_DELAY_ATTRIBUTE} which is a valid {@link Number}
- * type, then this value is used. Otherwise, the the {@link #defaultRefreshCheckDelay}
+ * type, then this value is used. Otherwise, the {@link #defaultRefreshCheckDelay}
* value is used.
* @param beanDefinition the BeanDefinition to check
* @return the refresh check delay
diff --git a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
index bff51a79..f8bc2365 100644
--- a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
+++ b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -154,7 +154,7 @@ public class StandardScriptFactory implements ScriptFactory, BeanClassLoaderAwar
}
catch (InstantiationException ex) {
throw new ScriptCompilationException(
- scriptSource, "Could not instantiate script class: " + scriptClass.getName(), ex);
+ scriptSource, "Unable to instantiate script class: " + scriptClass.getName(), ex);
}
catch (IllegalAccessException ex) {
throw new ScriptCompilationException(
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
index 913406f0..33d53bf9 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -31,9 +31,12 @@ import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
+import javax.validation.ValidationProviderResolver;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
+import javax.validation.bootstrap.GenericBootstrap;
+import javax.validation.bootstrap.ProviderSpecificBootstrap;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
@@ -51,9 +54,9 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
- * This is the central class for {@code javax.validation} (JSR-303) setup
- * in a Spring application context: It bootstraps a {@code javax.validation.ValidationFactory}
- * and exposes it through the Spring {@link org.springframework.validation.Validator} interface
+ * This is the central class for {@code javax.validation} (JSR-303) setup in a Spring
+ * application context: It bootstraps a {@code javax.validation.ValidationFactory} and
+ * exposes it through the Spring {@link org.springframework.validation.Validator} interface
* as well as through the JSR-303 {@link javax.validation.Validator} interface and the
* {@link javax.validation.ValidatorFactory} interface itself.
*
@@ -92,6 +95,8 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
@SuppressWarnings("rawtypes")
private Class providerClass;
+ private ValidationProviderResolver validationProviderResolver;
+
private MessageInterpolator messageInterpolator;
private TraversableResolver traversableResolver;
@@ -121,6 +126,15 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
}
/**
+ * Specify a JSR-303 {@link ValidationProviderResolver} for bootstrapping the
+ * provider of choice, as an alternative to {@code META-INF} driven resolution.
+ * @since 4.3
+ */
+ public void setValidationProviderResolver(ValidationProviderResolver validationProviderResolver) {
+ this.validationProviderResolver = validationProviderResolver;
+ }
+
+ /**
* Specify a custom MessageInterpolator to use for this ValidatorFactory
* and its exposed default Validator.
*/
@@ -216,11 +230,23 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
@Override
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void afterPropertiesSet() {
- @SuppressWarnings({"rawtypes", "unchecked"})
- Configuration<?> configuration = (this.providerClass != null ?
- Validation.byProvider(this.providerClass).configure() :
- Validation.byDefaultProvider().configure());
+ Configuration<?> configuration;
+ if (this.providerClass != null) {
+ ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass);
+ if (this.validationProviderResolver != null) {
+ bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
+ }
+ configuration = bootstrap.configure();
+ }
+ else {
+ GenericBootstrap bootstrap = Validation.byDefaultProvider();
+ if (this.validationProviderResolver != null) {
+ bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
+ }
+ configuration = bootstrap.configure();
+ }
// Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
if (this.applicationContext != null) {
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
index aa88b18c..6d46113d 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,6 +28,7 @@ import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import org.springframework.beans.NotReadablePropertyException;
+import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
@@ -190,9 +191,9 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
* Return FieldError arguments for a validation error on the given field.
* Invoked for each violated constraint.
* <p>The default implementation returns a first argument indicating the field name
- * (of type DefaultMessageSourceResolvable, with "objectName.field" and "field" as codes).
- * Afterwards, it adds all actual constraint annotation attributes (i.e. excluding
- * "message", "groups" and "payload") in alphabetical order of their attribute names.
+ * (see {@link #getResolvableField}). Afterwards, it adds all actual constraint
+ * annotation attributes (i.e. excluding "message", "groups" and "payload") in
+ * alphabetical order of their attribute names.
* <p>Can be overridden to e.g. add further attributes from the constraint descriptor.
* @param objectName the name of the target object
* @param field the field that caused the binding error
@@ -204,14 +205,16 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
*/
protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor<?> descriptor) {
List<Object> arguments = new LinkedList<Object>();
- String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
- arguments.add(new DefaultMessageSourceResolvable(codes, field));
+ arguments.add(getResolvableField(objectName, field));
// Using a TreeMap for alphabetical ordering of attribute names
Map<String, Object> attributesToExpose = new TreeMap<String, Object>();
for (Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
String attributeName = entry.getKey();
Object attributeValue = entry.getValue();
if (!internalAnnotationAttributes.contains(attributeName)) {
+ if (attributeValue instanceof String) {
+ attributeValue = new ResolvableAttribute(attributeValue.toString());
+ }
attributesToExpose.put(attributeName, attributeValue);
}
}
@@ -220,6 +223,22 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
}
/**
+ * Build a resolvable wrapper for the specified field, allowing to resolve the field's
+ * name in a {@code MessageSource}.
+ * <p>The default implementation returns a first argument indicating the field:
+ * of type {@code DefaultMessageSourceResolvable}, with "objectName.field" and "field"
+ * as codes, and with the plain field name as default message.
+ * @param objectName the name of the target object
+ * @param field the field that caused the binding error
+ * @return a corresponding {@code MessageSourceResolvable} for the specified field
+ * @since 4.3
+ */
+ protected MessageSourceResolvable getResolvableField(String objectName, String field) {
+ String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
+ return new DefaultMessageSourceResolvable(codes, field);
+ }
+
+ /**
* Extract the rejected value behind the given constraint violation,
* for exposure through the Spring errors representation.
* @param field the field that caused the binding error
@@ -249,13 +268,13 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
@Override
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validate(object, groups);
}
@Override
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateProperty(object, propertyName, groups);
}
@@ -263,20 +282,50 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
public <T> Set<ConstraintViolation<T>> validateValue(
Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.validateValue(beanType, propertyName, value, groups);
}
@Override
public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
- Assert.notNull(this.targetValidator, "No target Validator set");
+ Assert.state(this.targetValidator != null, "No target Validator set");
return this.targetValidator.getConstraintsForClass(clazz);
}
@Override
+ @SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> type) {
- Assert.notNull(this.targetValidator, "No target Validator set");
- return this.targetValidator.unwrap(type);
+ Assert.state(this.targetValidator != null, "No target Validator set");
+ return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
+ }
+
+
+ /**
+ * Wrapper for a String attribute which can be resolved via a {@code MessageSource},
+ * falling back to the original attribute as a default value otherwise.
+ */
+ private static class ResolvableAttribute implements MessageSourceResolvable {
+
+ private final String resolvableString;
+
+ public ResolvableAttribute(String resolvableString) {
+ this.resolvableString = resolvableString;
+ }
+
+ @Override
+ public String[] getCodes() {
+ return new String[] {this.resolvableString};
+ }
+
+ @Override
+ public Object[] getArguments() {
+ return null;
+ }
+
+ @Override
+ public String getDefaultMessage() {
+ return this.resolvableString;
+ }
}
}