1. 개요

웹 애플리케이션을 개발할 때 종종 여러보기에서 동일한 속성을 참조해야합니다. 예를 들어, 여러 페이지에 표시해야하는 장바구니 내용이있을 수 있습니다.

이러한 속성을 저장하기에 좋은 위치는 사용자 세션입니다.

이 예제에서는 간단한 예제에 초점을 맞추고 세션 속성 작업을위한 두 가지 다른 전략을 살펴 봅니다 .

  • 범위 프록시 사용
  • @ SessionAttributes 어노테이션 사용

2. Maven 설정

Spring Boot 스타터를 사용하여 프로젝트를 부트 스트랩하고 필요한 모든 의존성을 가져올 것입니다.

설정에는 부모 선언, 웹 스타터 및 thymeleaf 스타터가 필요합니다.

또한 단위 테스트에서 몇 가지 추가 유틸리티를 제공하기 위해 스프링 테스트 스타터를 포함 할 것입니다.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
     </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

이러한 의존성의 최신 버전은 Maven Central 에서 찾을 수 있습니다 .

3. 사용 사례 예

이 예에서는 간단한 "TODO"애플리케이션을 구현합니다. TodoItem 인스턴스를 생성하기위한 양식 과 모든 TodoItem 을 표시하는 List보기가 있습니다.

양식을 사용하여 TodoItem생성 하면 양식의 후속 액세스가 가장 최근에 추가 된 TodoItem 의 값으로 미리 채워 집니다 . 우리는 사용합니다 t을 양식 값 "기억"하는 방법을 보여 자신의 기능을 세션 범위에 저장됩니다.

2 개의 모델 클래스는 간단한 POJO로 구현됩니다.

public class TodoItem {

    private String description;
    private LocalDateTime createDate;

    // getters and setters
}
public class TodoList extends ArrayDeque<TodoItem>{

}

우리 하여 ToDoList의 클래스는 확장 의 ArrayDeque을 우리에게를 통해 가장 최근에 추가 된 항목에 편리하게 액세스 제공하는 peekLast와의 방법을.

2 개의 컨트롤러 클래스가 필요합니다 : 우리가 살펴볼 각 전략에 대해 하나씩. 미묘한 차이가 있지만 핵심 기능은 둘 다에 표시됩니다. 각각에는 3 개의 @RequestMapping 이 있습니다 .

  • @GetMapping ( "/ form") –이 메서드는 양식을 초기화하고 양식보기를 렌더링합니다. TodoList 가 비어 있지 않은경우메서드는 가장 최근에 추가 된 TodoItem으로 양식을 미리 채 웁니다 .
  • ( "/ 양식") @PostMapping -이 방법은 제출 된 추가하기위한 책임이있다 가 ToDoItem을 받는 하여 ToDoList 하고 List의 URL로 리디렉션.
  • @GetMapping (“/ todos.html”) – 이 메소드는표시를 위해 TodoList 모델추가하고 List보기를 렌더링합니다.

4. 범위 프록시 사용

4.1. 설정

이 설정에서 TodoList프록시에 의해 지원되는 세션 범위 @Bean 으로 구성됩니다 . @Bean 이 프록시 라는 사실은 우리가 싱글 톤 범위의 @Controller 에 주입 할 수 있다는 것을 의미합니다 .

컨텍스트가 초기화 될 때 세션이 없기 때문에 Spring은 TodoList 의 프록시를 생성 하여 의존성으로 주입합니다. TodoList 의 대상 인스턴스는 요청에 의해 필요할 때 필요에 따라 인스턴스화됩니다.

Spring의 빈 범위에 대한보다 심층적 인 논의 는 주제에 대한 기사를 참조하십시오 .

먼저 @Configuration 클래스 내에서 빈을 정의합니다 .

@Bean
@Scope(
  value = WebApplicationContext.SCOPE_SESSION, 
  proxyMode = ScopedProxyMode.TARGET_CLASS)
public TodoList todos() {
    return new TodoList();
}

다음으로 빈을 @Controller에 대한 의존성으로 선언하고 다른 의존성과 마찬가지로 삽입합니다.

@Controller
@RequestMapping("/scopedproxy")
public class TodoControllerWithScopedProxy {

    private TodoList todos;

    // constructor and request mappings
}

마지막으로 요청에서 Bean을 사용하는 것은 단순히 해당 메소드를 호출하는 것을 포함합니다.

