【问题标题】:Spring MVC model keeps attribute from previous request for same mappingSpring MVC 模型保留先前请求相同映射的属性
【发布时间】:2020-12-11 16:00:45
【问题描述】:

当我测试控制器时,我发现以下代码可以正常工作。 但我不明白为什么模型在后期映射返回后包含“passProperty”属性。 PassProperty 类仅使用验证约束进行注释。 在控制器中,它仅在方法中使用 @Valid 注释进行注释,如下面的代码所示。

我在控制器中有以下映射:

获取:

@GetMapping("/accounts/change-password")
public ModelAndView changePass(Principal principal) {
    ModelAndView mv = new ModelAndView("change-password");
    mv.addObject("passProperties", new PassProperties());

    return mv;
}

帖子:

@PostMapping("/accounts/change-password")
public ModelAndView changePass(@Valid PassProperties passProperties, BindingResult bindingResult, Principal principal) {
    ModelAndView mv = new ModelAndView("change-password");

    if (bindingResult.hasErrors() == false) {
        User user = userService.findUser(principal.getName());
        String oldPass = passProperties.getOldPass();

        if (encoder.matches(oldPass, user.getPassword())) {
            user.setPassword(encoder.encode(passProperties.getNewPass1()));
            userService.saveUser(user);
            mv.addObject("msg", "Hasło zostało zmienione");
        } else {
            mv.addObject("msg", "Stare hasło nie pasuje.");
        }

    }

    return mv;
}

编辑 1

我正在添加以下测试和输出。 如您所见,返回的 ModelAndView 包含未在 @PostMapping 方法中添加的 passProperties。 passProperties 对象仅使用@Valid 注释,并且在PassProperties 类字段中使用约束@NotNull、@Length 和@NotBlank 注释。它没有在任何地方使用@ModelAttribute 之类的东西进行注释。 测试:

@Test
void changePassPost_whenPassPropertiesHasNoErrors_returnsModelWithPassProperties_And_ChangesPassword() throws Exception {
    User principal = new User ("user", "user", true, "LVL99");

    PassProperties passProperties = new PassProperties();
    passProperties.setOldPass("user");
    passProperties.setNewPass1("zaq1");
    passProperties.setNewPass2("zaq1");

    when(userService.findUser("user")).thenReturn(principal);
    when(encoder.encode(passProperties.getNewPass1())).thenReturn(passProperties.getNewPass1());
    when(encoder.matches(anyString(),anyString())).thenReturn(true);

    ModelAndView mv = mockMvc.perform(MockMvcRequestBuilders.post("/accounts/change-password")
                .with(SecurityMockMvcRequestPostProcessors.csrf())
                .with(SecurityMockMvcRequestPostProcessors.user(new UserDetailsImpl(principal)))
                .param("oldPass", passProperties.getOldPass())
                .param("newPass1", passProperties.getNewPass1())
                .param("newPass2", passProperties.getNewPass2())
            )
            .andDo(MockMvcResultHandlers.print())
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.model().attributeExists("passProperties"))
            .andExpect(MockMvcResultMatchers.model().hasNoErrors())
            .andExpect(MockMvcResultMatchers.view().name("change-password"))
            .andReturn().getModelAndView();

    System.out.println("-------------------------------");
    System.out.println(mv.getModel().get("passProperties"));
    System.out.println(mv.getModel().get("msg"));
    System.out.println("-------------------------------");

    ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
    verify(encoder, times(1)).encode(passProperties.getNewPass1());
    verify(userService, times(1)).saveUser(userCaptor.capture());
    assertEquals(passProperties.getNewPass1(), userCaptor.getValue().getPassword());
    assertEquals(passProperties.getNewPass1(), principal.getPassword());

}

输出:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /accounts/change-password
       Parameters = {oldPass=[user], newPass1=[zaq1], newPass2=[zaq1], _csrf=[8fc305ee-f0eb-42a6-8588-cce3ffdba469]}
          Headers = []
             Body = null
    Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@a37a47df: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@a37a47df: Principal: com.example.JabaVeans.service.UserDetailsImpl@58e46572; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: LVL99}

Handler:
             Type = com.example.JabaVeans.controller.AccountsController
           Method = com.example.JabaVeans.controller.AccountsController#changePass(PassProperties, BindingResult, Principal)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = change-password
             View = null
        Attribute = passProperties
            value = PassProperties(oldPass=user, newPass1=zaq1, newPass2=zaq1)
           errors = []
        Attribute = msg
            value = Hasło zostało zmienione

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>

    Forwarded URL = null
   Redirected URL = null
          Cookies = []
-------------------------------
PassProperties(oldPass=user, newPass1=zaq1, newPass2=zaq1)
Hasło zostało zmienione
-------------------------------

2020-08-22 18:01:49.655  INFO 12964 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-08-22 18:01:49.665  INFO 12964 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 0

编辑 2

我知道我可以在 @PostMapping 中用新的 PassProperties 覆盖“passProperties”以擦除以前的值,但我真的很困惑为什么会这样。

【问题讨论】:

    标签: java spring spring-boot spring-mvc


    【解决方案1】:

    如果你检查你的 PassProperties 类,也许它有一些验证注释,比如@NotNull、@Min、@Max、@Email、@Size 等。 使用 Controller 中的 @Valid 注解,Spring 将根据您在类中定义的验证注解检查 PassProperties 对象是否有效。如果您的 PassProperties 对象的某些字段无效,则该方法将不会启动,并且 spring 基于该字段返回错误。

    我相信 PassProperties 有一些与密码相关的属性,比如 oldPass、newPass。根据您的代码,@GetMapping 中的 changePass 方法只是返回类 PassProperties 的新对象实例。 changePass 的 @PostMapping 正在接收可能由某个用户发送的 PassProperties 对象,在这种情况下,就像我在上面解释的那样,只有在所有验证都为真时才有效(因为 @Valid 注释)。

    【讨论】:

    • 这不是问题。 changePass 的@PostMapping 成功完成处理并返回ModelAndView mv 后,模型仍然包含与之前传递给方法的值相同的passProperties 属性。
    • 此外,在返回之前在方法末尾调用 mv.clear() 之后,passProperties 仍然存在于模型中,并且可以通过例如 th:text 访问/显示
    • 好的,现在我明白了你的问题,我会编辑我的答案
    猜你喜欢
    • 1970-01-01
    • 2013-05-08
    • 2013-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-08
    相关资源
    最近更新 更多