Skip to content

Commit ea4a766

Browse files
committed
Consistent support for SpEL next to placeholders in annotation attributes
Issue: SPR-13625
1 parent e0d7c6b commit ea4a766

File tree

8 files changed

+157
-57
lines changed

8 files changed

+157
-57
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory.config;
18+
19+
import org.springframework.util.StringValueResolver;
20+
21+
/**
22+
* {@link StringValueResolver} adapter for resolving placeholders and
23+
* expressions against a {@link ConfigurableBeanFactory}.
24+
*
25+
* <p>Note that this adapter resolves expressions as well, in contrast
26+
* to the {@link ConfigurableBeanFactory#resolveEmbeddedValue} method.
27+
* The {@link BeanExpressionContext} used is for the plain bean factory,
28+
* with no scope specified for any contextual objects to access.
29+
*
30+
* @author Juergen Hoeller
31+
* @since 4.3
32+
* @see ConfigurableBeanFactory#resolveEmbeddedValue(String)
33+
* @see ConfigurableBeanFactory#getBeanExpressionResolver()
34+
* @see BeanExpressionContext
35+
*/
36+
public class EmbeddedValueResolver implements StringValueResolver {
37+
38+
private final BeanExpressionContext exprContext;
39+
40+
private final BeanExpressionResolver exprResolver;
41+
42+
43+
public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
44+
this.exprContext = new BeanExpressionContext(beanFactory, null);
45+
this.exprResolver = beanFactory.getBeanExpressionResolver();
46+
}
47+
48+
49+
@Override
50+
public String resolveStringValue(String strVal) {
51+
String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
52+
if (this.exprResolver != null && value != null) {
53+
Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
54+
value = (evaluated != null ? evaluated.toString() : null);
55+
}
56+
return value;
57+
}
58+
59+
}

spring-context/src/main/java/org/springframework/context/EmbeddedValueResolverAware.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,9 @@
2929
* @author Juergen Hoeller
3030
* @author Chris Beams
3131
* @since 3.0.3
32-
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue
32+
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveEmbeddedValue(String)
33+
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanExpressionResolver()
34+
* @see org.springframework.beans.factory.config.EmbeddedValueResolver
3335
*/
3436
public interface EmbeddedValueResolverAware extends Aware {
3537

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
5959
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
6060
import org.springframework.beans.factory.config.DependencyDescriptor;
61+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
6162
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
6263
import org.springframework.beans.factory.support.RootBeanDefinition;
6364
import org.springframework.core.BridgeMethodResolver;
@@ -68,6 +69,7 @@
6869
import org.springframework.util.ClassUtils;
6970
import org.springframework.util.ReflectionUtils;
7071
import org.springframework.util.StringUtils;
72+
import org.springframework.util.StringValueResolver;
7173

7274
/**
7375
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
@@ -181,6 +183,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
181183

182184
private transient BeanFactory beanFactory;
183185

186+
private transient StringValueResolver embeddedValueResolver;
187+
184188
private transient final Map<String, InjectionMetadata> injectionMetadataCache =
185189
new ConcurrentHashMap<String, InjectionMetadata>(256);
186190

@@ -274,12 +278,15 @@ public void setResourceFactory(BeanFactory resourceFactory) {
274278
}
275279

276280
@Override
277-
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
281+
public void setBeanFactory(BeanFactory beanFactory) {
278282
Assert.notNull(beanFactory, "BeanFactory must not be null");
279283
this.beanFactory = beanFactory;
280284
if (this.resourceFactory == null) {
281285
this.resourceFactory = beanFactory;
282286
}
287+
if (beanFactory instanceof ConfigurableBeanFactory) {
288+
this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
289+
}
283290
}
284291

285292

@@ -595,8 +602,8 @@ public ResourceElement(Member member, AnnotatedElement ae, PropertyDescriptor pd
595602
resourceName = Introspector.decapitalize(resourceName.substring(3));
596603
}
597604
}
598-
else if (beanFactory instanceof ConfigurableBeanFactory){
599-
resourceName = ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(resourceName);
605+
else if (embeddedValueResolver != null) {
606+
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
600607
}
601608
if (resourceType != null && Object.class != resourceType) {
602609
checkResourceType(resourceType);

spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
2323
import org.springframework.beans.BeansException;
2424
import org.springframework.beans.factory.Aware;
2525
import org.springframework.beans.factory.config.BeanPostProcessor;
26-
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
26+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
2727
import org.springframework.context.ApplicationContextAware;
2828
import org.springframework.context.ApplicationEventPublisherAware;
2929
import org.springframework.context.ConfigurableApplicationContext;
@@ -61,12 +61,15 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
6161

6262
private final ConfigurableApplicationContext applicationContext;
6363

64+
private final StringValueResolver embeddedValueResolver;
65+
6466

6567
/**
6668
* Create a new ApplicationContextAwareProcessor for the given context.
6769
*/
6870
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
6971
this.applicationContext = applicationContext;
72+
this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
7073
}
7174

