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 {