@RequestParam and co support placeholders and expressions in their defaultValue attributes (SPR-5922); @Value expressions supported as MVC handler method arguments as well (against request scope)

This commit is contained in:
Juergen Hoeller 2009-09-14 10:48:15 +00:00
parent 7835e66abb
commit bb70c9a4c4
5 changed files with 127 additions and 24 deletions

View File

@ -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.
*
* <p>This is typically used for assigning default field values
* with "#{systemProperties.myProp}" style expressions.
* <p>Typically used for expression-driven dependency injection. Also supported
* for dynamic resolution of handler method parameters, e.g. in Spring MVC.
*
* <p>A common use case is to assign default field values using
* "#{systemProperties.myProp}" style expressions.
*
* @author Juergen Hoeller
* @since 3.0

View File

@ -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<Class<?>, PortletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, 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 {

View File

@ -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<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
private ConfigurableBeanFactory beanFactory;
private BeanExpressionContext expressionContext;
private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, 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();

View File

@ -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 {

View File

@ -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.
*
* <p>Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and {@link
* org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
* <p>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 {