7275

@@ -103,8 +106,7 @@ private void invokeAwareInterfaces(Object bean) {
103106
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
104107
}
105108
if (bean instanceof EmbeddedValueResolverAware) {
106-
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
107-
new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
109+
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
108110
}
109111
if (bean instanceof ResourceLoaderAware) {
110112
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
@@ -126,19 +128,4 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
126128
return bean;
127129
}
128130

129-
130-
private static class EmbeddedValueResolver implements StringValueResolver {
131-
132-
private final ConfigurableBeanFactory beanFactory;
133-
134-
public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
135-
this.beanFactory = beanFactory;
136-
}
137-
138-
@Override
139-
public String resolveStringValue(String strVal) {
140-
return this.beanFactory.resolveEmbeddedValue(strVal);
141-
}
142-
}
143-
144131
}

spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import org.springframework.beans.factory.BeanFactory;
2828
import org.springframework.beans.factory.BeanFactoryAware;
2929
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
30+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
3031
import org.springframework.core.annotation.AnnotationUtils;
3132
import org.springframework.jmx.export.metadata.InvalidMetadataException;
3233
import org.springframework.jmx.export.metadata.JmxAttributeSource;
@@ -50,19 +51,13 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
5051

5152

5253
@Override
53-
public void setBeanFactory(final BeanFactory beanFactory) {
54+
public void setBeanFactory(BeanFactory beanFactory) {
5455
if (beanFactory instanceof ConfigurableBeanFactory) {
55-
// Not using EmbeddedValueResolverAware in order to avoid a spring-context dependency:
56-
// ConfigurableBeanFactory and its resolveEmbeddedValue live in the spring-beans module.
57-
this.embeddedValueResolver = new StringValueResolver() {
58-
@Override
59-
public String resolveStringValue(String strVal) {
60-
return ((ConfigurableBeanFactory) beanFactory).resolveEmbeddedValue(strVal);
61-
}
62-
};
56+
this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
6357
}
6458
}
6559

60+
6661
@Override
6762
public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
6863
ManagedResource ann = AnnotationUtils.findAnnotation(beanClass, ManagedResource.class);

spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@
2424
import java.lang.reflect.Method;
2525
import java.util.Calendar;
2626
import java.util.Date;
27+
import java.util.HashMap;
2728
import java.util.List;
29+
import java.util.Map;
2830
import java.util.Properties;
2931
import java.util.TimeZone;
3032

@@ -364,8 +366,8 @@ public void propertyPlaceholderWithCron() {
364366
properties.setProperty("schedules.businessHours", businessHoursCronExpression);
365367
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
366368
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class);
367-
context.registerBeanDefinition("placeholder", placeholderDefinition);
368369
context.registerBeanDefinition("postProcessor", processorDefinition);
370+
context.registerBeanDefinition("placeholder", placeholderDefinition);
369371
context.registerBeanDefinition("target", targetDefinition);
370372
context.refresh();
371373

@@ -395,8 +397,8 @@ public void propertyPlaceholderWithFixedDelay() {
395397
properties.setProperty("initialDelay", "1000");
396398
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
397399
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class);
398-
context.registerBeanDefinition("placeholder", placeholderDefinition);
399400
context.registerBeanDefinition("postProcessor", processorDefinition);
401+
context.registerBeanDefinition("placeholder", placeholderDefinition);
400402
context.registerBeanDefinition("target", targetDefinition);
401403
context.refresh();
402404

@@ -427,8 +429,8 @@ public void propertyPlaceholderWithFixedRate() {
427429
properties.setProperty("initialDelay", "1000");
428430
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
429431
BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class);
430-
context.registerBeanDefinition("placeholder", placeholderDefinition);
431432
context.registerBeanDefinition("postProcessor", processorDefinition);
433+
context.registerBeanDefinition("placeholder", placeholderDefinition);
432434
context.registerBeanDefinition("target", targetDefinition);
433435
context.refresh();
434436

@@ -450,6 +452,35 @@ public void propertyPlaceholderWithFixedRate() {
450452
assertEquals(3000L, task.getInterval());
451453
}
452454

455+
@Test
456+
public void expressionWithCron() {
457+
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
458+
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
459+
BeanDefinition targetDefinition = new RootBeanDefinition(ExpressionWithCronTestBean.class);
460+
context.registerBeanDefinition("postProcessor", processorDefinition);
461+
context.registerBeanDefinition("target", targetDefinition);
462+
Map<String, String> schedules = new HashMap<String, String>();
463+
schedules.put("businessHours", businessHoursCronExpression);
464+
context.getBeanFactory().registerSingleton("schedules", schedules);
465+
context.refresh();
466+
467+
Object postProcessor = context.getBean("postProcessor");
468+
Object target = context.getBean("target");
469+
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
470+
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
471+
@SuppressWarnings("unchecked")
472+
List<CronTask> cronTasks = (List<CronTask>)
473+
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
474+
assertEquals(1, cronTasks.size());
475+
CronTask task = cronTasks.get(0);
476+
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
477+
Object targetObject = runnable.getTarget();
478+
Method targetMethod = runnable.getMethod();
479+
assertEquals(target, targetObject);
480+
assertEquals("x", targetMethod.getName());
481+
assertEquals(businessHoursCronExpression, task.getExpression());
482+
}
483+
453484
@Test
454485
public void propertyPlaceholderForMetaAnnotation() {
455486
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
@@ -699,6 +730,14 @@ public void fixedRate() {
699730
}
700731

701732

733+
static class ExpressionWithCronTestBean {
734+
735+
@Scheduled(cron = "#{schedules.businessHours}")
736+
public void x() {
737+
}
738+
}
739+
740+
702741
@Scheduled(cron="${schedules.businessHours}")
703742
@Target(ElementType.METHOD)
704743
@Retention(RetentionPolicy.RUNTIME)

spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.springframework.beans.factory.SmartInitializingSingleton;
3737
import org.springframework.beans.factory.config.BeanPostProcessor;
3838
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
39+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
3940
import org.springframework.core.MethodIntrospector;
4041
import org.springframework.core.Ordered;
4142
import org.springframework.core.annotation.AnnotationUtils;
@@ -49,6 +50,7 @@
4950
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
5051
import org.springframework.util.Assert;
5152
import org.springframework.util.StringUtils;
53+
import org.springframework.util.StringValueResolver;
5254

5355
/**
5456
* Bean post-processor that registers methods annotated with {@link JmsListener}
@@ -95,6 +97,8 @@ public class JmsListenerAnnotationBeanPostProcessor
9597

9698
private BeanFactory beanFactory;
9799

100+
private StringValueResolver embeddedValueResolver;
101+
98102
private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory = new MessageHandlerMethodFactoryAdapter();
99103

100104
private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
@@ -146,6 +150,9 @@ public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHa
146150
@Override
147151
public void setBeanFactory(BeanFactory beanFactory) {
148152
this.beanFactory = beanFactory;
153+
if (beanFactory instanceof ConfigurableBeanFactory) {
154+
this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
155+
}
149156
}
150157

151158

@@ -243,6 +250,7 @@ protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMe
243250
endpoint.setMethod(invocableMethod);
244251
endpoint.setMostSpecificMethod(mostSpecificMethod);
245252
endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
253+
endpoint.setEmbeddedValueResolver(this.embeddedValueResolver);
246254
endpoint.setBeanFactory(this.beanFactory);
247255
endpoint.setId(getEndpointId(jmsListener));
248256
endpoint.setDestination(resolve(jmsListener.destination()));
@@ -293,15 +301,8 @@ private String getEndpointId(JmsListener jmsListener) {
293301
}
294302
}
295303

296-
/**
297-
* Resolve the specified value if possible.
298-
* @see ConfigurableBeanFactory#resolveEmbeddedValue
299-
*/
300304
private String resolve(String value) {
301-
if (this.beanFactory instanceof ConfigurableBeanFactory) {
302-
return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
303-
}
304-
return value;
305+
return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
305306
}
306307

307308

0 commit comments

Comments
 (0)