Skip to content

April 4, 2011

Add custom annotation to Spring MVC controller

The question of how to add custom annotations to my Spring MVC controllers puzzled me for some time, because the documentation in this area is lacking. Even the bible of Spring 3.0, Spring Recipes by Gary Mak, et. al., did not address the topic.

But then I found this great blog post detailing exactly what I was interested in.  In a nutshell, you need to implement WebArgumentResolver and set your class as a customArgumentResolver of the AnnotationMethodHandlerAdapter bean.  What I was interested in was adding a @RequestAttribute annotation that would work like @RequestParam, but would obviously pull the value from a request attribute rather than a request parameter.

Following the tutorial in the link above, I created this annotation:

package my.package;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAttribute {

    /**
     * The name of the request attribute to bind to.
     */
    String value() default "";

    /**
     * Whether the parameter is required.
     * Default is true, leading to an exception thrown in case
     * of the parameter missing in the request. Switch this to
     * false if you prefer a null in case of the parameter missing.
     * Alternatively, provide a {@link #defaultValue() defaultValue},
     * which implicitly sets this flag to false.
     */
    boolean required() default true;
  
    /**
     * The default value to use as a fallback. Supplying a default value
     * implicitly sets {@link #required()} to false.
     */
    String defaultValue() default "";
}

And the corresponding argument resolver:

package my.package;

import java.lang.annotation.Annotation;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;

public class RequestAttributeArgumentResolver implements WebArgumentResolver {

    public Object resolveArgument(MethodParameter param,
                                  NativeWebRequest request) throws Exception {

        Annotation[] paramAnns = param.getParameterAnnotations();
        
        Class paramType = param.getParameterType();

        for (Annotation paramAnn : paramAnns) {
            if (RequestAttribute.class.isInstance(paramAnn)) {
                RequestAttribute reqAttr = (RequestAttribute) paramAnn;
                HttpServletRequest httprequest = (HttpServletRequest) request.getNativeRequest();
                Object result = httprequest.getAttribute(reqAttr.value());
                
                if (result == null)
                    result = reqAttr.defaultValue();
                
                if (result == null && reqAttr.required())
                    raiseMissingParameterException(reqAttr.value(), paramType);
                else
                    return result;
            }
        }

        return WebArgumentResolver.UNRESOLVED;
    }

    protected void raiseMissingParameterException(String paramName,
                                                  Class paramType) throws Exception {
        throw new IllegalStateException("Missing parameter '" + paramName
                                        + "' of type [" + paramType.getName() + "]");
    }
}

Just wire it up in your Spring configuration:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="customArgumentResolver">
    <bean class="my.package.RequestAttributeArgumentResolver" />
  </property>
</bean>

You may already be doing something with AnnotationMethodHandlerAdapter, such as setting a binding initializer, so in that case just add the customArgumentResolver to your existing config.

Read more from Java

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments