【问题标题】:Strange behavior : Spring Session Attributes vs ModelAttribute奇怪的行为:Spring Session Attributes vs ModelAttribute
【发布时间】:2015-06-29 20:41:19
【问题描述】:

我在 JBoss 7.2 上托管的 Spring MVC 4.0.6 遇到了一个非常奇怪的问题。更新现有用户时,提交的信息有时会传输到 POST RequestMapping 控制器方法(下面的 validateUpdate 方法)。

UserController.java

@Controller
@RequestMapping(value = "/security/user")
@SessionAttributes(value = {"userForm", "user"})
public class UserController {

    private final String CREATE_VIEW = "user/createOrUpdate";
    private final String READ_VIEW = "user/readOrDelete";
    private final String UPDATE_VIEW = CREATE_VIEW;
    private final String DELETE_VIEW = READ_VIEW;    

    @Autowired
    private LocationService locationService;

    @Autowired
    private SecurityService securityService;

    @Autowired
    private UserValidator userValidator;

    @InitBinder("userForm")
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(userValidator);
        binder.setDisallowedFields("strId");
    }

    @ModelAttribute("regions")
    public List<Region> populateRegions() {

        Locale locale = LocaleContextHolder.getLocale();
        List<Region> regions = this.locationService.lstRegions(locale.getLanguage());

        return regions;
    }

    @RequestMapping(value = "/update/{intUserId}", method = RequestMethod.GET)
    public String updateUser(@PathVariable Integer intUserId, Model model) {

        User user = this.securityService.findUserByPk(intUserId);

        if (user != null) {
            UserForm userForm = new UserForm();
            userForm.setUser(user);

            model.addAttribute(userForm);
        }

        return UPDATE_VIEW;
    }

    @RequestMapping(value = "/update/validate",  method = RequestMethod.POST)
    public String validateUpdate(@Valid @ModelAttribute("userForm") UserForm userForm,
                                BindingResult result,
                                Model model,
                                RedirectAttributes redirectAttributes,
                                SessionStatus status) {

        return this.performCreateOrUpdateOperation(userForm, result, model, redirectAttributes, status);
    }

    private String performCreateOrUpdateOperation(
        UserForm userForm, 
        BindingResult result,
        Model model,
        SessionStatus status) {

        if(result.hasErrors()) {
            return UPDATE_VIEW;
        } else {

            User user = userForm.getUser();

            this.securityService.validateCreateOrUpdateUser(result, user);

            if (result.hasErrors() == false) {

                if (userForm.isNew()) {
                    this.securityService.addUser(user);
                } else {
                    this.securityService.updateUser(user);
                }

                model.addAttribute(user);

                status.setComplete();

                return "user/success";

            } else {
                return UPDATE_VIEW;
            }
        }
    }
}

表单豆

public class UserForm {

    private String strUserIdToSearch = "";

    private String strId = "0";
    private String strUserId = "";
    private String strFirstName = "";
    private String strLastName = "";
    private String strEmail = "";

    private String strRegionId = "0";
    private boolean booArchived = false;

    public User getUser() {

        User user = new User();

        user.setIntId(Integer.valueOf(this.strId));
        user.setStrUserId(this.strUserId);
        user.setStrFirstName(this.strFirstName);
        user.setStrLastName(this.strLastName);
        user.setStrEmail(this.strEmail);

        Region region = new Region(Integer.valueOf(this.strRegionId));
        user.setRegion(region);

        user.setBooArchived(this.booArchived);

        return user;
    }

    public void setUser(User user) {

        this.strUserIdToSearch = user.getStrUserId();

        this.strId = String.valueOf(user.getIntId());
        this.strUserId = user.getStrUserId();
        this.strFirstName = user.getStrFirstName();
        this.strLastName = user.getStrLastName();
        this.strEmail = user.getStrEmail();

        this.strRegionId = String.valueOf(user.getRegion().getIntId());
        this.booArchived = user.getBooArchived();
    }

    ...getters and setters...
}

JSP(为清晰起见删除了样式) crudMethod 是一个 JSP 标记,根据 ${requestScope['javax.servlet.forward.request_uri']}

返回“create”、“read”、“update”或“delete”
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

    <spring:url var="userFormAction" value="/rest/security/user/${crudMethod}/validate" />    

<form:form id="userForm" modelAttribute="userForm" action="${userFormAction}" method="POST">