@GetMapping("/form")
public String showForm(Model model) {
    if (!todos.isEmpty()) {
        model.addAttribute("todo", todos.peekLast());
    } else {
        model.addAttribute("todo", new TodoItem());
    }
    return "scopedproxyform";
}

4.2. 단위 테스트

범위가 지정된 프록시를 사용하여 구현을 테스트하기 위해 먼저 SimpleThreadScope를 구성합니다 . 이렇게하면 단위 테스트가 테스트중인 코드의 런타임 조건을 정확하게 시뮬레이션 할 수 있습니다.

먼저 TestConfigCustomScopeConfigurer를 정의합니다 .

@Configuration
public class TestConfig {

    @Bean
    public CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("session", new SimpleThreadScope());
        return configurer;
    }
}

이제 양식의 초기 요청에 초기화되지 않은 TodoItem이 포함되어 있는지 테스트하여 시작할 수 있습니다 .

@RunWith(SpringRunner.class) 
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestConfig.class) 
public class TodoControllerWithScopedProxyIntegrationTest {

    // ...

    @Test
    public void whenFirstRequest_thenContainsUnintializedTodo() throws Exception {
        MvcResult result = mockMvc.perform(get("/scopedproxy/form"))
          .andExpect(status().isOk())
          .andExpect(model().attributeExists("todo"))
          .andReturn();

        TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
 
        assertTrue(StringUtils.isEmpty(item.getDescription()));
    }
}

제출이 리디렉션을 발행하고 후속 양식 요청이 새로 추가 된 TodoItem으로 미리 채워져 있는지 확인할 수도 있습니다 .

@Test
public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo() throws Exception {
    mockMvc.perform(post("/scopedproxy/form")
      .param("description", "newtodo"))
      .andExpect(status().is3xxRedirection())
      .andReturn();

    MvcResult result = mockMvc.perform(get("/scopedproxy/form"))
      .andExpect(status().isOk())
      .andExpect(model().attributeExists("todo"))
      .andReturn();
    TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
 
    assertEquals("newtodo", item.getDescription());
}

4.3. 토론

범위가 지정된 프록시 전략을 사용하는 주요 기능은 요청 매핑 메서드 서명에 영향을주지 않는다는 것입니다. 이것은 @SessionAttributes 전략에 비해 매우 높은 수준의 가독성을 유지합니다 .

컨트롤러에는 기본적으로 단일 범위가 있다는 것을 기억하는 것이 도움이 될 수 있습니다 .

이것이 우리가 프록시되지 않은 세션 범위 빈을 단순히 주입하는 대신 프록시를 사용해야하는 이유입니다. 더 작은 범위의 빈을 더 큰 범위의 빈에 주입 할 수 없습니다.

이 경우이 경우 Scope 'session'is not active for the current thread를 포함하는 메시지와 함께 예외가 트리거 됩니다.

세션 범위로 컨트롤러를 정의하려는 경우 proxyMode 지정을 피할 수 있습니다. 특히 각 사용자 세션에 대해 컨트롤러 인스턴스를 만들어야하므로 컨트롤러를 만드는 데 비용이 많이 드는 경우 단점이있을 수 있습니다.

참고 하여 ToDoList가 주입을위한 다른 구성 요소에 사용할 수 있습니다. 이는 사용 사례에 따라 장점 또는 단점이 될 수 있습니다. 전체 애플리케이션에서 빈을 사용할 수 있도록 만드는 것이 문제가되는 경우 다음 예제에서 볼 수 있듯이 @SessionAttributes사용하는 대신 인스턴스의 범위를 컨트롤러로 지정할 수 있습니다 .

5. @SessionAttributes 어노테이션 사용

5.1. 설정

이 설정에서는 TodoList 를 Spring 관리 @Bean 으로 정의하지 않습니다 . 대신 @ModelAttribute 로 선언 하고 @SessionAttributes 어노테이션을 지정 하여 컨트롤러의 세션 범위를 지정합니다 .

컨트롤러에 처음 액세스하면 Spring은 인스턴스를 인스턴스화하고 Model에 배치합니다 . @SessionAttributes에 빈을 선언하기 때문에 Spring은 인스턴스를 저장할 것이다.

Spring @ModelAttribute대한 더 자세한 논의 는 주제에 대한 기사를 참조하십시오 .

