Karthik's Weblog

Taking Spring MVC @Controller method injection a step further

Posted in Java, Spring by karthik on February 3, 2010

Spring MVC @Controllers typically allow you to annotate your controller method parameters making it http agnostic and increasing the testability of the controllers at the same time. But sometimes you are required to repeat the parameter signature as shown below –

@Controller
class AccountController{
@RequestMapping("/create")
public String create(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){
}
@RequestMapping("/update")
public String update(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

}
}

Controller methods ‘create’ & ‘update’ access the same set of servlet environment specific parameters.
I was wondering if it would help if we could move all those parameters to an annotated POJO as shown
below and reuse the POJO in the controller methods –

Note that the Spring MVC @Controller annotations that let you access request, cookie, header values
can be specified at the method parameter level only. I obviously wanted to reuse these annotations and found that to be a limitation only to realize later that I could make use of constructor injection style

public class MyEnv{
private final String smUserdn;
private final String smSession;
private final String userId;
@Autowired
public MyEnv(@RequestHeader(value="SM_USERDN") String smuserdn;
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

this.smuserdn = smuserdn;
this.smSession = smSession;
this.userid = userId;
}

}
@Controller
class AccountController{
@RequestMapping("/create")
public String create(@Composite MyEnv env){

}
@RequestMapping("/update")
public String update(@Composite MyEnv env){
}
}

You need the @Composite custom annotation to trigger this binding.
Do you guys see a need for something like this? I did implement this feature. But wanted to check if people find this useful at all.
thanks!

Advertisements
Tagged with: , ,

My love hate relationship with Spring MVC’s AnnotationMethodHandlerAdapter.

Posted in Spring by karthik on December 16, 2009

I like the Spring 3.0 annotation based controllers. I never understood how Spring “MVC” managed to sustain its popularity all this while with a programming model based on “controller inheritance” especially when you had competing frameworks like Struts2 and Stripes that supported a pure POJO controller model from day one.
So it was heartening to know that Spring 3.0 will deprecate the Inheritance based controllers going forward.

The class that does the majority of the work in Spring @MVC is AnnotationMethodHandlerAdapter (with the help of a few others).

One of the nice features supported by Spring MVC is the ability to selectively turn-off automatic data binding from request parameters to model objects. You usually specify the fields that you don’t want bound in the @InitBinder method.

In this example, field Foo.id is auto-generated and say I don’t want to auto-bind the field “id”, you usually specify that information in your @InitBinder method in the @Controller.

class Foo{
private String name;
private int id;
private String
//getter-s
//setter-s
}

While this is enough to prevent auto-binding,
you might still want to signal this security breach as an error in your controller method.