<form:hidden path="strId" />
<form:hidden path="strUserId" id="strUserId" />
<form:hidden path="strLastName" id="strLastName" />
<form:hidden path="strFirstName" id="strFirstName" />
<form:hidden path="strEmail" id="strEmail" />

<form:select id="strRegionId" path="strRegionId">
    <form:option value="0" label="${strSelectRegionLabel}" />
    <form:options items="${regions}" itemValue="intId" itemLabel="${strRegionLabel}" />
</form:select>

</form:form>

因此,当我提交表单时,例如,将区域更改为列表中的另一个 ID(比如说从 1 到 6)。有时有效,有时无效。通过“作品”,我的意思是我点击了成功页面,然后我再次返回查看用户。有时它保持在 1,有时它变为 6。

我找到了一种模式/解决方法,可以一直重现该问题:

  1. 加载更新表单(UserController > updateUser)
  2. 将区域从 1 更改为 6
  3. 单击保存。表单提交,因此正在调用 UserController.validateUser 方法。
  4. 获得成功页面。在那个成功页面上,我得到了一个指向用户读取操作的链接。单击该链接,我意识到该区域没有改变(主要问题)。阅读页面上有更新用户的链接。
  5. 重新执行与第 2 步完全相同的更改。
  6. 单击保存。表单提交,我得到了成功的页面视图。
  7. 再次单击读取超链接,现在我看到更改有效。

问题是:为什么?我错过了什么吗??

注意:与业务层无关。我已经测试过了,它很稳定。这肯定与我使用 Spring MVC 框架有关。

浏览器是IE11。

感谢您的帮助。

* 2015-06-29 更新 *

又搜索了几遍,我发现:

  • 不工作时,请求Content-Length头值为0。
  • 当它工作时,有一个值(例如:146)。
  • 请求消息体总是正确的,像这样:

    strId=THE_ID&strUserId=THE_USERID&strLastName=THE_LASTNAME&strFirstName=THE_FIRSTNAME&strEmail=THE_EMAILADDRESS&strRegionId=THE_REGIONID&booArchived=false

    请注意,“THE_REGIONID”每次都很好。

相关资源

【问题讨论】:

  • 它与您的浏览器有关,与缓存有关。我强烈建议使用 post-redirect-get 模式。更新后重定向到成功页面而不是渲染它。所以不要返回user/success,而是返回redirect:/url/to/success.jsp-page。成功页面的控制器应该再次从数据库中检索用户(而不是将其添加到模型中),如果您真的不想将用户置于 flash 范围内,那么它将在重定向中存活。
  • 我先使用了flash属性,但是如果用户出于某种原因刷新成功页面,内容正在被清除,所以这不是一个选项。将尝试实现 post-redirect-get 模式,然后按照您的建议从数据库中重新加载元素。谢谢。
  • 最后,我怀疑它与缓存有关。我在方法“validateUpdate”中添加了一个断点,以便立即查看提交的值。即使在这一点上,该地区的价值也保持不变。如果它工作但没有正确显示,它会坚持你的缓存想法......但它甚至没有被正确提交......
  • 如果它没有更新,您的绑定或 UserForm 中的映射一定有问题(为此我建议使用映射框架而不是手工劳动或直接使用用户)。
  • 能否详细介绍一下“映射框架”?谢谢你的帮助。我已经查看了整个日志,我没有看到 Spring 有任何看起来异常的东西。我的印象是它与过滤器有关,甚至可能是最糟糕的......我的服务器(预身份验证)......但话又说回来,我在日志中没有看到任何关于它的痕迹......服务器在调试模式。

标签: spring spring-mvc httpsession


【解决方案1】:
<form:select id="intRegionId" path="strRegionId">

我认为 form:select 中的 id 是罪魁祸首。它与 UserForm 中的属性名称“strRegionId”不匹配。因此它不绑定。将 id 值更改为 strRegionId

【讨论】:

  • 刚刚测试过,不幸的是同样的故障存在。
  • 您是否只看到 regionId 或所有参数的问题?
  • 所有参数。模型属性被完全清除。这是由仅 IE 的身份验证问题(或他们所说的功能......!)引起的。见这里:blogs.msdn.com/b/ieinternals/archive/2010/11/22/…
猜你喜欢
  • 2014-04-23
  • 1970-01-01
  • 2012-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-20
  • 2012-05-31
  • 1970-01-01
相关资源
最近更新 更多