diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java
index b411c0330d7..40df0991d71 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java
@@ -25,8 +25,11 @@ import java.lang.annotation.Target;
* Annotation at the field or method/constructor parameter level
* that indicates a default value expression for the affected argument.
*
- *
This is typically used for assigning default field values
- * with "#{systemProperties.myProp}" style expressions.
+ *
Typically used for expression-driven dependency injection. Also supported
+ * for dynamic resolution of handler method parameters, e.g. in Spring MVC.
+ *
+ *
A common use case is to assign default field values using
+ * "#{systemProperties.myProp}" style expressions.
*
* @author Juergen Hoeller
* @since 3.0
diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 532df2f1499..aeeffa92a0e 100644
--- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -53,11 +53,15 @@ import javax.portlet.WindowState;
import javax.servlet.http.Cookie;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.config.BeanExpressionContext;
+import org.springframework.beans.factory.config.BeanExpressionResolver;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.StylerUtils;
-import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
@@ -78,6 +82,7 @@ import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestScope;
import org.springframework.web.portlet.HandlerAdapter;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.MissingPortletRequestParameterException;
@@ -112,7 +117,7 @@ import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
* @see #setWebBindingInitializer
* @see #setSessionAttributeStore
*/
-public class AnnotationMethodHandlerAdapter extends PortletContentGenerator implements HandlerAdapter {
+public class AnnotationMethodHandlerAdapter extends PortletContentGenerator implements HandlerAdapter, BeanFactoryAware {
private static final String IMPLICIT_MODEL_ATTRIBUTE = "org.springframework.web.portlet.mvc.ImplicitModel";
@@ -131,6 +136,10 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
private ModelAndViewResolver[] customModelAndViewResolvers;
+ private ConfigurableBeanFactory beanFactory;
+
+ private BeanExpressionContext expressionContext;
+
private final Map, PortletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap, PortletHandlerMethodResolver>();
@@ -233,6 +242,13 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
this.customModelAndViewResolvers = customModelAndViewResolvers;
}
+ public void setBeanFactory(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.beanFactory = (ConfigurableBeanFactory) beanFactory;
+ this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
+ }
+ }
+
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
@@ -543,7 +559,7 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
public PortletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore,
- parameterNameDiscoverer, customArgumentResolvers, new HttpMessageConverter[0]);
+ parameterNameDiscoverer, customArgumentResolvers, null);
}
@Override
@@ -556,6 +572,19 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl
throw new PortletSessionRequiredException(message);
}
+ @Override
+ protected Object resolveDefaultValue(String value) {
+ if (beanFactory == null) {
+ return value;
+ }
+ String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, expressionContext);
+ }
+
@Override
protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest)
throws Exception {
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 156ff84bc2f..349d263c4c3 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -46,6 +46,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.config.BeanExpressionContext;
+import org.springframework.beans.factory.config.BeanExpressionResolver;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
@@ -88,6 +93,7 @@ import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestScope;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
@@ -120,18 +126,16 @@ import org.springframework.web.util.WebUtils;
* @see #setWebBindingInitializer
* @see #setSessionAttributeStore
*/
-public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
+public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, BeanFactoryAware {
/**
* Log category to use when no mapped handler is found for a request.
- *
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
- *
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
@@ -156,13 +160,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
private ModelAndViewResolver[] customModelAndViewResolvers;
- private final Map, ServletHandlerMethodResolver> methodResolverCache =
- new ConcurrentHashMap, ServletHandlerMethodResolver>();
-
private HttpMessageConverter>[] messageConverters =
new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
+ private ConfigurableBeanFactory beanFactory;
+
+ private BeanExpressionContext expressionContext;
+
+ private final Map, ServletHandlerMethodResolver> methodResolverCache =
+ new ConcurrentHashMap, ServletHandlerMethodResolver>();
+
public AnnotationMethodHandlerAdapter() {
// no restriction of HTTP methods by default
@@ -318,6 +326,13 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
this.messageConverters = messageConverters;
}
+ public void setBeanFactory(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.beanFactory = (ConfigurableBeanFactory) beanFactory;
+ this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
+ }
+ }
+
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
@@ -595,6 +610,19 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
throw new HttpSessionRequiredException(message);
}
+ @Override
+ protected Object resolveDefaultValue(String value) {
+ if (beanFactory == null) {
+ return value;
+ }
+ String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, expressionContext);
+ }
+
@Override
protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest();
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
index 30e3e0b9583..5dd634523a2 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
@@ -51,11 +51,12 @@ import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
-import org.springframework.beans.BeansException;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.annotation.AnnotationConfigUtils;
@@ -186,7 +187,7 @@ public class ServletAnnotationControllerTests {
}
@Test
- public void defaultParamMissing() throws Exception {
+ public void defaultParameters() throws Exception {
initServlet(DefaultValueParamController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
@@ -195,6 +196,23 @@ public class ServletAnnotationControllerTests {
assertEquals("foo-bar", response.getContentAsString());
}
+ @Test
+ public void defaultExpressionParameters() throws Exception {
+ initServlet(DefaultExpressionValueParamController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myPath.do");
+ request.setContextPath("/myApp");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ System.setProperty("myHeader", "bar");
+ try {
+ servlet.service(request, response);
+ }
+ finally {
+ System.clearProperty("myHeader");
+ }
+ assertEquals("foo-bar-/myApp", response.getContentAsString());
+ }
+
@Test
public void methodNotAllowed() throws Exception {
initServlet(MethodNotAllowedController.class);
@@ -281,13 +299,15 @@ public class ServletAnnotationControllerTests {
doTestAdaptedHandleMethods(MyAdaptedController3.class);
}
- private void initServlet(final Class> controllerclass) throws ServletException {
+ private void initServlet(final Class> controllerClass) throws ServletException {
servlet = new DispatcherServlet() {
@Override
- protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
- throws BeansException {
+ protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
- wac.registerBeanDefinition("controller", new RootBeanDefinition(controllerclass));
+ wac.registerBeanDefinition("controller", new RootBeanDefinition(controllerClass));
+ RootBeanDefinition ppc = new RootBeanDefinition(PropertyPlaceholderConfigurer.class);
+ ppc.getPropertyValues().addPropertyValue("properties", "myKey=foo");
+ wac.registerBeanDefinition("ppc", ppc);
wac.refresh();
return wac;
}
@@ -1615,6 +1635,18 @@ public class ServletAnnotationControllerTests {
}
}
+ @Controller
+ public static class DefaultExpressionValueParamController {
+
+ @RequestMapping("/myPath.do")
+ public void myHandle(@RequestParam(value = "id", defaultValue = "${myKey}") String id,
+ @RequestHeader(defaultValue = "#{systemProperties.myHeader}") String header,
+ @Value("#{request.contextPath}") String contextPath, HttpServletResponse response)
+ throws IOException {
+ response.getWriter().write(String.valueOf(id) + "-" + String.valueOf(header) + "-" + contextPath);
+ }
+ }
+
@Controller
public static class MethodNotAllowedController {
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
index d015d341437..e28a69ae9f3 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Conventions;
import org.springframework.core.GenericTypeResolver;
@@ -68,11 +69,11 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartRequest;
/**
- * Support class for invoking an annotated handler method. Operates on the introspection results of a {@link
- * HandlerMethodResolver} for a specific handler type.
+ * Support class for invoking an annotated handler method. Operates on the introspection results of a
+ * {@link HandlerMethodResolver} for a specific handler type.
*
- * Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and {@link
- * org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
+ *
Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and
+ * {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
@@ -213,6 +214,9 @@ public class HandlerMethodInvoker {
attrName = attr.value();
found++;
}
+ else if (Value.class.isInstance(paramAnn)) {
+ defaultValue = ((Value) paramAnn).value();
+ }
else if ("Valid".equals(paramAnn.annotationType().getSimpleName())) {
validate = true;
}
@@ -228,6 +232,9 @@ public class HandlerMethodInvoker {
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
+ else if (defaultValue != null) {
+ args[i] = resolveDefaultValue(defaultValue);
+ }
else {
Class paramType = methodParam.getParameterType();
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
@@ -399,7 +406,7 @@ public class HandlerMethodInvoker {
}
if (paramValue == null) {
if (StringUtils.hasText(defaultValue)) {
- paramValue = defaultValue;
+ paramValue = resolveDefaultValue(defaultValue);
}
else if (required) {
raiseMissingParameterException(paramName, paramType);
@@ -426,7 +433,7 @@ public class HandlerMethodInvoker {
}
if (headerValue == null) {
if (StringUtils.hasText(defaultValue)) {
- headerValue = defaultValue;
+ headerValue = resolveDefaultValue(defaultValue);
}
else if (required) {
raiseMissingHeaderException(headerName, paramType);
@@ -493,7 +500,7 @@ public class HandlerMethodInvoker {
Object cookieValue = resolveCookieValue(cookieName, paramType, webRequest);
if (cookieValue == null) {
if (StringUtils.hasText(defaultValue)) {
- cookieValue = defaultValue;
+ cookieValue = resolveDefaultValue(defaultValue);
}
else if (required) {
raiseMissingCookieException(cookieName, paramType);
@@ -670,6 +677,10 @@ public class HandlerMethodInvoker {
}
}
+ protected Object resolveDefaultValue(String value) {
+ return value;
+ }
+
protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest)
throws Exception {