diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..bd8c1666e00202f9996626606f339c05c29d13a0 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java @@ -0,0 +1,59 @@ +/* + * 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.beans.factory.config; + +import org.springframework.util.StringValueResolver; + +/** + * {@link StringValueResolver} adapter for resolving placeholders and + * expressions against a {@link ConfigurableBeanFactory}. + * + *

Note that this adapter resolves expressions as well, in contrast + * to the {@link ConfigurableBeanFactory#resolveEmbeddedValue} method. + * The {@link BeanExpressionContext} used is for the plain bean factory, + * with no scope specified for any contextual objects to access. + * + * @author Juergen Hoeller + * @since 4.3 + * @see ConfigurableBeanFactory#resolveEmbeddedValue(String) + * @see ConfigurableBeanFactory#getBeanExpressionResolver() + * @see BeanExpressionContext + */ +public class EmbeddedValueResolver implements StringValueResolver { + + private final BeanExpressionContext exprContext; + + private final BeanExpressionResolver exprResolver; + + + public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) { + this.exprContext = new BeanExpressionContext(beanFactory, null); + this.exprResolver = beanFactory.getBeanExpressionResolver(); + } + + + @Override + public String resolveStringValue(String strVal) { + String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal); + if (this.exprResolver != null && value != null) { + Object evaluated = this.exprResolver.evaluate(value, this.exprContext); + value = (evaluated != null ? evaluated.toString() : null); + } + return value; + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java index 98d25eaaff8690529d5fe18fe922547299b2b69d..a3345c2c30f48d6eb5da87084d0ea772e03f5603 100644 --- a/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java +++ b/spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ import org.springframework.util.StringValueResolver; * @author Juergen Hoeller * @author Chris Beams * @since 3.0.3 - * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue(String) + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanExpressionResolver() + * @see org.springframework.beans.factory.config.EmbeddedValueResolver */ public interface EmbeddedValueResolverAware extends Aware { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 66f2506f5dfbbd6939b0bad5c3ae610828fcfe7c..c3dbc0276ba7f92aae8d6020d5b1874f2f59c1c8 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.InjectionMetadata; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.BridgeMethodResolver; @@ -68,6 +69,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; /** * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation @@ -181,6 +183,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private transient BeanFactory beanFactory; + private transient StringValueResolver embeddedValueResolver; + private transient final Map injectionMetadataCache = new ConcurrentHashMap(256); @@ -274,12 +278,15 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { Assert.notNull(beanFactory, "BeanFactory must not be null"); this.beanFactory = beanFactory; if (this.resourceFactory == null) { this.resourceFactory = beanFactory; } + if (beanFactory instanceof ConfigurableBeanFactory) { + this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); + } } @@ -595,8 +602,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean resourceName = Introspector.decapitalize(resourceName.substring(3)); } } - else if (beanFactory instanceof ConfigurableBeanFactory){ - resourceName = ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(resourceName); + else if (embeddedValueResolver != null) { + resourceName = embeddedValueResolver.resolveStringValue(resourceName); } if (resourceType != null && Object.class != resourceType) { checkResourceType(resourceType); diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java index 2a521b3b64a1918f9f21bd904b398f0326f4cc7e..c3f3dc54818361cc8fada22d3661d287ebe5c36f 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.security.PrivilegedAction; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ConfigurableApplicationContext; @@ -61,12 +61,15 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { private final ConfigurableApplicationContext applicationContext; + private final StringValueResolver embeddedValueResolver; + /** * Create a new ApplicationContextAwareProcessor for the given context. */ public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; + this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory()); } @@ -103,8 +106,7 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { - ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver( - new EmbeddedValueResolver(this.applicationContext.getBeanFactory())); + ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); @@ -126,19 +128,4 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { return bean; } - - private static class EmbeddedValueResolver implements StringValueResolver { - - private final ConfigurableBeanFactory beanFactory; - - public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override - public String resolveStringValue(String strVal) { - return this.beanFactory.resolveEmbeddedValue(strVal); - } - } - } diff --git a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java index 0a802724edc97f6f5cfa65c24cffcf96ea182e35..8b07ce08dc9cb345c11b7b3778aab8f19f8858cd 100644 --- a/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java +++ b/spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.springframework.beans.annotation.AnnotationBeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jmx.export.metadata.InvalidMetadataException; import org.springframework.jmx.export.metadata.JmxAttributeSource; @@ -50,19 +51,13 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac @Override - public void setBeanFactory(final BeanFactory beanFactory) { + public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) { - // Not using EmbeddedValueResolverAware in order to avoid a spring-context dependency: - // ConfigurableBeanFactory and its resolveEmbeddedValue live in the spring-beans module. - this.embeddedValueResolver = new StringValueResolver() { - @Override - public String resolveStringValue(String strVal) { - return ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(strVal); - } - }; + this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); } } + @Override public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class beanClass) throws InvalidMetadataException { ManagedResource ann = AnnotationUtils.findAnnotation(beanClass, ManagedResource.class); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index 0da2dddc0bfc0bee211e893b475f34700916fd26..1b7247dc09acda47610a5feb02ec6590e27c40fa 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.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. @@ -24,7 +24,9 @@ import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.TimeZone; @@ -364,8 +366,8 @@ public class ScheduledAnnotationBeanPostProcessorTests { properties.setProperty("schedules.businessHours", businessHoursCronExpression); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class); - context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -395,8 +397,8 @@ public class ScheduledAnnotationBeanPostProcessorTests { properties.setProperty("initialDelay", "1000"); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class); - context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -427,8 +429,8 @@ public class ScheduledAnnotationBeanPostProcessorTests { properties.setProperty("initialDelay", "1000"); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class); - context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -450,6 +452,35 @@ public class ScheduledAnnotationBeanPostProcessorTests { assertEquals(3000L, task.getInterval()); } + @Test + public void expressionWithCron() { + String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class); + context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("target", targetDefinition); + Map schedules = new HashMap(); + schedules.put("businessHours", businessHoursCronExpression); + context.getBeanFactory().registerSingleton("schedules", schedules); + context.refresh(); + + Object postProcessor = context.getBean("postProcessor"); + Object target = context.getBean("target"); + ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) + new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); + @SuppressWarnings("unchecked") + List cronTasks = (List) + new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); + assertEquals(1, cronTasks.size()); + CronTask task = cronTasks.get(0); + ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); + Object targetObject = runnable.getTarget(); + Method targetMethod = runnable.getMethod(); + assertEquals(target, targetObject); + assertEquals("x", targetMethod.getName()); + assertEquals(businessHoursCronExpression, task.getExpression()); + } + @Test public void propertyPlaceholderForMetaAnnotation() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; @@ -699,6 +730,14 @@ public class ScheduledAnnotationBeanPostProcessorTests { } + static class ExpressionWithCronTestBean { + + @Scheduled(cron = "#{schedules.businessHours}") + public void x() { + } + } + + @Scheduled(cron="${schedules.businessHours}") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java index fa9b61b25bb2a09370a916551ea5dc69902753fc..f27b609c4ba58fa5a930dd0c074f478793edc308 100644 --- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java +++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.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. @@ -36,6 +36,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.core.MethodIntrospector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; @@ -49,6 +50,7 @@ import org.springframework.messaging.handler.annotation.support.MessageHandlerMe import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; /** * Bean post-processor that registers methods annotated with {@link JmsListener} @@ -95,6 +97,8 @@ public class JmsListenerAnnotationBeanPostProcessor private BeanFactory beanFactory; + private StringValueResolver embeddedValueResolver; + private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory = new MessageHandlerMethodFactoryAdapter(); private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar(); @@ -146,6 +150,9 @@ public class JmsListenerAnnotationBeanPostProcessor @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; + if (beanFactory instanceof ConfigurableBeanFactory) { + this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); + } } @@ -243,6 +250,7 @@ public class JmsListenerAnnotationBeanPostProcessor endpoint.setMethod(invocableMethod); endpoint.setMostSpecificMethod(mostSpecificMethod); endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory); + endpoint.setEmbeddedValueResolver(this.embeddedValueResolver); endpoint.setBeanFactory(this.beanFactory); endpoint.setId(getEndpointId(jmsListener)); endpoint.setDestination(resolve(jmsListener.destination())); @@ -293,15 +301,8 @@ public class JmsListenerAnnotationBeanPostProcessor } } - /** - * Resolve the specified value if possible. - * @see ConfigurableBeanFactory#resolveEmbeddedValue - */ private String resolve(String value) { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value); - } - return value; + return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); } diff --git a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java index 2b80bf285070ab0e977aab76a0e96767e9a06d84..56a6283a03b92588fd5addff1a5613f89dad861b 100644 --- a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java +++ b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java @@ -22,7 +22,9 @@ import java.util.Arrays; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jms.listener.MessageListenerContainer; import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter; @@ -33,6 +35,7 @@ import org.springframework.messaging.handler.annotation.support.MessageHandlerMe import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; /** * A {@link JmsListenerEndpoint} providing the method to invoke to process @@ -42,7 +45,7 @@ import org.springframework.util.StringUtils; * @author Juergen Hoeller * @since 4.1 */ -public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint { +public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint implements BeanFactoryAware { private Object bean; @@ -52,6 +55,8 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint { private MessageHandlerMethodFactory messageHandlerMethodFactory; + private StringValueResolver embeddedValueResolver; + private BeanFactory beanFactory; @@ -110,12 +115,24 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint { } /** - * Set the {@link BeanFactory} to use to resolve expressions (can be null). + * Set a value resolver for embedded placeholders and expressions. + */ + public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) { + this.embeddedValueResolver = embeddedValueResolver; + } + + /** + * Set the {@link BeanFactory} to use to resolve expressions (can be {@code null}). */ + @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; + if (this.embeddedValueResolver == null && beanFactory instanceof ConfigurableBeanFactory) { + this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); + } } + @Override protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) { Assert.state(this.messageHandlerMethodFactory != null, @@ -179,15 +196,8 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint { } } - /** - * Resolve the specified value if possible. - * @see ConfigurableBeanFactory#resolveEmbeddedValue - */ private String resolve(String value) { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value); - } - return value; + return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); }