2020-7-21 스프링 MVC(8) 핸들러메서드(2) - SessionAttirubte, RedirectAttributes, FlashAttribute

SessionAttributes와 SessionAttribute

  • @SessionAttirubtes

    • 모델 정보를 HTTP 세션에 저장하는 어노테이션.
  • HttpSession을 직정사용할수도 있지만, 어노테이션에 설정한 이름에 해당하는 모델 정보를 자동으로 세션에 넣어준다.

    • @GetMapping("events/form")
      public String eventForm(Model model, HttpSession httpSession) {
        Event newEvent = new Event();
        newEvent.setLimit(50);
        model.addAttribute("event", newEvent);
        httpSession.setAttribute("event", newEvent);
        return "/events/form";
      }
      
      @Test
      public void eventForm() throws Exception {
        mockMvc.perform(get("/events/form"))
          .andDo(print())
          .andExpect(view().name("/events/form"))
          .andExpect(model().attributeExists("event"))
          .andExpect(request().sessionAttribute("event", notNullValue())); // 세션에 특정값이 있는지 없는지 확인
      }
      
  • @ModelAttribute는 세션에 있는 데이터도 바인딩된다.

  • 여러화면에서 사용해야할 객체를 공유할때 사용

    • @Controller
      @SessionAttributes("event") // 이름에 해당하는 모델을 세션에 넣어주는거다.
      public class SampleController {
      
        @GetMapping("events/form")
        public String eventForm(Model model) {
          Event newEvent = new Event();
          newEvent.setLimit(50);
          model.addAttribute("event", newEvent);
          return "/events/form";
        }
      }
      

SessionAttirubte

  • HTTP세션에 들어있는 값을 참조할 때 사용한다.

    • HttpSession을 사용할 때에 비해 타입 컨버전을 자동으로 지원한다.
    • HTTP 세션에 데이터를 넣고 빼고 싶은 경우 HttpSession을 사용해라
  • @SessionAttributes와는 많이 다르다

    • @SessionAttributes는 해당 컨트롤러 내에서만 동작 => 해당 컨트롤러 안에서 다루는 특정 모델 객체를 세션에 넣고 공유할 때 사용

    • @SessionAttribute는 컨트롤러 밖(인터셉터, 필터 등)에서 만들어 준 세션 데이터에 접근할때 사용

    • @Configuration
      public class WebConfig implements WebMvcConfigurer {
         ...
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(new VisitTimeInterceptor());
          }
      }
      
      public class VisitTimeInterceptor implements HandlerInterceptor {
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              HttpSession session = request.getSession();
              if (session.getAttribute("visitTime") == null)
                  session.setAttribute("visitTime", LocalDateTime.now());
              return true;
          }
      }
      
      
      @Controller
      @SessionAttributes("event")
      public class SampleController {
      	...
          @GetMapping("/events/list")
          public String getEvents(Model model, @SessionAttribute LocalDateTime visitTime) {
              System.out.println("==== visitTime : " + visitTime);
              ...
              return "/events/list";
          }
      }
      

    -

@RedirectAttributes

  • 리다이렉트 할때 기본적으로 Primitive type데이터는 URI 쿼리 매개변수에 추가된다

    • 스프링부트에서 이 기능이 기본적으로 비활성화

    • Ignore-default-model-on-redirect프로퍼티를 사용하여 활성화 할 수도 있다.

    • @PostMapping("/events/form/limit")
      public String eventFormLimitSubmit(@Validated @ModelAttribute Event event, BindingResult bindingResult, SessionStatus sessionStatus, Model model) {
        if (bindingResult.hasErrors())
          return "/events/form-limit";
        sessionStatus.setComplete(); // session을 비우기
        model.addAttribute("name", event.getName());
        model.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
      }
      //result
      http://localhost:8080/events/list?name=redirect&limit=123
      => redirect url에 name, limit가 붙었다.
      => spring.mvc.ignore-default-model-on-redirect=false 설정되있었다.
      
        @PostMapping("/events/form/limit")
        public String eventFormLimitSubmit(@Validated @ModelAttribute Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors())
          return "/events/form-limit";
        sessionStatus.setComplete(); // session을 비우기
        redirectAttributes.addAttribute("name", event.getName());
        redirectAttributes.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
      }
      spring.mvc.ignore-default-model-on-redirect설정을 지우고, redirectAttributes를 이용하면 원하는 값만 전달할  있다.
      

    -

  • 원하는 값만 리다이렉트할때 전달하고 싶다면 RedirectAttributes에 명시적으로 추가할 수 있다.

  • 리다이렉트 요청을 처리하는 곳에서 쿼리 매개변수를 @RequestParam, @ModelAttribute로 받을 수 있다.

