【问题标题】:Spring MVC and redirecting (P/R/G )Spring MVC 和重定向(P/R/G)
【发布时间】:2014-02-23 08:08:45
【问题描述】:

我试图在我的 Spring MVC 应用程序中实现 P/R/G (POST/Redirect/GET) 模式以避免重复的表单提交,而不是显示一些成功视图 (GET),我正在重定向到另一个 URL (redirect:/essays/main/student/{studentId}/activity/add/existing),我也需要为其传递 complete 模型。 org.springframework.web.servlet.view.RedirectView 的 Spring 文档说:

“重定向到绝对、上下文相对或当前请求相对 URL 的视图,将所有模型属性公开为 HTTP 查询参数。”

所以我可以从请求中检索序列化的对象,它适用于字符串,但如果我想传递更复杂的对象,它就不起作用,比如在我的例子中,几个对象列表(activityList,@987654325 @, teacherList)。

这就是它的工作原理: 首先我展示了我的searchActivity 视图,这很好用:

@RequestMapping(value="/{studentId}/activity/search", method = RequestMethod.GET)
public String getSearchActivity(@PathVariable Integer studentId, Model model) {

    StudentActivityDTO studentActivityDTO = new StudentActivityDTO();
    Student student = studentService.get(studentId);
    studentActivityDTO.setStudent(student);
    model.addAttribute("studentActivityDTO", studentActivityDTO);

    return "searchActivity";
}

这是我searchActivity 观点的重要部分:

<c:url var="studentUrl" value="/essays/main/student/${studentActivityDTO.student.studentId}/activity/search" />
<form:form modelAttribute="studentActivityDTO" id="myForm" method="POST" action="${studentUrl}">
...
<label for="activityDescription">Eneter search string:</label>
<input type="text" id="activityDescription" name="activityDescription">
<input type="submit" value="Submit" id="submit"/>
...
</form:form>

然后我输入一个搜索字符串(activityDescription)并提交我的表单以进行实际搜索(POST),除了最后一行代码(重定向部分)之外,它也可以正常工作:

@RequestMapping(value="/{studentId}/activity/search", method = RequestMethod.POST)
public String postSearchActivity(@PathVariable Integer studentId,
        @RequestParam(value="activityDescription") String activityDescription,
        @ModelAttribute("studentActivityDTO") StudentActivityDTO studentActivityDTO,
        Model model) {

    List<Activity> activityList = activityService.search(activityDescription);
    model.addAttribute("activityList", activityList);

    Student student = studentService.get(studentId);
    studentActivityDTO.setStudent(student);
    model.addAttribute("studentActivityDTO", studentActivityDTO);
    model.addAttribute("activityDescription", activityDescription);
    model.addAttribute("courseList", courseService.getAll());
    model.addAttribute("teacherList", teacherService.getAll());

    return "redirect:/essays/main/student/{studentId}/activity/add/existing";
    // return "addExistingActivity"; <-- If I use this it works fine!
}

现在我需要将模型传递给一些控制器的 GET 方法:

@RequestMapping(value="/{studentId}/activity/add/existing", method = RequestMethod.GET)
public String getAddExistingActivity(@PathVariable Integer studentId, Model model) {
    // some stuff
    return "addExistingActivity";
}

addExistingActivity视图的重要部分:

<c:url var="studentUrl" value="/essays/main/student/${studentActivityDTO.student.studentId}/activity/add/existing" />
<form:form modelAttribute="studentActivityDTO" id="myForm" method="POST" action="${studentUrl}">
...
<label for="activityId">Activity Id:</label>
<input type="text" id="activityId" name="activityId">
<input type="submit" id="submit" value="Submit"/>

<c:if test="${!empty activityList}">
...
</c:if>
<c:if test="${empty activityList}">
    <div style="color: #ff0000">No results!</div>
</c:if>
...
</form:form>

但我的列表(activityListcourseListteacherList)不存在,而且我总是收到消息“没有结果!”。我只在我的堆栈跟踪中得到这个:

[DEBUG] [http-bio-8080-exec-7 08:32:44] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'studentId' of type [java.lang.Integer] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-7 08:32:44] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'studentActivityDTO' of type [rs.ac.uns.tfzr.zpupin.dto.StudentActivityDTO] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-7 08:32:44] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'org.springframework.validation.BindingResult.studentActivityDTO' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'addExistingActivity'

但如果我使用 return "addExistingActivity" 而不是 redirect:... 一切正常,我的堆栈跟踪中有这个:

