How do I prevent Spring 3.0 MVC @ModelAttribute variables from appearing in URL?

Viewed 50116

Using Spring MVC 3.0.0.RELEASE, I have the following Controller:

@Controller
@RequestMapping("/addIntake.htm")
public class AddIntakeController{

  private final Collection<String> users;

  public AddIntakeController(){
    users = new ArrayList<String>();
    users.add("user1");
    users.add("user2");
    // ...
    users.add("userN");
  }

  @ModelAttribute("users")
  public Collection<String> getUsers(){
    return this.users;
  }

  @RequestMapping(method=RequestMethod.GET)
  public String setupForm(ModelMap model){

    // Set up command object
    Intake intake = new Intake();
    intake.setIntakeDate(new Date());
    model.addAttribute("intake", intake);

    return "addIntake";
  }

  @RequestMapping(method=RequestMethod.POST)
  public String addIntake(@ModelAttribute("intake")Intake intake, BindingResult result){

    // Validate Intake command object and persist to database
    // ...

    String caseNumber = assignIntakeACaseNumber();

    return "redirect:intakeDetails.htm?caseNumber=" + caseNumber;

  }

}

The Controller reads Intake information from a command object populated from an HTML form, validates the command object, persists the information to the database, and returns a case number.

Everything works great, except for when I redirect to the intakeDetails.htm page, I get a URL that looks like this:

http://localhost:8080/project/intakeDetails.htm?caseNumber=1&users=user1&users=user2&users=user3&users=user4...

How do I prevent the user Collection from showing up in the URL?

13 Answers

There are no good ways to solve this problem (i.e. without creating custom components, without excessive amounts of explicit xml configuration and without manual instantiation of RedirectView).

You can either instantiate RedirectView manually via its 4-argument constructor, or declare the following bean in your context (near other view resolvers):

public class RedirectViewResolver implements ViewResolver, Ordered {
    // Have a highest priority by default
    private int order = Integer.MIN_VALUE; 

    // Uses this prefix to avoid interference with the default behaviour
    public static final String REDIRECT_URL_PREFIX = "redirectWithoutModel:";     

    public View resolveViewName(String viewName, Locale arg1) throws Exception {
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            return new RedirectView(redirectUrl, true, true, false);
        }
        return null;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

The @ModelAttribute method annotation is intended to be used for exposing reference data to the view layer. I can't say for sure in your case, but I wouldn't say that a collection of users qualified as reference data. I suggest that you pass this information through to the model explicitly in your @RequestMapping-annotated handler methods.

If you still want to use @ModelAttribute, there's a blog entry here that discusses the redirect problem.

But all the previous examples have a common issue, as all @ModelAttribute methods are run before the handler is executed, if the handler returns a redirect the model data will be added to the url as a query string. This should be avoided at all costs as it could expose some secrets on how you have put together your application.

His suggested solution (see part 4 of the blog) is to use a HandlerInterceptorAdapter to make the common reference data visible to the view. Since reference data shouldn't be tightly coupled to the controllers, this shouldn't pose a problem, design-wise.

I know this question and answer is old, but I stumbled upon it after having similar issues myself and there isn't a lot of other info out there that I could find.

I think the accepted answer is not a very good one. The answer right below it by axtavt is much better. The question is not whether annotating model attributes on a controller makes sense. It's about how to issue a "clean" redirect from within a controller that does normally use ModelAttributes. The controller itself normally requires the reference data, but sometimes it needs to redirect somewhere else for exceptional conditions or whatever, and passing the reference data doesn't make sense. I think this a valid and common pattern.

(Fwiw, I ran in to this problem unexpectedly with Tomcat. Redirects were simply not working and I was getting odd error messages like: java.lang.ArrayIndexOutOfBoundsException: 8192. I eventually determined that Tomcat's default max header length is 8192. I didn't realize the ModelAttributes were being added automatically to the redirect URL, and that was causing the header length to exceed Tomcat's max header length.)

I implemented a variant of Sid's answer with less copying and pasting involved:

public class RedirectsNotExposingModelUrlBasedViewResolver extends UrlBasedViewResolver {

    @Override
    protected View createView(String viewName, Locale locale) throws Exception {
        View view = super.createView(viewName, locale);
        if (view instanceof RedirectView) {
            ((RedirectView) view).setExposeModelAttributes(false);
        }
        return view;
    }

}

This also requires a view resolver bean to be defined:

<bean id="viewResolver" class="com.example.RedirectsNotExposingModelUrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>

Don't use @ModelAttribute. Store the users in the ModelMap explicitly. You're doing as much with the command object anyway.

@RequestMapping(method=RequestMethod.GET)
    public String setupForm(ModelMap model){

        // Set up command object
        Intake intake = new Intake();
        intake.setIntakeDate(new Date());
        model.addAttribute("intake", intake);

        model.addAttribute("users", users);

        return "addIntake";
    }

The disadvantage to this is if a validation error takes place in addIntake(). If you want to simply return the logical name of the form, you must also remember to repopulate the model with the users, otherwise the form won't be setup correctly.

Related