Spring MVCのセッション属性

1概要

Webアプリケーションを開発するときは、複数のビューで同じ属性を参照する必要があります。たとえば、ショッピングカートのコンテンツを複数のページに表示する必要があるとします。

これらの属性を保存するのに適した場所は、ユーザーのセッションです。

このチュートリアルでは、簡単な例に焦点を絞り、セッション属性を扱うための2つの異なる方法を検討します。

スコープスコーププロキシの使用

  • @ SessionAttributes アノテーションを使用する

2 Mavenのセットアップ

Spring Bootスターターを使用してプロジェクトをブートストラップし、必要なすべての依存関係を取り込みます。

私たちの設定には、親の宣言、Webスターター、およびThymeleafスターターが必要です。

私達はまた私達のユニットテストに追加の有用性を提供するために春のテストスターターを含めます:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</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>

これらの依存関係の最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.springframework.boot%22%20AND%20(a%3A%22springにあります。 -boot-starter-web%22%20OR%20a%3A%22spring-boot-starter-thymeleaf%22%20OR%20a%3A%22spring-boot-start-starter-test%22)[Maven Central上]。

3ユースケースの例

この例では、単純な「TODO」アプリケーションを実装します。 TodoItem のインスタンスを作成するためのフォームと、すべての __TodoItem __を表示するリストビューがあります。

フォームを使用して TodoItem を作成すると、その後のフォームへのアクセスには、最後に追加された TodoItem の値が事前に入力されます。 セッションスコープに格納されているフォーム値を「記憶」する方法を示すために、 この機能を使用します。

私たちの2つのモデルクラスは単純なPOJOとして実装されています。

public class TodoItem {

    private String description;
    private LocalDateTime createDate;

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

}

TodoList クラスは ArrayDeque を拡張して、 peekLast メソッドを介して最後に追加された項目に簡単にアクセスできるようにします。

2つのコントローラクラスが必要です。各戦略に1つずつ必要です。微妙な違いはありますが、中心となる機能は両方で表されます。それぞれ3つの __ @ RequestMapping __があります。

  • @ GetMapping(“/form”) - このメソッドが責任を持ちます。

フォームの初期化とフォームビューのレンダリング TodoList が空でない場合、このメソッドは最後に追加された TodoItem をフォームに事前入力します。

  • @ PostMapping(“/form”) - このメソッドは追加を担当します。

送信された TodoItem TodoList に送信し、リストのURLにリダイレクトします。

  • @ GetMapping(“/todos.html”)– このメソッドは単純に

TodoList Model に表示してリストビューをレンダリングします。

4スコーププロキシを使用する

4.1. セットアップ

この設定では、私たちの TodoList は、プロキシによってサポートされるセッションスコープの @ Bean として設定されています。 @ Bean がプロキシであるという事実は、シングルトンスコープの @ Controller にそれを挿入できることを意味します。

コンテキストの初期化時にセッションがないため、Springは依存関係として注入するために TodoList のプロキシを作成します。 TodoList のターゲットインスタンスは、要求に応じて必要に応じてインスタンス化されます。

SpringでのBeanスコープの詳細については、リンク:/spring-bean-scopes[トピックに関する記事]を参照してください。

まず、 @ Configuration クラス内にBeanを定義します。

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

次に、Beanを @ 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を構成します。

まず、 TestConfig CustomScopeConfigurer を定義します。

@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__戦略と比較して、読みやすさが非常に高いレベルで維持されます。

コントローラにはデフォルトで singleton スコープがあることを思い出してください。

これが、プロキシではないセッションスコープのBeanを単に注入するのではなくプロキシを使用する必要がある理由です。小さいスコープのBeanを大きいスコープのBeanに注入することはできません。

これを試みると、この場合、次の内容を含むメッセージを含む例外が発生します。 Scope 'session’は現在のスレッドに対してアクティブではありません

セッションスコープでコントローラを定義したい場合は、 proxyMode を指定しないでください。これは、コントローラーインスタンスをユーザーセッションごとに作成する必要があるため、コントローラーの作成にコストがかかる場合は特に不利です。

TodoList は他のコンポーネントがインジェクションに使用できることに注意してください。

これは、ユースケースによっては利点となることもあれば、不利なこともあります。アプリケーション全体でBeanを使用できるようにすることに問題がある場合は、次の例のように @ SessionAttributes を使用してインスタンスの範囲をコントローラに変更できます。

5 @ SessionAttributes アノテーションを使用する

5.1. セットアップ

この設定では、 TodoList をSpring管理の @ Bean として定義しません。

代わりに、それを @ ModelAttribute として宣言し、それをコントローラーのセッションに適用するための @ SessionAttributes アノテーションを指定します。

私たちのコントローラが初めてアクセスされたとき、Springはインスタンスをインスタンス化し、それを Model に配置します。 Beanも @ SessionAttributes で宣言しているので、Springはインスタンスを格納します。

Springでの @ ModelAttribute のより詳細な議論については、リンク:/spring-mvc-and-the-modelattribute-annotation[トピックに関する記事]を参照してください。

まず、コントローラにメソッドを提供して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 メソッドでは、 RedirectViewを返す前に RedirectAttributes を注入し、 addFlashAttribute__を呼び出します。これは、最初の例と比較して、実装上の重要な違いです。

@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 実装を使います。リダイレクト中、 Model に格納されているすべての属性は、通常、それらがURLに含まれている場合にのみフレームワークで使用可能になります。

  • addFlashAttribute を使用することによって、 TodoList がリダイレクトをURLでエンコードしなくても** 生き残りたいことをフレームワークに伝えます。

5.2. 単体テスト

フォームビューコントローラメソッドの単体テストは、最初の例で見たテストと同じです。ただし、 @ PostMapping のテストは、動作を検証するためにflash属性にアクセスする必要があるため、少し異なります。

@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____sを必要としない直接的な解決策です。

最初の例とは異なり、 @ RequestMapping メソッドに TodoList をインジェクトする必要があります _. _

さらに、リダイレクトシナリオではフラッシュ属性を使用する必要があります。

6. 結論

この記事では、Spring MVCでセッション属性を扱うための2つの戦略としてスコープ付きプロキシと @ SessionAttributes を使用することを検討しました。この単純な例では、sessionに格納されている属性はセッションの存続期間中しか存続できません。

サーバーの再起動またはセッションのタイムアウトの間に属性を保持する必要がある場合は、情報の保存を透過的に処理するためにSpring Sessionを使用することを検討できます。詳しくはSpring Sessionの 私たちの記事 を見てください。

いつものように、この記事で使用されているすべてのコードはhttps://github.com/eugenp/tutorials/tree/master/spring-mvc-forms-thymeleaf[GitHubで利用可能]