Flash Attributes

주로 리다이렉트시에 데이터를 전달할때 사용한다

  • 데이터가 URI에 노출되지 않는다
  • 임의의 객체를 저장할 수 있다
  • 보통 HTTP 세션을 사용한다.

리다이렉트 하기전에 데이터를 HTTP 세션에 저장하고 리다이렉트 요청을 처리한 다음 그 즉시 제거한다.

@PostMapping("/events/form/limit")
public String eventFormLimitSubmit(@Validated @ModelAttribute Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes redirectAttributes) {
  if (bindingResult.hasErrors())
    return "/events/form-limit";
  sessionStatus.setComplete(); // session을 비우기
  // 불편한점
  redirectAttributes.addAttribute("name", event.getName());
  redirectAttributes.addAttribute("limit", event.getLimit());
  //해결책
  redirectAttributes.addFlashAttribute("newEvent", event); // HTTP세션에 들어간다.
  return "redirect:/events/list";
}

@Test
public void flashAttributesTest() throws Exception {
  Event newEvent = new Event();
  newEvent.setLimit(1000);
  newEvent.setName("Test event");
  mockMvc.perform(get("/events/list")
                  .sessionAttr("visitTime", LocalDateTime.now())
                  .flashAttr("newEvent", newEvent))
    .andDo(print())
    .andExpect(status().isOk());
}
  • redirectAttribute에서 addAttribute는 쿼리파라미터를 uri에 붙여야되서 문자열로 변환이 가능해야한다. 객체를 전달하지 못한다.
  • addFlashAttribute를 사용하면 객체를 전달할 수 있다. 세션에 들어가고 리다이렉트된 요청이 처리되면 세션에서 제거된다.

MultiPartFile

  • 파일 업로드시 사용하는 메서드 아규먼트

  • MultipartResolver빈이 설정 되어 있어야 사용할 수 있다.(스프링부트는 자동으로 설정해준다)

  • POST multipart/form-data 요청에 들어있는 파일을 참조할 수 있다.

  • List<MultipartFile>아규먼트로 여러 파일을 참조할 수 있다.

    • <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      
      <div th:if="${message}">
          <h2 th:text="${message}"></h2>
      </div>
      <form method="POST" enctype="multipart/form-data" action="#" th:action="@{/file}">
          File: <input type="file" name="file"/>
          <input type="submit" value="Upload"/>
      </form>
      </body>
      </html>
      
    • @Controller
      public class FileController {
      
        @GetMapping("file")
        public String fileUploadForm(Model model) {
          return "files/index";
        }
      
        @PostMapping("file")
        public String fileUpload(@RequestParam MultipartFile file, RedirectAttributes attributes) {
          // save
          String message = file.getOriginalFilename() + " is uploaded";
          attributes.addFlashAttribute("message", message);
          return "redirect:/file";
        }
      }
      
      @RunWith(SpringRunner.class)
      @SpringBootTest
      @AutoConfigureMockMvc
      public class FileControllerTest {
      
        @Autowired
        private MockMvc mockMvc;
      
        @Test
        public void fileUploadTest() throws Exception {
          MockMultipartFile file = new MockMultipartFile("file", "text.txt", "text/plain", "hello file".getBytes());
          this.mockMvc.perform(multipart("/file").file(file))
            .andDo(print())
            .andExpect(status().is3xxRedirection());
        }
      }
      
    • Attributes.addFlashAttribute를 하면 redirect할때 Model에 알아서 값이 담긴다.

    • @SpringBootTest를 하면 스프링부트 애플리케이션 기준으로 모든빈을 다 등록해준다. mockMvc를 자동으로 만들어주지 않는다.

    -

Written on July 21, 2020