@Controller
class FooBarController{
@InitBinder
public void myInitBinder(WebDataBinder binder){
//do not bind the field id
binder.setDisallowedFields(new String[]{"id"});
}
@RequestMapping("/foo")
void foo(Foo foo, BindResult bindResult){
String[] suppressedFields = result.getSuppressedFields(); #1
if (suppressedFields.length > 0) { #2
throw new IllegalBindOperationException( #3
"You've attempted to bind fields that aren't allowed by @InitBinder: "
+ StringUtils.arrayToCommaDelimitedString(suppressedFields)); #4
}
//do something useful }
@RequestMapping("/bar")
void bar(Foo foo, BindResult bindResult){
String[] suppressedFields = result.getSuppressedFields(); #1
if (suppressedFields.length > 0) { #2
throw new IllegalBindOperationException( #3
"You've attempted to bind fields that aren't allowed by @InitBinder: "
+ StringUtils.arrayToCommaDelimitedString(suppressedFields)); #4
}
//do something useful }
}

You obviously don’t want to be writing this piece of logic (lines #1 through #4) in every handler method.
May be you can move this check to a utility API and call the utility API instead. But then Spring encourages us to model such concerns as an interceptor.

But how do I access the BindResult object in the interceptor unless the handler method declares it to be a part of its signature? While I’m not a fan of AspectJ, do let me know if there is a way.

Another option could be to achieve this by subclassing AnnotationMethodHandlerAdapter and overriding some relevant methods? But which one? – I couldn’t find any – let me know if you did.

Ok how about HandlerMethodInvoker? anybody?

Finally I realized that while AnnotationMethodHandlerAdapter & HandlerMethodInvoker combine to give us this excellent @MVC programming model, these classes like to keep everything to themselves – pretty much every interesting method /inner class in some cases is private.

Suffice to say that it’s been quite frustrating.

I was hoping for a “redesigned” AnnotationMethodHandlerAdapter class for Spring 3.0. But that hasn’t happened either.
Let me point out at the same time that AnnotationMethodHandlerAdapter is customizable but I feel it could be much better.
I love it but I hate it too.

@HandlerInterceptor for Spring MVC

Posted in Java, Spring by karthik on October 12, 2009

I have been doing quite a bit of Spring MVC at work these days. I like the @Controller model and with the bunch of
enhancements that have gone into Spring 3.0, Spring MVC has the potential to become the defacto Action based framework.

While Spring MVC has excellent support for Controller Annotations to cut down on amount of the XML
configuration, you are still expected to configure HandlerInterceptor -s in the Spring configuration file.
Sometimes it helps to keep all the related code together in one artifact.

So let me quickly summarize what I did –

I defined a custom annotation @Interceptors that lets you specify the interceptors that you would like to apply on
the controller.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Interceptors {
      /** HandlerInterceptor**/  
     Class[] value();
}

The custom @Interceptors annotation is exactly same as the EJB 3.0 Interceptors annotation definition. I kind of liked the fact that I could navigate to the interceptor implementation class using the IDE.

Next you define a couple of interceptors like so –

class MyInterceptor1 implements HandlerInterceptor{
        //..
} 

class MyInterceptor2 extends HandlerInterceptorAdapter{
        //..
}

and annotate the @Controller-s –

@Controller
@Interceptors({MyInterceptor1.class,MyInterceptor2.class})    
class TestController{

      @RequestMapping("/test")
      void test(ModelMap mm){

      }
}

Next let’s look at the implementation –

Spring’s HandlerMapping is responsible for resolving the HandlerExectionChain that maps to a given request URL. The HandlerExecutionChain encapuslates the Controller and the associated interceptors for a given request URL.

public interface HandlerMapping {      
      HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;    
}

 

Spring’s DefaultAnnotationHandlerMapping class maps handlers/@Controllers based on HTTP paths expressed through the RequestMapping annotation at the type or method level.

So we will enhance DefaultAnnotationHandlerMapping and add the ability to detect @Interceptors on the controllers

   
   package com.springmvc.extensions;

   import java.util.ArrayList;
   import java.util.List;
   import javax.servlet.http.HttpServletRequest;
   import org.springframework.beans.BeanUtils;
   import org.springframework.core.annotation.AnnotationUtils;
   import org.springframework.web.servlet.HandlerExecutionChain;
   import org.springframework.web.servlet.HandlerInterceptor;
   import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;

   public class HandlerInterceptorAnnotationAwareHandlerMapping extends  DefaultAnnotationHandlerMapping {

    @Override
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler,HttpServletRequest request){ #1
        HandlerExecutionChain chain = super.getHandlerExecutionChain(handler,request);  #2
        HandlerInterceptor[] interceptors = detectInterceptors(chain.getHandler().getClass());
        chain.addInterceptors(interceptors);
        return chain;
    }

    protected HandlerInterceptor[] detectInterceptors(Class handlerClass) {

        Interceptors interceptorAnnot = AnnotationUtils.findAnnotation(handlerClass, Interceptors.class);    #3
        List interceptors = new ArrayList();
        if (interceptorAnnot != null) {
          Class[] interceptorClasses = interceptorAnnot.value();
          if (interceptorClasses != null) {
            for (Class interceptorClass : interceptorClasses) {     #4   
              if (!HandlerInterceptor.class.isAssignableFrom(interceptorClass)) {
                raiseIllegalInterceptorValue(handlerClass,interceptorClass);      #5 
              }
              interceptors.add((HandlerInterceptor) BeanUtils.instantiateClass(interceptorClass));   #6 
            }
          }
        }
        return interceptors.toArray(new HandlerInterceptor[0]);
    }


    protected void raiseIllegalInterceptorValue(Class handlerClass,Class interceptorClass) {          
        throw new IllegalArgumentException(interceptorClass + " specified on "
            + handlerClass + " does not implement " + HandlerInterceptor.class.getName());
            
    }

 }

 

#1 Override the getHandlerExecutionChain() method defined in DefaultAnnotationHandlerMapping
#2 Call ‘super()’ so that you don’t change the base class behavior . This way you can aggregate the interceptors configured
in the Spring context file against the property ‘interceptors’ as well (it at all configured) .
#3 Look for @Interceptors annotation for a given Controller class.
#4 #5 Run through all the specified HandlerInterceptor implementations , making sure that they indeed implement the Spring’s
HandlerInterceptor interface.
#6 Instantiate the HandlerInterceptor instance (This will look for a default no-arg contructor). You could look up the
Spring ApplicationContext and check if an Interceptor has been configured as well.

As a final step, you need to declare the HandlerInterceptorAnnotationAwareHandlerMapping class in the Spring Application Context

  <bean class="com.springmvc.extensions.HandlerInterceptorAnnotationAwareHandlerMapping "/>

 

Let me know what you think.

Update: Nice to see keith link to this blog on spring jira

Tagged with: , ,