diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2016-09-26 09:12:32 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2016-09-26 09:12:32 +0200 |
commit | c3df6b7858afaef71fbe0b42cd62f7f12e595bf0 (patch) | |
tree | c724137b4083c01dd5e4a830803f291ee76a2de8 /spring-context | |
parent | 75a721d1019da2a2fa86e24ff439df4a224e5b19 (diff) |
New upstream version 4.3.3
Diffstat (limited to 'spring-context')
36 files changed, 693 insertions, 314 deletions
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 f12d2119..9364e800 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 @@ -69,7 +69,9 @@ public @interface CacheEvict { * 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> + * can only be used if {@link #beforeInvocation()} is {@code false}. For supported + * wrappers such as {@code Optional}, {@code #result} refers to the actual object, + * not the wrapper</li> * <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> 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 cd48e410..72d41be0 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 @@ -31,7 +31,9 @@ import org.springframework.core.annotation.AliasFor; * * <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation * 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. + * method to be invoked and its result to be stored in the associated cache. Note + * that Java8's {@code Optional} return types are automatically handled and its + * content is stored in the cache if present. * * <p>This annotation may be used as a <em>meta-annotation</em> to create custom * <em>composed annotations</em> with attribute overrides. @@ -73,7 +75,9 @@ public @interface CachePut { * <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 #result} for a reference to the result of the method invocation. For + * supported wrappers such as {@code Optional}, {@code #result} refers to the actual + * object, not the wrapper</li> * <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> @@ -138,7 +142,9 @@ public @interface CachePut { * <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 #result} for a reference to the result of the method invocation. For + * supported wrappers such as {@code Optional}, {@code #result} refers to the actual + * object, not the wrapper</li> * <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> 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 225ada51..9defd955 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 @@ -38,7 +38,9 @@ import org.springframework.core.annotation.AliasFor; * replace the default one (see {@link #keyGenerator}). * * <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. + * will be invoked and the returned value stored in the associated cache. Note + * that Java8's {@code Optional} return types are automatically handled and its + * content is stored in the cache if present. * * <p>This annotation may be used as a <em>meta-annotation</em> to create custom * <em>composed annotations</em> with attribute overrides. @@ -144,7 +146,9 @@ public @interface Cacheable { * <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 #result} for a reference to the result of the method invocation. For + * supported wrappers such as {@code Optional}, {@code #result} refers to the actual + * object, not the wrapper</li> * <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> diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java index 70ab3a5f..4d3da400 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.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. @@ -31,14 +31,16 @@ public abstract class AbstractCacheInvoker { private CacheErrorHandler errorHandler; + + protected AbstractCacheInvoker() { + this(new SimpleCacheErrorHandler()); + } + protected AbstractCacheInvoker(CacheErrorHandler errorHandler) { Assert.notNull("ErrorHandler must not be null"); this.errorHandler = errorHandler; } - protected AbstractCacheInvoker() { - this(new SimpleCacheErrorHandler()); - } /** * Set the {@link CacheErrorHandler} instance to use to handle errors @@ -56,6 +58,7 @@ public abstract class AbstractCacheInvoker { return this.errorHandler; } + /** * Execute {@link Cache#get(Object)} on the specified {@link Cache} and * invoke the error handler if an exception occurs. Return {@code null} @@ -67,9 +70,9 @@ public abstract class AbstractCacheInvoker { try { return cache.get(key); } - catch (RuntimeException e) { - getErrorHandler().handleCacheGetError(e, cache, key); - return null; // If the exception is handled, return a cache miss + catch (RuntimeException ex) { + getErrorHandler().handleCacheGetError(ex, cache, key); + return null; // If the exception is handled, return a cache miss } } @@ -81,8 +84,8 @@ public abstract class AbstractCacheInvoker { try { cache.put(key, result); } - catch (RuntimeException e) { - getErrorHandler().handleCachePutError(e, cache, key, result); + catch (RuntimeException ex) { + getErrorHandler().handleCachePutError(ex, cache, key, result); } } @@ -94,8 +97,8 @@ public abstract class AbstractCacheInvoker { try { cache.evict(key); } - catch (RuntimeException e) { - getErrorHandler().handleCacheEvictError(e, cache, key); + catch (RuntimeException ex) { + getErrorHandler().handleCacheEvictError(ex, cache, key); } } @@ -107,8 +110,8 @@ public abstract class AbstractCacheInvoker { try { cache.clear(); } - catch (RuntimeException e) { - getErrorHandler().handleCacheClearError(e, cache); + catch (RuntimeException ex) { + getErrorHandler().handleCacheClearError(ex, cache); } } 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 2de8bd8f..7cfb76d9 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 @@ -444,7 +444,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker excluded.add(context); } } - catch (VariableNotAvailableException e) { + catch (VariableNotAvailableException ex) { // Ignoring failure due to missing result, consider the cache put has to proceed } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java index 8d65e15e..213bcd06 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.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,8 @@ package org.springframework.cache.interceptor; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.springframework.context.expression.MethodBasedEvaluationContext; import org.springframework.core.ParameterNameDiscoverer; @@ -38,25 +38,27 @@ import org.springframework.core.ParameterNameDiscoverer; * * @author Costin Leau * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 */ class CacheEvaluationContext extends MethodBasedEvaluationContext { - private final List<String> unavailableVariables; + private final Set<String> unavailableVariables = new HashSet<String>(1); - CacheEvaluationContext(Object rootObject, Method method, Object[] args, - ParameterNameDiscoverer paramDiscoverer) { - super(rootObject, method, args, paramDiscoverer); - this.unavailableVariables = new ArrayList<String>(); + CacheEvaluationContext(Object rootObject, Method method, Object[] arguments, + ParameterNameDiscoverer parameterNameDiscoverer) { + + super(rootObject, method, arguments, parameterNameDiscoverer); } + /** - * Add the specified variable name as unavailable for that context. Any expression trying - * to access this variable should lead to an exception. - * <p>This permits the validation of expressions that could potentially a variable even - * when such variable isn't available yet. Any expression trying to use that variable should - * therefore fail to evaluate. + * Add the specified variable name as unavailable for that context. + * Any expression trying to access this variable should lead to an exception. + * <p>This permits the validation of expressions that could potentially a + * variable even when such variable isn't available yet. Any expression + * trying to use that variable should therefore fail to evaluate. */ public void addUnavailableVariable(String name) { this.unavailableVariables.add(name); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index 8cbaa0da..b15b7c8e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.core.annotation.AliasFor; /** * Indicates that a method produces a bean to be managed by the Spring container. @@ -44,15 +45,15 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; * * <h3>Bean Names</h3> * - * <p>While a {@link #name() name} attribute is available, the default strategy for + * <p>While a {@link #name} attribute is available, the default strategy for * determining the name of a bean is to use the name of the {@code @Bean} method. * This is convenient and intuitive, but if explicit naming is desired, the - * {@code name} attribute may be used. Also note that {@code name} accepts an array - * of Strings. This is in order to allow for specifying multiple names (i.e., aliases) - * for a single bean. + * {@code name} attribute (or its alias {@code value}) may be used. Also note + * that {@code name} accepts an array of Strings, allowing for multiple names + * (i.e. a primary bean name plus one or more aliases) for a single bean. * * <pre class="code"> - * @Bean(name={"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean' + * @Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean' * public MyBean myBean() { * // instantiate and configure MyBean obj * return obj; @@ -78,9 +79,9 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; * <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3> * * <p>Typically, {@code @Bean} methods are declared within {@code @Configuration} - * classes. In this case, bean methods may reference other {@code @Bean} methods - * in the same class by calling them <i>directly</i>. This ensures that references between - * beans are strongly typed and navigable. Such so-called <em>'inter-bean references'</em> are + * classes. In this case, bean methods may reference other {@code @Bean} methods in the + * same class by calling them <i>directly</i>. This ensures that references between beans + * are strongly typed and navigable. Such so-called <em>'inter-bean references'</em> are * guaranteed to respect scoping and AOP semantics, just like {@code getBean()} lookups * would. These are the semantics known from the original 'Spring JavaConfig' project * which require CGLIB subclassing of each such configuration class at runtime. As a @@ -190,10 +191,24 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; public @interface Bean { /** - * The name of this bean, or if plural, aliases for this bean. If left unspecified - * the name of the bean is the name of the annotated method. If specified, the method - * name is ignored. + * Alias for {@link #name}. + * <p>Intended to be used when no other attributes are needed, for example: + * {@code @Bean("customBeanName")}. + * @since 4.3.3 + * @see #name */ + @AliasFor("name") + String[] value() default {}; + + /** + * The name of this bean, or if several names, a primary bean name plus aliases. + * <p>If left unspecified, the name of the bean is the name of the annotated method. + * If specified, the method name is ignored. + * <p>The bean name and aliases may also be configured via the {@link #value} + * attribute if no other attributes are declared. + * @see #value + */ + @AliasFor("value") String[] name() default {}; /** 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 247c5838..6a8fba7b 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 @@ -25,17 +25,10 @@ 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; @@ -61,16 +54,16 @@ import org.springframework.util.StringUtils; */ class ComponentScanAnnotationParser { - private final ResourceLoader resourceLoader; - private final Environment environment; - private final BeanDefinitionRegistry registry; + private final ResourceLoader resourceLoader; private final BeanNameGenerator beanNameGenerator; + private final BeanDefinitionRegistry registry; + - public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment environment, + public ComponentScanAnnotationParser(Environment environment, ResourceLoader resourceLoader, BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry) { this.resourceLoader = resourceLoader; @@ -90,7 +83,7 @@ class ComponentScanAnnotationParser { scanner.setResourceLoader(this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); - boolean useInheritedGenerator = BeanNameGenerator.class == generatorClass; + boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); @@ -164,7 +157,8 @@ class ComponentScanAnnotationParser { Assert.isAssignable(TypeFilter.class, filterClass, "An error occurred while processing a @ComponentScan CUSTOM type filter: "); TypeFilter filter = BeanUtils.instantiateClass(filterClass, TypeFilter.class); - invokeAwareMethods(filter); + ParserStrategyUtils.invokeAwareMethods( + filter, this.environment, this.resourceLoader, this.registry); typeFilters.add(filter); break; default: @@ -188,27 +182,4 @@ 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/Configuration.java b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java index 5d3b6777..9cb02b7d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java @@ -84,7 +84,22 @@ import org.springframework.stereotype.Component; * {@code @Configuration} classes are candidates for component scanning (typically using * Spring XML's {@code <context:component-scan/>} element) and therefore may also take * advantage of {@link Autowired @Autowired}/{@link javax.inject.Inject @Inject} - * at the field and method level (but not at the constructor level). + * like any regular {@code @Component}. In particular, if a single constructor is present + * autowiring semantics will be applied transparently: + * + * <pre class="code"> + * @Configuration + * public class AppConfig { + * private final SomeBean someBean; + * + * public AppConfig(SomeBean someBean) { + * this.someBean = someBean; + * } + * + * // @Bean definition using "SomeBean" + * + * }</pre> + * * <p>{@code @Configuration} classes may not only be bootstrapped using * component scanning, but may also themselves <em>configure</em> component scanning using * the {@link ComponentScan @ComponentScan} annotation: @@ -104,13 +119,13 @@ import org.springframework.stereotype.Component; * * Externalized values may be looked up by injecting the Spring * {@link org.springframework.core.env.Environment} into a {@code @Configuration} - * class using the {@code @Autowired} or the {@code @Inject} annotation: + * class the usual (e.g. using the {@code @Autowired} annotation): * * <pre class="code"> * @Configuration * public class AppConfig { * - * @Inject Environment env; + * @Autowired Environment env; * * @Bean * public MyBean myBean() { @@ -175,7 +190,7 @@ import org.springframework.stereotype.Component; * <p>{@code @Configuration} classes may be composed using the {@link Import @Import} annotation, * not unlike the way that {@code <import>} works in Spring XML. Because * {@code @Configuration} objects are managed as Spring beans within the container, - * imported configurations may be injected using {@code @Autowired} or {@code @Inject}: + * imported configurations may be injected the usual way (e.g. via constructor injection): * * <pre class="code"> * @Configuration @@ -191,7 +206,11 @@ import org.springframework.stereotype.Component; * @Import(DatabaseConfig.class) * public class AppConfig { * - * @Inject DatabaseConfig dataConfig; + * private final DatabaseConfig dataConfig; + * + * public AppConfig(DatabaseConfig dataConfig) { + * this.dataConfig = dataConfig; + * } * * @Bean * public MyBean myBean() { @@ -240,8 +259,8 @@ import org.springframework.stereotype.Component; * As mentioned above, {@code @Configuration} classes may be declared as regular Spring * {@code <bean>} definitions within Spring XML files. It is also possible to * import Spring XML configuration files into {@code @Configuration} classes using - * the {@link ImportResource @ImportResource} annotation. Bean definitions imported from XML can be - * injected using {@code @Autowired} or {@code @Inject}: + * the {@link ImportResource @ImportResource} annotation. Bean definitions imported from + * XML can be injected the usual way (e.g. using the {@code Inject} annotation): * * <pre class="code"> * @Configuration @@ -340,9 +359,7 @@ import org.springframework.stereotype.Component; * <ul> * <li>@Configuration classes must be non-final * <li>@Configuration classes must be non-local (may not be declared within a method) - * <li>@Configuration classes must have a default/no-arg constructor and may not use - * {@link Autowired @Autowired} constructor parameters. Any nested configuration classes - * must be {@code static}. + * <li>Any nested configuration classes must be {@code static}. * </ul> * * @author Rod Johnson 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 46d98634..c4c54832 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 @@ -158,7 +158,7 @@ class ConfigurationClassParser { this.resourceLoader = resourceLoader; this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( - resourceLoader, environment, componentScanBeanNameGenerator, registry); + environment, resourceLoader, componentScanBeanNameGenerator, registry); this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } @@ -509,7 +509,8 @@ class ConfigurationClassParser { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); - invokeAwareMethods(selector); + ParserStrategyUtils.invokeAwareMethods( + selector, this.environment, this.resourceLoader, this.registry); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); @@ -526,7 +527,8 @@ class ConfigurationClassParser { Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); - invokeAwareMethods(registrar); + ParserStrategyUtils.invokeAwareMethods( + registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { @@ -565,30 +567,6 @@ class ConfigurationClassParser { return false; } - /** - * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and - * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}. - */ - private void invokeAwareMethods(Object importStrategyBean) { - if (importStrategyBean instanceof Aware) { - if (importStrategyBean instanceof EnvironmentAware) { - ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment); - } - if (importStrategyBean instanceof ResourceLoaderAware) { - ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader); - } - if (importStrategyBean instanceof BeanClassLoaderAware) { - ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ? - ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() : - this.resourceLoader.getClassLoader()); - ((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader); - } - if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) { - ((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry); - } - } - } - /** * Validate each {@link ConfigurationClass} object. diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 301256a6..d86ac32f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -205,6 +205,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo public void setResourceLoader(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); this.resourceLoader = resourceLoader; + if (!this.setMetadataReaderFactoryCalled) { + this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + } } @Override diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java new file mode 100644 index 00000000..f1ced515 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java @@ -0,0 +1,65 @@ +/* + * 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.annotation; + +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.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; + +/** + * Common delegate code for the handling of parser strategies, e.g. + * {@code TypeFilter}, {@code ImportSelector}, {@code ImportBeanDefinitionRegistrar} + * + * @author Juergen Hoeller + * @since 4.3.3 + */ +abstract class ParserStrategyUtils { + + /** + * Invoke {@link BeanClassLoaderAware}, {@link BeanFactoryAware}, + * {@link EnvironmentAware}, and {@link ResourceLoaderAware} contracts + * if implemented by the given object. + */ + public static void invokeAwareMethods(Object parserStrategyBean, Environment environment, + ResourceLoader resourceLoader, BeanDefinitionRegistry registry) { + + if (parserStrategyBean instanceof Aware) { + if (parserStrategyBean instanceof BeanClassLoaderAware) { + ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ? + ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader()); + ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader); + } + if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) { + ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry); + } + if (parserStrategyBean instanceof EnvironmentAware) { + ((EnvironmentAware) parserStrategyBean).setEnvironment(environment); + } + if (parserStrategyBean instanceof ResourceLoaderAware) { + ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader); + } + } + } + +} 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 a57eb0c6..48de2bff 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 @@ -16,7 +16,6 @@ package org.springframework.context.event; -import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; @@ -53,6 +52,7 @@ import org.springframework.util.StringUtils; * evaluated prior to invoking the underlying method. * * @author Stephane Nicoll + * @author Juergen Hoeller * @author Sam Brannen * @since 4.2 */ @@ -70,26 +70,58 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe private final List<ResolvableType> declaredEventTypes; + private final String condition; + + private final int order; + private final AnnotatedElementKey methodKey; private ApplicationContext applicationContext; private EventExpressionEvaluator evaluator; - private String condition; - - private EventListener eventListener; - public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) { this.beanName = beanName; this.method = method; this.targetClass = targetClass; this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); - this.declaredEventTypes = resolveDeclaredEventTypes(); - this.methodKey = new AnnotatedElementKey(this.method, this.targetClass); + + EventListener ann = AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class); + this.declaredEventTypes = resolveDeclaredEventTypes(method, ann); + this.condition = (ann != null ? ann.condition() : null); + this.order = resolveOrder(method); + + this.methodKey = new AnnotatedElementKey(method, targetClass); + } + + + private List<ResolvableType> resolveDeclaredEventTypes(Method method, EventListener ann) { + int count = method.getParameterTypes().length; + if (count > 1) { + throw new IllegalStateException( + "Maximum one parameter is allowed for event listener method: " + method); + } + if (ann != null && ann.classes().length > 0) { + List<ResolvableType> types = new ArrayList<ResolvableType>(ann.classes().length); + for (Class<?> eventType : ann.classes()) { + types.add(ResolvableType.forClass(eventType)); + } + return types; + } + else { + if (count == 0) { + throw new IllegalStateException( + "Event parameter is mandatory for event listener method: " + method); + } + return Collections.singletonList(ResolvableType.forMethodParameter(method, 0)); + } } + private int resolveOrder(Method method) { + Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class); + return (ann != null ? ann.value() : 0); + } /** * Initialize this instance. @@ -128,8 +160,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe @Override public int getOrder() { - Order order = getMethodAnnotation(Order.class); - return (order != null ? order.value() : 0); + return this.order; } @@ -164,8 +195,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe if (this.method.getParameterTypes().length == 0) { return new Object[0]; } - if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) - && event instanceof PayloadApplicationEvent) { + if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) && + event instanceof PayloadApplicationEvent) { return new Object[] {((PayloadApplicationEvent) event).getPayload()}; } else { @@ -212,10 +243,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe return true; } - protected <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { - return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); - } - /** * Invoke the event listener method with the given argument values. */ @@ -253,13 +280,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe return this.applicationContext.getBean(this.beanName); } - protected EventListener getEventListener() { - if (this.eventListener == null) { - this.eventListener = AnnotatedElementUtils.findMergedAnnotation(this.method, EventListener.class); - } - return this.eventListener; - } - /** * Return the condition to use. * <p>Matches the {@code condition} attribute of the {@link EventListener} @@ -267,12 +287,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe * is meta-annotated with {@code @EventListener}. */ protected String getCondition() { - if (this.condition == null) { - EventListener eventListener = AnnotatedElementUtils.findMergedAnnotation(this.method, EventListener.class); - if (eventListener != null) { - this.condition = eventListener.condition(); - } - } return this.condition; } @@ -346,29 +360,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe return null; } - private List<ResolvableType> resolveDeclaredEventTypes() { - int count = this.method.getParameterTypes().length; - if (count > 1) { - throw new IllegalStateException( - "Maximum one parameter is allowed for event listener method: " + this.method); - } - EventListener ann = getEventListener(); - if (ann != null && ann.classes().length > 0) { - List<ResolvableType> types = new ArrayList<ResolvableType>(); - for (Class<?> eventType : ann.classes()) { - types.add(ResolvableType.forClass(eventType)); - } - return types; - } - else { - if (count == 0) { - throw new IllegalStateException( - "Event parameter is mandatory for event listener method: " + this.method); - } - return Collections.singletonList(ResolvableType.forMethodParameter(this.method, 0)); - } - } - @Override public String toString() { 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 6c564535..816069f9 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 @@ -17,6 +17,7 @@ package org.springframework.context.expression; import java.lang.reflect.Method; +import java.util.Arrays; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -34,27 +35,27 @@ import org.springframework.util.ObjectUtils; * </ol> * * @author Stephane Nicoll - * @author Sergey Podgurskiy + * @author Juergen Hoeller * @since 4.2 */ public class MethodBasedEvaluationContext extends StandardEvaluationContext { private final Method method; - private final Object[] args; + private final Object[] arguments; - private final ParameterNameDiscoverer paramDiscoverer; + private final ParameterNameDiscoverer parameterNameDiscoverer; - private boolean paramLoaded = false; + private boolean argumentsLoaded = false; - public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] args, - ParameterNameDiscoverer paramDiscoverer) { + public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] arguments, + ParameterNameDiscoverer parameterNameDiscoverer) { super(rootObject); this.method = method; - this.args = args; - this.paramDiscoverer = paramDiscoverer; + this.arguments = arguments; + this.parameterNameDiscoverer = parameterNameDiscoverer; } @@ -64,9 +65,9 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext { if (variable != null) { return variable; } - if (!this.paramLoaded) { + if (!this.argumentsLoaded) { lazyLoadArguments(); - this.paramLoaded = true; + this.argumentsLoaded = true; variable = super.lookupVariable(name); } return variable; @@ -76,22 +77,30 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext { * Load the param information only when needed. */ protected void lazyLoadArguments() { - // shortcut if no args need to be loaded - if (ObjectUtils.isEmpty(this.args)) { + // Shortcut if no args need to be loaded + if (ObjectUtils.isEmpty(this.arguments)) { return; } - // save arguments as indexed variables - for (int i = 0; i < this.args.length; i++) { - setVariable("a" + i, this.args[i]); - setVariable("p" + i, this.args[i]); - } + // Expose indexed variables as well as parameter names (if discoverable) + String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method); + int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterTypes().length); + int argsCount = this.arguments.length; - String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method); - // save parameter names (if discovered) - if (parameterNames != null) { - for (int i = 0; i < this.args.length; i++) { - setVariable(parameterNames[i], this.args[i]); + for (int i = 0; i < paramCount; i++) { + Object value = null; + if (argsCount > paramCount && i == paramCount - 1) { + // Expose remaining arguments as vararg array for last parameter + value = Arrays.copyOfRange(this.arguments, i, argsCount); + } + else if (argsCount > i) { + // Actual argument found - otherwise left as null + value = this.arguments[i]; + } + setVariable("a" + i, value); + setVariable("p" + i, value); + if (paramNames != null) { + setVariable(paramNames[i], value); } } } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index aff0017e..754ceafe 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.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. @@ -91,6 +91,8 @@ public class GenericApplicationContext extends AbstractApplicationContext implem private ResourceLoader resourceLoader; + private boolean customClassLoader = false; + private final AtomicBoolean refreshed = new AtomicBoolean(); @@ -198,6 +200,10 @@ public class GenericApplicationContext extends AbstractApplicationContext implem } + //--------------------------------------------------------------------- + // ResourceLoader / ResourcePatternResolver override if necessary + //--------------------------------------------------------------------- + /** * This implementation delegates to this context's ResourceLoader if set, * falling back to the default superclass behavior else. @@ -225,6 +231,20 @@ public class GenericApplicationContext extends AbstractApplicationContext implem return super.getResources(locationPattern); } + @Override + public void setClassLoader(ClassLoader classLoader) { + super.setClassLoader(classLoader); + this.customClassLoader = true; + } + + @Override + public ClassLoader getClassLoader() { + if (this.resourceLoader != null && !this.customClassLoader) { + return this.resourceLoader.getClassLoader(); + } + return super.getClassLoader(); + } + //--------------------------------------------------------------------- // Implementations of AbstractApplicationContext's template methods diff --git a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java index b77e6046..abac5b27 100644 --- a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java +++ b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.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. @@ -165,6 +165,9 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar result.append(",\n"); } result.append("{\n\"bean\": \"").append(beanName).append("\",\n"); + result.append("\"aliases\": "); + appendArray(result, bf.getAliases(beanName)); + result.append(",\n"); String scope = bd.getScope(); if (!StringUtils.hasText(scope)) { scope = BeanDefinition.SCOPE_SINGLETON; @@ -178,16 +181,9 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar result.append("\"type\": null,\n"); } result.append("\"resource\": \"").append(getEscapedResourceDescription(bd)).append("\",\n"); - result.append("\"dependencies\": ["); - String[] dependencies = bf.getDependenciesForBean(beanName); - if (dependencies.length > 0) { - result.append("\""); - } - result.append(StringUtils.arrayToDelimitedString(dependencies, "\", \"")); - if (dependencies.length > 0) { - result.append("\""); - } - result.append("]\n}"); + result.append("\"dependencies\": "); + appendArray(result, bf.getDependenciesForBean(beanName)); + result.append("\n}"); elementAppended = true; } } @@ -241,4 +237,16 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar return result.toString(); } + private void appendArray(StringBuilder result, String[] arr) { + result.append('['); + if (arr.length > 0) { + result.append('\"'); + } + result.append(StringUtils.arrayToDelimitedString(arr, "\", \"")); + if (arr.length > 0) { + result.append('\"'); + } + result.append(']'); + } + } 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 3c84ff39..e52d5d4f 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 @@ -104,7 +104,7 @@ public @interface DateTimeFormat { /** * The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSZ}, - * e.g. "2000-10-31 01:30:00.000-05:00". + * e.g. "2000-10-31T01:30:00.000-05:00". * <p>This is the default if no annotation value is specified. */ DATE_TIME, diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java index b1115366..6806628b 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.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. @@ -191,7 +191,7 @@ public class DateFormatter implements Formatter<Date> { if (timeStyle != -1) { return DateFormat.getTimeInstance(timeStyle, locale); } - throw new IllegalStateException("Unsupported style pattern '"+ this.stylePattern+ "'"); + throw new IllegalStateException("Unsupported style pattern '" + this.stylePattern + "'"); } return DateFormat.getDateInstance(this.style, locale); diff --git a/spring-context/src/main/java/org/springframework/format/support/FormatterPropertyEditorAdapter.java b/spring-context/src/main/java/org/springframework/format/support/FormatterPropertyEditorAdapter.java index 7d490f5d..c3812473 100644 --- a/spring-context/src/main/java/org/springframework/format/support/FormatterPropertyEditorAdapter.java +++ b/spring-context/src/main/java/org/springframework/format/support/FormatterPropertyEditorAdapter.java @@ -18,7 +18,6 @@ package org.springframework.format.support; import java.beans.PropertyEditor; import java.beans.PropertyEditorSupport; -import java.text.ParseException; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.format.Formatter; @@ -65,7 +64,10 @@ public class FormatterPropertyEditorAdapter extends PropertyEditorSupport { try { setValue(this.formatter.parse(text, LocaleContextHolder.getLocale())); } - catch (ParseException ex) { + catch (IllegalArgumentException ex) { + throw ex; + } + catch (Throwable ex) { throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex); } } diff --git a/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java b/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java index e65e4fbd..342607ed 100644 --- a/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java +++ b/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java @@ -17,7 +17,6 @@ package org.springframework.format.support; import java.lang.annotation.Annotation; -import java.text.ParseException; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -193,11 +192,14 @@ public class FormattingConversionService extends GenericConversionService try { result = this.parser.parse(text, LocaleContextHolder.getLocale()); } - catch (ParseException ex) { + catch (IllegalArgumentException ex) { + throw ex; + } + catch (Throwable ex) { throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex); } if (result == null) { - throw new IllegalStateException("Parsers are not allowed to return null"); + throw new IllegalStateException("Parsers are not allowed to return null: " + this.parser); } TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass()); if (!resultType.isAssignableTo(targetType)) { 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 89d35299..7b5cb34f 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 @@ -19,6 +19,7 @@ package org.springframework.scheduling.annotation; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -113,7 +114,7 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64)); private final Map<Object, Set<ScheduledTask>> scheduledTasks = - new ConcurrentHashMap<Object, Set<ScheduledTask>>(16); + new IdentityHashMap<Object, Set<ScheduledTask>>(16); @Override @@ -263,8 +264,8 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea new MethodIntrospector.MetadataLookup<Set<Scheduled>>() { @Override public Set<Scheduled> inspect(Method method) { - Set<Scheduled> scheduledMethods = - AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class); + Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( + method, Scheduled.class, Schedules.class); return (!scheduledMethods.isEmpty() ? scheduledMethods : null); } }); @@ -302,11 +303,7 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea 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); - } + Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4); // Determine initial delay long initialDelay = scheduled.initialDelay(); @@ -400,6 +397,16 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea // Check whether we had any attribute set Assert.isTrue(processedSchedule, errorMessage); + + // Finally register the scheduled tasks + synchronized (this.scheduledTasks) { + Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean); + if (registeredTasks == null) { + registeredTasks = new LinkedHashSet<ScheduledTask>(4); + this.scheduledTasks.put(bean, registeredTasks); + } + registeredTasks.addAll(tasks); + } } catch (IllegalArgumentException ex) { throw new IllegalStateException( @@ -410,7 +417,10 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea @Override public void postProcessBeforeDestruction(Object bean, String beanName) { - Set<ScheduledTask> tasks = this.scheduledTasks.remove(bean); + Set<ScheduledTask> tasks; + synchronized (this.scheduledTasks) { + tasks = this.scheduledTasks.remove(bean); + } if (tasks != null) { for (ScheduledTask task : tasks) { task.cancel(); @@ -420,18 +430,22 @@ public class ScheduledAnnotationBeanPostProcessor implements DestructionAwareBea @Override public boolean requiresDestruction(Object bean) { - return this.scheduledTasks.containsKey(bean); + synchronized (this.scheduledTasks) { + 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(); + synchronized (this.scheduledTasks) { + Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values(); + for (Set<ScheduledTask> tasks : allTasks) { + for (ScheduledTask task : tasks) { + task.cancel(); + } } + this.scheduledTasks.clear(); } - this.scheduledTasks.clear(); this.registrar.destroy(); } 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 c7b356fd..09d14360 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 @@ -42,6 +42,7 @@ import org.springframework.util.StringUtils; * <li>"0 0 * * * *" = the top of every hour of every day.</li> * <li>"*/10 * * * * *" = every ten seconds.</li> * <li>"0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.</li> + * <li>"0 * 6,19 * * *" = 6:00 AM and 7:00 PM every day.</li> * <li>"0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.</li> * <li>"0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays</li> * <li>"0 0 0 25 12 ?" = every Christmas Day at midnight</li> @@ -115,7 +116,7 @@ public class CronSequenceGenerator { /* The plan: - 1 Round up to the next whole second + 1 Start with whole second (rounding up if necessary) 2 If seconds match move on, otherwise find the next match: 2.1 If next match is in the next minute then roll forwards @@ -127,8 +128,6 @@ public class CronSequenceGenerator { 4 If hour matches move on, otherwise find the next match 4.1 If next match is in the next day then roll forwards, 4.2 Reset the minutes and seconds and go to 2 - - ... */ Calendar calendar = new GregorianCalendar(); @@ -427,7 +426,7 @@ public class CronSequenceGenerator { @Override public String toString() { - return (getClass().getSimpleName() + ": " + this.expression); + return getClass().getSimpleName() + ": " + this.expression; } } diff --git a/spring-context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java index 83d72a9d..bd4aa553 100644 --- a/spring-context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.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,17 +35,17 @@ import org.springframework.util.xml.DomUtils; /** * BeanDefinitionParser implementation for the '{@code <lang:groovy/>}', - * '{@code <lang:jruby/>}' and '{@code <lang:bsh/>}' tags. + * '{@code <lang:std/>}' and '{@code <lang:bsh/>}' tags. * Allows for objects written using dynamic languages to be easily exposed with * the {@link org.springframework.beans.factory.BeanFactory}. * - * <p>The script for each object can be specified either as a reference to the Resource - * containing it (using the '{@code script-source}' attribute) or inline in the XML configuration - * itself (using the '{@code inline-script}' attribute. + * <p>The script for each object can be specified either as a reference to the + * resource containing it (using the '{@code script-source}' attribute) or inline + * in the XML configuration itself (using the '{@code inline-script}' attribute. * - * <p>By default, dynamic objects created with these tags are <strong>not</strong> refreshable. - * To enable refreshing, specify the refresh check delay for each object (in milliseconds) using the - * '{@code refresh-check-delay}' attribute. + * <p>By default, dynamic objects created with these tags are <strong>not</strong> + * refreshable. To enable refreshing, specify the refresh check delay for each + * object (in milliseconds) using the '{@code refresh-check-delay}' attribute. * * @author Rob Harrop * @author Rod Johnson @@ -176,14 +176,13 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { // Attach any refresh metadata. String refreshCheckDelay = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); if (StringUtils.hasText(refreshCheckDelay)) { - bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, new Long(refreshCheckDelay)); + bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, Long.valueOf(refreshCheckDelay)); } // Attach any proxy target class metadata. String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); if (StringUtils.hasText(proxyTargetClass)) { - Boolean flag = new Boolean(proxyTargetClass); - bd.setAttribute(ScriptFactoryPostProcessor.PROXY_TARGET_CLASS_ATTRIBUTE, flag); + bd.setAttribute(ScriptFactoryPostProcessor.PROXY_TARGET_CLASS_ATTRIBUTE, Boolean.valueOf(proxyTargetClass)); } // Add constructor arguments. diff --git a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptEvaluator.java b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptEvaluator.java index 3bcf53f0..e94b9d40 100644 --- a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptEvaluator.java +++ b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptEvaluator.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,8 @@ import java.util.Map; import groovy.lang.Binding; import groovy.lang.GroovyRuntimeException; import groovy.lang.GroovyShell; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.scripting.ScriptCompilationException; @@ -40,6 +42,8 @@ public class GroovyScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAw private ClassLoader classLoader; + private CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + /** * Construct a new GroovyScriptEvaluator. @@ -56,6 +60,35 @@ public class GroovyScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAw } + /** + * Set a custom compiler configuration for this evaluator. + * @since 4.3.3 + * @see #setCompilationCustomizers + */ + public void setCompilerConfiguration(CompilerConfiguration compilerConfiguration) { + this.compilerConfiguration = + (compilerConfiguration != null ? compilerConfiguration : new CompilerConfiguration()); + } + + /** + * Return this evaluator's compiler configuration (never {@code null}). + * @since 4.3.3 + * @see #setCompilerConfiguration + */ + public CompilerConfiguration getCompilerConfiguration() { + return this.compilerConfiguration; + } + + /** + * Set one or more customizers to be applied to this evaluator's compiler configuration. + * <p>Note that this modifies the shared compiler configuration held by this evaluator. + * @since 4.3.3 + * @see #setCompilerConfiguration + */ + public void setCompilationCustomizers(CompilationCustomizer... compilationCustomizers) { + this.compilerConfiguration.addCompilationCustomizers(compilationCustomizers); + } + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; @@ -69,7 +102,8 @@ public class GroovyScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAw @Override public Object evaluate(ScriptSource script, Map<String, Object> arguments) { - GroovyShell groovyShell = new GroovyShell(this.classLoader, new Binding(arguments)); + GroovyShell groovyShell = new GroovyShell( + this.classLoader, new Binding(arguments), this.compilerConfiguration); try { String filename = (script instanceof ResourceScriptSource ? ((ResourceScriptSource) script).getResource().getFilename() : null); 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 fdc2bb83..6e84456d 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 @@ -17,12 +17,15 @@ package org.springframework.scripting.groovy; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import groovy.lang.Script; import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; @@ -33,6 +36,8 @@ import org.springframework.scripting.ScriptFactory; import org.springframework.scripting.ScriptSource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.scripting.ScriptFactory} implementation @@ -55,7 +60,9 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea private final String scriptSourceLocator; - private final GroovyObjectCustomizer groovyObjectCustomizer; + private GroovyObjectCustomizer groovyObjectCustomizer; + + private CompilerConfiguration compilerConfiguration; private GroovyClassLoader groovyClassLoader; @@ -78,27 +85,62 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea * Interpreted by the post-processor that actually creates the script. */ public GroovyScriptFactory(String scriptSourceLocator) { - this(scriptSourceLocator, null); + Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); + this.scriptSourceLocator = scriptSourceLocator; } /** * Create a new GroovyScriptFactory for the given script source, * specifying a strategy interface that can create a custom MetaClass * to supply missing methods and otherwise change the behavior of the object. - * <p>We don't need to specify script interfaces here, since - * a Groovy script defines its Java interfaces itself. * @param scriptSourceLocator a locator that points to the source of the script. * Interpreted by the post-processor that actually creates the script. * @param groovyObjectCustomizer a customizer that can set a custom metaclass * or make other changes to the GroovyObject created by this factory * (may be {@code null}) + * @see GroovyObjectCustomizer#customize */ public GroovyScriptFactory(String scriptSourceLocator, GroovyObjectCustomizer groovyObjectCustomizer) { - Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); - this.scriptSourceLocator = scriptSourceLocator; + this(scriptSourceLocator); this.groovyObjectCustomizer = groovyObjectCustomizer; } + /** + * Create a new GroovyScriptFactory for the given script source, + * specifying a strategy interface that can create a custom MetaClass + * to supply missing methods and otherwise change the behavior of the object. + * @param scriptSourceLocator a locator that points to the source of the script. + * Interpreted by the post-processor that actually creates the script. + * @param compilerConfiguration a custom compiler configuration to be applied + * to the GroovyClassLoader (may be {@code null}) + * @since 4.3.3 + * @see GroovyClassLoader#GroovyClassLoader(ClassLoader, CompilerConfiguration) + */ + public GroovyScriptFactory(String scriptSourceLocator, CompilerConfiguration compilerConfiguration) { + this(scriptSourceLocator); + this.compilerConfiguration = compilerConfiguration; + } + + /** + * Create a new GroovyScriptFactory for the given script source, + * specifying a strategy interface that can customize Groovy's compilation + * process within the underlying GroovyClassLoader. + * @param scriptSourceLocator a locator that points to the source of the script. + * Interpreted by the post-processor that actually creates the script. + * @param compilationCustomizers one or more customizers to be applied to the + * GroovyClassLoader compiler configuration + * @since 4.3.3 + * @see CompilerConfiguration#addCompilationCustomizers + * @see org.codehaus.groovy.control.customizers.ImportCustomizer + */ + public GroovyScriptFactory(String scriptSourceLocator, CompilationCustomizer... compilationCustomizers) { + this(scriptSourceLocator); + if (!ObjectUtils.isEmpty(compilationCustomizers)) { + this.compilerConfiguration = new CompilerConfiguration(); + this.compilerConfiguration.addCompilationCustomizers(compilationCustomizers); + } + } + @Override public void setBeanFactory(BeanFactory beanFactory) { @@ -109,7 +151,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea @Override public void setBeanClassLoader(ClassLoader classLoader) { - this.groovyClassLoader = new GroovyClassLoader(classLoader); + this.groovyClassLoader = buildGroovyClassLoader(classLoader); } /** @@ -118,12 +160,22 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea public GroovyClassLoader getGroovyClassLoader() { synchronized (this.scriptClassMonitor) { if (this.groovyClassLoader == null) { - this.groovyClassLoader = new GroovyClassLoader(ClassUtils.getDefaultClassLoader()); + this.groovyClassLoader = buildGroovyClassLoader(ClassUtils.getDefaultClassLoader()); } return this.groovyClassLoader; } } + /** + * Build a {@link GroovyClassLoader} for the given {@code ClassLoader}. + * @param classLoader the ClassLoader to build a GroovyClassLoader for + * @since 4.3.3 + */ + protected GroovyClassLoader buildGroovyClassLoader(ClassLoader classLoader) { + return (this.compilerConfiguration != null ? + new GroovyClassLoader(classLoader, this.compilerConfiguration) : new GroovyClassLoader(classLoader)); + } + @Override public String getScriptSourceLocator() { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index bf4c9e6a..88d27eac 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -20,9 +20,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import javax.inject.Provider; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; @@ -65,6 +68,7 @@ import static org.junit.Assert.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen */ public class ConfigurationClassProcessingTests { @@ -92,40 +96,57 @@ public class ConfigurationClassProcessingTests { } + @Rule + public final ExpectedException exception = ExpectedException.none(); + + + @Test + public void customBeanNameIsRespectedWhenConfiguredViaNameAttribute() { + customBeanNameIsRespected(ConfigWithBeanWithCustomName.class, + () -> ConfigWithBeanWithCustomName.testBean, "customName"); + } + @Test - public void customBeanNameIsRespected() { + public void customBeanNameIsRespectedWhenConfiguredViaValueAttribute() { + customBeanNameIsRespected(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class, + () -> ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.testBean, "enigma"); + } + + private void customBeanNameIsRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) { GenericApplicationContext ac = new GenericApplicationContext(); AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); - ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithCustomName.class)); + ac.registerBeanDefinition("config", new RootBeanDefinition(testClass)); ac.refresh(); - assertSame(ac.getBean("customName"), ConfigWithBeanWithCustomName.testBean); + + assertSame(testBeanSupplier.get(), ac.getBean(beanName)); // method name should not be registered - try { - ac.getBean("methodName"); - fail("bean should not have been registered with 'methodName'"); - } - catch (NoSuchBeanDefinitionException ex) { - // expected - } + exception.expect(NoSuchBeanDefinitionException.class); + ac.getBean("methodName"); } @Test - public void aliasesAreRespected() { - BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class); - assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean); - String[] aliases = factory.getAliases("name1"); - for (String alias : aliases) - assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean); + public void aliasesAreRespectedWhenConfiguredViaNameAttribute() { + aliasesAreRespected(ConfigWithBeanWithAliases.class, + () -> ConfigWithBeanWithAliases.testBean, "name1"); + } + + @Test + public void aliasesAreRespectedWhenConfiguredViaValueAttribute() { + aliasesAreRespected(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class, + () -> ConfigWithBeanWithAliasesConfiguredViaValueAttribute.testBean, "enigma"); + } + + private void aliasesAreRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) { + TestBean testBean = testBeanSupplier.get(); + BeanFactory factory = initBeanFactory(testClass); + + assertSame(testBean, factory.getBean(beanName)); + Arrays.stream(factory.getAliases(beanName)).map(factory::getBean).forEach(alias -> assertSame(testBean, alias)); // method name should not be registered - try { - factory.getBean("methodName"); - fail("bean should not have been registered with 'methodName'"); - } - catch (NoSuchBeanDefinitionException ex) { - // expected - } + exception.expect(NoSuchBeanDefinitionException.class); + factory.getBean("methodName"); } @Test // SPR-11830 @@ -146,8 +167,9 @@ public class ConfigurationClassProcessingTests { assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set); } - @Test(expected=BeanDefinitionParsingException.class) + @Test public void testFinalBeanMethod() { + exception.expect(BeanDefinitionParsingException.class); initBeanFactory(ConfigWithFinalBean.class); } @@ -219,6 +241,7 @@ public class ConfigurationClassProcessingTests { adaptive = factory.getBean(AdaptiveInjectionPoints.class); assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName()); assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName()); + factory.close(); } @Test @@ -240,15 +263,28 @@ public class ConfigurationClassProcessingTests { SpousyTestBean listener = factory.getBean("listenerTestBean", SpousyTestBean.class); assertTrue(listener.refreshed); + factory.close(); } @Configuration static class ConfigWithBeanWithCustomName { - static TestBean testBean = new TestBean(); + static TestBean testBean = new TestBean(ConfigWithBeanWithCustomName.class.getSimpleName()); - @Bean(name="customName") + @Bean(name = "customName") + public TestBean methodName() { + return testBean; + } + } + + + @Configuration + static class ConfigWithBeanWithCustomNameConfiguredViaValueAttribute { + + static TestBean testBean = new TestBean(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class.getSimpleName()); + + @Bean("enigma") public TestBean methodName() { return testBean; } @@ -258,9 +294,21 @@ public class ConfigurationClassProcessingTests { @Configuration static class ConfigWithBeanWithAliases { - static TestBean testBean = new TestBean(); + static TestBean testBean = new TestBean(ConfigWithBeanWithAliases.class.getSimpleName()); + + @Bean(name = { "name1", "alias1", "alias2", "alias3" }) + public TestBean methodName() { + return testBean; + } + } + + + @Configuration + static class ConfigWithBeanWithAliasesConfiguredViaValueAttribute { + + static TestBean testBean = new TestBean(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class.getSimpleName()); - @Bean(name={"name1", "alias1", "alias2", "alias3"}) + @Bean({ "enigma", "alias1", "alias2", "alias3" }) public TestBean methodName() { return testBean; } @@ -270,9 +318,9 @@ public class ConfigurationClassProcessingTests { @Configuration static class ConfigWithBeanWithProviderImplementation implements Provider<TestBean> { - static TestBean testBean = new TestBean(); + static TestBean testBean = new TestBean(ConfigWithBeanWithProviderImplementation.class.getSimpleName()); - @Bean(name="customName") + @Bean(name = "customName") public TestBean get() { return testBean; } @@ -284,7 +332,7 @@ public class ConfigurationClassProcessingTests { static Set<String> set = Collections.singleton("value"); - @Bean(name="customName") + @Bean(name = "customName") public Set<String> get() { return set; } @@ -406,7 +454,7 @@ public class ConfigurationClassProcessingTests { }; } - //@Bean + // @Bean public BeanFactoryPostProcessor beanFactoryPostProcessor() { return new BeanFactoryPostProcessor() { @Override diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java index 89738660..438c126f 100644 --- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java @@ -77,7 +77,7 @@ public class AnnotationDrivenEventListenerTests { private EventCollector eventCollector; - private CountDownLatch countDownLatch; // 1 call by default + private CountDownLatch countDownLatch; // 1 call by default @After @@ -93,16 +93,23 @@ public class AnnotationDrivenEventListenerTests { load(TestEventListener.class); TestEvent event = new TestEvent(this, "test"); TestEventListener listener = this.context.getBean(TestEventListener.class); + this.eventCollector.assertNoEventReceived(listener); this.context.publishEvent(event); this.eventCollector.assertEvent(listener, event); this.eventCollector.assertTotalEventsCount(1); + + this.eventCollector.clear(); + this.context.publishEvent(event); + this.eventCollector.assertEvent(listener, event); + this.eventCollector.assertTotalEventsCount(1); } @Test public void simpleEventXmlConfig() { this.context = new ClassPathXmlApplicationContext( "org/springframework/context/event/simple-event-configuration.xml"); + TestEvent event = new TestEvent(this, "test"); TestEventListener listener = this.context.getBean(TestEventListener.class); this.eventCollector = getEventCollector(this.context); @@ -116,7 +123,6 @@ public class AnnotationDrivenEventListenerTests { @Test public void metaAnnotationIsDiscovered() { load(MetaAnnotationListenerTestBean.class); - MetaAnnotationListenerTestBean bean = this.context.getBean(MetaAnnotationListenerTestBean.class); this.eventCollector.assertNoEventReceived(bean); diff --git a/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java b/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java index 9df254c2..cbbee976 100644 --- a/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java +++ b/spring-context/src/test/java/org/springframework/context/event/test/EventCollector.java @@ -30,6 +30,7 @@ import static org.junit.Assert.*; * Test utility to collect and assert events. * * @author Stephane Nicoll + * @author Juergen Hoeller */ @Component public class EventCollector { @@ -73,7 +74,7 @@ public class EventCollector { */ public void assertEvent(String listenerId, Object... events) { List<Object> actual = this.content.getOrDefault(listenerId, Collections.emptyList()); - assertEquals("wrong number of events", events.length, actual.size()); + assertEquals("Wrong number of events", events.length, actual.size()); for (int i = 0; i < events.length; i++) { assertEquals("Wrong event at index " + i, events[i], actual.get(i)); } @@ -97,8 +98,15 @@ public class EventCollector { for (Map.Entry<String, List<Object>> entry : this.content.entrySet()) { actual += entry.getValue().size(); } - assertEquals("Wrong number of total events (" + this.content.size() + ") " + - "registered listener(s)", number, actual); + assertEquals("Wrong number of total events (" + this.content.size() + + ") registered listener(s)", number, actual); + } + + /** + * Clear the collected events, allowing for reuse of the collector. + */ + public void clear() { + this.content.clear(); } } diff --git a/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java b/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java index 2711b006..cdb05632 100644 --- a/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java @@ -30,17 +30,18 @@ import static org.junit.Assert.*; * Unit tests for {@link MethodBasedEvaluationContext}. * * @author Stephane Nicoll + * @author Juergen Hoeller * @author Sergey Podgurskiy */ public class MethodBasedEvaluationContextTests { private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer(); + @Test public void simpleArguments() { - Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", - String.class, Boolean.class); - MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {"test", true}); + Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class); + MethodBasedEvaluationContext context = createEvaluationContext(method, "test", true); assertEquals("test", context.lookupVariable("a0")); assertEquals("test", context.lookupVariable("p0")); @@ -51,16 +52,21 @@ public class MethodBasedEvaluationContextTests { assertEquals(true, context.lookupVariable("flag")); assertNull(context.lookupVariable("a2")); + assertNull(context.lookupVariable("p2")); } @Test public void nullArgument() { - Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", - String.class, Boolean.class); - MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null}); + Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class); + MethodBasedEvaluationContext context = createEvaluationContext(method, null, null); assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("p0")); + assertNull(context.lookupVariable("foo")); + + assertNull(context.lookupVariable("a1")); + assertNull(context.lookupVariable("p1")); + assertNull(context.lookupVariable("flag")); } @Test @@ -68,39 +74,58 @@ public class MethodBasedEvaluationContextTests { Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null}); + assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("p0")); + assertNull(context.lookupVariable("flag")); + + assertNull(context.lookupVariable("a1")); assertNull(context.lookupVariable("p1")); + assertNull(context.lookupVariable("vararg")); } @Test public void varArgNull() { Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); - MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null}); + MethodBasedEvaluationContext context = createEvaluationContext(method, null, null); + assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("p0")); + assertNull(context.lookupVariable("flag")); + + assertNull(context.lookupVariable("a1")); assertNull(context.lookupVariable("p1")); + assertNull(context.lookupVariable("vararg")); } @Test public void varArgSingle() { Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); - MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, "hello"}); + MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello"); + assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("p0")); + assertNull(context.lookupVariable("flag")); + + assertEquals("hello", context.lookupVariable("a1")); assertEquals("hello", context.lookupVariable("p1")); + assertEquals("hello", context.lookupVariable("vararg")); } @Test public void varArgMultiple() { Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); - MethodBasedEvaluationContext context = createEvaluationContext(method, - new Object[] {null, new String[]{"hello", "hi"}}); + MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello", "hi"); + assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("p0")); - assertArrayEquals(new String[]{"hello", "hi"}, (String[]) context.lookupVariable("p1")); + assertNull(context.lookupVariable("flag")); + + assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("a1")); + assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("p1")); + assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("vararg")); } - private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) { + private MethodBasedEvaluationContext createEvaluationContext(Method method, Object... args) { return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover); } @@ -112,9 +137,7 @@ public class MethodBasedEvaluationContextTests { } private void hello(Boolean flag, String... vararg){ - } - } -}
\ No newline at end of file +} diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java index 90488829..d9d93306 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java @@ -228,7 +228,7 @@ public class EnableMBeanExportConfigurationTests { return new MBeanServerFactoryBean(); } - @Bean(name="bean:name=testBean4") + @Bean("bean:name=testBean4") @Lazy public AnnotationTestBean testBean4() { AnnotationTestBean bean = new AnnotationTestBean(); @@ -237,7 +237,7 @@ public class EnableMBeanExportConfigurationTests { return bean; } - @Bean(name="bean:name=testBean5") + @Bean("bean:name=testBean5") public AnnotationTestBeanFactory testBean5() throws Exception { return new AnnotationTestBeanFactory(); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index 0d55ec7a..afc56975 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -124,6 +124,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.class); asyncTest.doNothing(5); asyncTest.doSomething(10); diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptEvaluatorTests.java b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptEvaluatorTests.java index 14ab7aa3..7921a081 100644 --- a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptEvaluatorTests.java +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptEvaluatorTests.java @@ -19,6 +19,7 @@ package org.springframework.scripting.groovy; import java.util.HashMap; import java.util.Map; +import org.codehaus.groovy.control.customizers.ImportCustomizer; import org.junit.Test; import org.springframework.core.io.ClassPathResource; @@ -59,6 +60,26 @@ public class GroovyScriptEvaluatorTests { } @Test + public void testGroovyScriptWithCompilerConfiguration() { + GroovyScriptEvaluator evaluator = new GroovyScriptEvaluator(); + MyBytecodeProcessor processor = new MyBytecodeProcessor(); + evaluator.getCompilerConfiguration().setBytecodePostprocessor(processor); + Object result = evaluator.evaluate(new StaticScriptSource("return 3 * 2")); + assertEquals(6, result); + assertTrue(processor.processed.contains("Script1")); + } + + @Test + public void testGroovyScriptWithImportCustomizer() { + GroovyScriptEvaluator evaluator = new GroovyScriptEvaluator(); + ImportCustomizer importCustomizer = new ImportCustomizer(); + importCustomizer.addStarImports("org.springframework.util"); + evaluator.setCompilationCustomizers(importCustomizer); + Object result = evaluator.evaluate(new StaticScriptSource("return ResourceUtils.CLASSPATH_URL_PREFIX")); + assertEquals("classpath:", result); + } + + @Test public void testGroovyScriptFromStringUsingJsr223() { StandardScriptEvaluator evaluator = new StandardScriptEvaluator(); evaluator.setLanguage("Groovy"); diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java index c8abcc42..65bec6c9 100644 --- a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java @@ -477,6 +477,8 @@ public class GroovyScriptFactoryTests { ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass()); Map<?, Messenger> beans = ctx.getBeansOfType(Messenger.class); assertEquals(4, beans.size()); + assertTrue(ctx.getBean(MyBytecodeProcessor.class).processed.contains( + "org.springframework.scripting.groovy.GroovyMessenger2")); } @Test diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java b/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java new file mode 100644 index 00000000..12a99139 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/MyBytecodeProcessor.java @@ -0,0 +1,37 @@ +/* + * 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.scripting.groovy; + +import java.util.HashSet; +import java.util.Set; + +import org.codehaus.groovy.control.BytecodeProcessor; + +/** + * @author Juergen Hoeller + */ +public class MyBytecodeProcessor implements BytecodeProcessor { + + public final Set<String> processed = new HashSet<String>(); + + @Override + public byte[] processBytecode(String name, byte[] original) { + this.processed.add(name); + return original; + } + +} diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/MyImportCustomizer.java b/spring-context/src/test/java/org/springframework/scripting/groovy/MyImportCustomizer.java new file mode 100644 index 00000000..24160f50 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/MyImportCustomizer.java @@ -0,0 +1,30 @@ +/* + * 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.scripting.groovy; + +import org.codehaus.groovy.control.customizers.ImportCustomizer; + +/** + * @author Juergen Hoeller + */ +public class MyImportCustomizer extends ImportCustomizer { + + public MyImportCustomizer() { + addStarImports("org.springframework.scripting.groovy"); + } + +} diff --git a/spring-context/src/test/resources/org/springframework/scripting/groovy/groovy-with-xsd.xml b/spring-context/src/test/resources/org/springframework/scripting/groovy/groovy-with-xsd.xml index a573d6a2..070a64d0 100644 --- a/spring-context/src/test/resources/org/springframework/scripting/groovy/groovy-with-xsd.xml +++ b/spring-context/src/test/resources/org/springframework/scripting/groovy/groovy-with-xsd.xml @@ -2,7 +2,7 @@ <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang" - xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd"> @@ -24,7 +24,7 @@ <lang:property name="message" value="Hello World!"/> </lang:groovy> - <lang:groovy id="calculator" depends-on="messenger" customizer-ref="customizer"> + <lang:groovy id="calculator" depends-on="messenger" customizer-ref="groovyObjectCustomizer"> <lang:inline-script> package org.springframework.scripting.groovy; import org.springframework.scripting.Calculator @@ -36,25 +36,32 @@ class GroovyCalculator implements Calculator { </lang:inline-script> </lang:groovy> - <lang:groovy id="customizer"> + <lang:groovy id="groovyObjectCustomizer" customizer-ref="importCustomizer"> <lang:inline-script><![CDATA[ -import org.springframework.scripting.groovy.GroovyObjectCustomizer; - public class TestCustomizer implements GroovyObjectCustomizer { - public void customize(GroovyObject o) { - println "customizing ${o}.." + public void customize(GroovyObject go) { + println "customizing ${go}..." } }]]> </lang:inline-script> </lang:groovy> + <bean id="importCustomizer" class="org.springframework.scripting.groovy.MyImportCustomizer"/> + <lang:groovy id="refreshableMessenger" refresh-check-delay="5000" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Hello World!"/> </lang:groovy> - <lang:groovy script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> - <lang:property name="message" value="Hello World!"/> - </lang:groovy> + <lang:groovy script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy" + customizer-ref="compilerConfiguration"> + <lang:property name="message" value="Hello World!"/> + </lang:groovy> + + <bean id="compilerConfiguration" class="org.codehaus.groovy.control.CompilerConfiguration"> + <property name="bytecodePostprocessor" ref="bytecodeProcessor"/> + </bean> + + <bean id="bytecodeProcessor" class="org.springframework.scripting.groovy.MyBytecodeProcessor"/> </beans> |