먼저 컨트롤러에 메소드를 제공하여 Bean을 선언하고 @ModelAttribute로 메소드에 어노테이션을 추가합니다 .

@ModelAttribute("todos")
public TodoList todos() {
    return new TodoList();
}

다음으로 컨트롤러에게 @SessionAttributes 를 사용하여 TodoList 를 세션 범위로 처리하도록 알립니다 .

@Controller
@RequestMapping("/sessionattributes")
@SessionAttributes("todos")
public class TodoControllerWithSessionAttributes {
    // ... other methods
}

마지막으로 요청 내에서 Bean을 사용하기 위해 @RequestMapping 의 메소드 서명에 해당 Bean에 대한 참조를 제공합니다 .

@GetMapping("/form")
public String showForm(
  Model model,
  @ModelAttribute("todos") TodoList todos) {
 
    if (!todos.isEmpty()) {
        model.addAttribute("todo", todos.peekLast());
    } else {
        model.addAttribute("todo", new TodoItem());
    }
    return "sessionattributesform";
}

에서 @PostMapping의 방법, 우리는 주입 RedirectAttributes을 하고 전화 addFlashAttribute을 우리의 반환하기 전에 만약 RedirectView을 . 이것은 첫 번째 예제와 비교하여 구현에서 중요한 차이점입니다.

@PostMapping("/form")
public RedirectView create(
  @ModelAttribute TodoItem todo, 
  @ModelAttribute("todos") TodoList todos, 
  RedirectAttributes attributes) {
    todo.setCreateDate(LocalDateTime.now());
    todos.add(todo);
    attributes.addFlashAttribute("todos", todos);
    return new RedirectView("/sessionattributes/todos.html");
}

Spring은 URL 매개 변수의 인코딩을 지원하기 위해 리디렉션 시나리오에 대한 Model 의 특수 RedirectAttributes 구현을 사용합니다 . 리디렉션 중에 모델에 저장된 모든 속성 은 일반적으로 URL에 포함 된 경우에만 프레임 워크에서 사용할 수 있습니다.

addFlashAttribute 를 사용 하여 URL에서 인코딩 할 필요없이 TodoList 가 리디렉션을 유지하기를 원한다는 것을 프레임 워크에 알립니다 .

5.2. 단위 테스트

폼 뷰 컨트롤러 메서드의 단위 테스트는 첫 번째 예제에서 살펴본 테스트와 동일합니다. 그러나 @PostMapping 테스트 는 동작을 확인하기 위해 플래시 속성에 액세스해야하기 때문에 약간 다릅니다.

@Test
public void whenTodoExists_thenSubsequentFormRequestContainsesMostRecentTodo() throws Exception {
    FlashMap flashMap = mockMvc.perform(post("/sessionattributes/form")
      .param("description", "newtodo"))
      .andExpect(status().is3xxRedirection())
      .andReturn().getFlashMap();

    MvcResult result = mockMvc.perform(get("/sessionattributes/form")
      .sessionAttrs(flashMap))
      .andExpect(status().isOk())
      .andExpect(model().attributeExists("todo"))
      .andReturn();
    TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
 
    assertEquals("newtodo", item.getDescription());
}

5.3. 토론

@ModelAttribute@SessionAttributes 세션의 속성을 저장하기위한 전략은 간단한 솔루션을 추가 컨텍스트 구성 또는 Spring 관리 필요하지 않습니다 @Bean .

첫 번째 예와는 달리, 그것은 주입하는 것이 필요 하여 ToDoList를@RequestMapping의 방법 .

또한 리디렉션 시나리오에 플래시 속성을 사용해야합니다.

6. 결론

이 기사에서 우리는 Spring MVC에서 세션 속성 작업을위한 두 가지 전략으로 범위가 지정된 프록시와 @SessionAttributes 를 사용하는 방법을 살펴 보았습니다 . 이 간단한 예에서 세션에 저장된 모든 속성은 세션 수명 동안 만 유지됩니다.

서버 재시작 또는 세션 시간 초과 사이에 속성을 유지해야하는 경우 정보 저장을 투명하게 처리하기 위해 Spring Session을 사용할 수 있습니다. 한 번 봐 가지고 우리의 기사 자세한 내용은 Spring 세션에 있습니다.

항상 그렇듯이이 기사에서 사용 된 모든 코드 는 GitHub 에서 사용할 수 있습니다 .