[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'studentId' of type [java.lang.Integer] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'studentActivityDTO' of type [rs.ac.uns.tfzr.zpupin.dto.StudentActivityDTO] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'org.springframework.validation.BindingResult.studentActivityDTO' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'activityList' of type [java.util.Collections$CheckedRandomAccessList] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'activityDescription' of type [java.lang.String] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'courseList' of type [java.util.Collections$CheckedRandomAccessList] to request in view with name 'addExistingActivity'
[DEBUG] [http-bio-8080-exec-6 07:42:25] (AbstractView.java:exposeModelAsRequestAttributes:373) Added model object 'teacherList' of type [java.util.Collections$CheckedRandomAccessList] to request in view with name 'addExistingActivity'

所有列表都存在并说明!

我需要什么样的自定义实现才能使其正常工作?任何帮助将不胜感激!

另外,我知道我可以为此使用 flash 属性,但按 F5 后它们不会消失吗?

【问题讨论】:

  • 如果我没记错的话,你必须在 Spring MVC 中添加任何模型属性作为“flash 属性”,以使用重定向在控制器之间传递模型属性。这可能会对您有所帮助:stackoverflow.com/questions/7429649/…
  • @CodeChimp 是的,但是在页面上按 F5 后,flash 属性就没有了,不是吗?我不希望这种情况发生......
  • 如果您要重定向到另一个控制器或应用程序服务器,该控制器或应用程序服务器不与执行重定向的人共享会话数据,那么除了将数据放在“共同”的地方之外真的没有其他方法.例如,您可以修改重定向的接收者以查找某个 ID,将数据写入具有该 ID 的文件共享/数据库,然后重定向传递该 ID。现在另一端知道去哪里找了。不过,这需要能够在两端修改代码。
  • @CodeChimp 我正在重定向到同一个控制器类。我想我可以使用@SessionAttributes,但是我根本不需要flash属性...
  • 是的,如果您的重定向是到同一个控制器,那么@SessionAttributes 是要走的路。我的假设是您正在重定向到一个单独的位置。出于好奇,如果它是同一个控制器,为什么还要使用重定向?

标签: spring-mvc redirect


【解决方案1】:

RedirectView 默认情况下将所有原始模型属性或包含原始模型的集合公开为 HTTP 查询参数。这就是为什么当您添加一个字符串作为模型属性时,它会被公开,而当您添加一个对象时 - 它不是。事实上,这就是当前 RedirectView 文档所说的(Spring 4.0.1):

...默认情况下,所有原始模型属性(或其集合)都作为 HTTP 查询参数公开(假设它们没有被用作 URI 模板变量)...

所以其实下面的方法:

@RequestMapping(value = "post", method = RequestMethod.POST)
public String post(@ModelAttribute PrgForm form, Model model) {
    model.addAttribute("testString", "Some string");
    model.addAttribute("testCollection", Lists.newArrayList("Element 1", "Element 2"));
    model.addAttribute("testObject", form);
    return "redirect:/demo/get";
}

将导致重定向到:demo/get?testString=Some+string&amp;testCollection=Element+1&amp;testCollection=Element+2

如您所见,testObject 不在查询参数中。

如果您仔细查看RedirectView 源代码,您会发现有一个方法isEligibleProperty(String, Object) 可以确定是否应将给定的模型元素作为查询属性公开。

此方法的行为可能会改变。所以实际上,你可以实现自己的RedirectView,如下所示:

private class CustomRedirectView extends RedirectView {

    public CustomRedirectView(String url) {
        super(url);
    }

    @Override
    protected boolean isEligibleProperty(String key, Object value) {
        if ("testObject".equals(key)) {
            return true;
        } else {
            return super.isEligibleProperty(key, value);
        }
    }

    @Override
    protected void appendQueryProperties(StringBuilder targetUrl, Map<String, Object> model, String encodingScheme) throws UnsupportedEncodingException {
        // do some stuff
    }
}

并从@Controller's 方法返回:

@RequestMapping(value = "post", method = RequestMethod.POST)
public View post(@ModelAttribute PrgForm form, Model model) {
    model.addAttribute("testString", "Some string");
    model.addAttribute("testCollection", Lists.newArrayList("Element 1", "Element 2"));
    model.addAttribute("testObject", form);
    return new CustomRedirectView("/demo/get");
}

我以前从未做过这种实现,所以我不确定要完全实现你需要支持的场景有多复杂。不过,我认为使用 flash 属性会更好。

我希望它有所帮助。

【讨论】:

  • 谢谢@RafalBorowiec,我需要考虑一下。只是一个简单的问题:按下 F5 后,flash 属性不会消失吗?
  • 是的,刷新后它们将不可用。
猜你喜欢
  • 2015-05-19
  • 1970-01-01
  • 2014-06-20
  • 2015-06-05
  • 1970-01-01
  • 1970-01-01
  • 2013-03-06
  • 1970-01-01
相关资源
最近更新 更多