Spring MVCのセッション属性
1. 概要
Webアプリケーションを開発するとき、多くの場合、複数のビューで同じ属性を参照する必要があります。 たとえば、ショッピングカートのコンテンツを複数のページに表示する必要がある場合があります。
これらの属性を保存するのに適した場所は、ユーザーのセッションです。
このチュートリアルでは、簡単な例とexamine 2 different strategies for working with a session attributeに焦点を当てます。
-
スコーププロキシの使用
-
@SessionAttributesアノテーションを使用する
2. Mavenセットアップ
Spring Bootスターターを使用してプロジェクトをブートストラップし、必要なすべての依存関係を取り込みます。
このセットアップには、親宣言、Webスターター、およびthymeleafスターターが必要です。
また、単体テストでいくつかの追加ユーティリティを提供するために、スプリングテストスターターを含めます。
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-test
test
これらの依存関係の最新バージョンはon Maven Centralにあります。
3. ユースケースの例
この例では、単純な「TODO」アプリケーションを実装します。 TodoItemのインスタンスを作成するためのフォームと、すべてのTodoItemsを表示するリストビューがあります。
フォームを使用してTodoItemを作成すると、フォームへの後続のアクセスには、最後に追加されたTodoItemの値が事前に入力されます。 セッションスコープに格納されているWe’ll use this feature to demonstrate how to “remember” form values。
2つのモデルクラスは、単純なPOJOとして実装されます。
public class TodoItem {
private String description;
private LocalDateTime createDate;
// getters and setters
}
public class TodoList extends ArrayDeque{
}
TodoListクラスはArrayDequeを拡張して、peekLastメソッドを介して最後に追加されたアイテムへの便利なアクセスを提供します。
2つのコントローラークラスが必要です。1つは、検討する戦略ごとに1つです。 それらには微妙な違いがありますが、コア機能は両方で表されます。 それぞれに3@RequestMappingsがあります。
-
@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スコープの詳細については、article on the topicを参照してください。
まず、@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. 単体テスト
スコープ付きプロキシwe first configure a 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. 討論
スコープ付きプロキシ戦略を使用する主な機能は、it has no impact on request mapping method signatures.です。これにより、@SessionAttributes戦略と比較して非常に高いレベルで読みやすさが維持されます。
コントローラにはデフォルトでsingletonスコープがあることを思い出してください。
これが、プロキシ化されていないセッションスコープBeanを単に注入するのではなく、プロキシを使用する必要がある理由です。 We can’t inject a bean with a lesser scope into a bean with greater scope.
この場合、そうしようとすると、Scope ‘session' is not active for the current threadを含むメッセージで例外がトリガーされます。
セッションスコープを使用してコントローラーを定義する場合は、proxyModeの指定を回避できます。 これには、特にユーザーセッションごとにコントローラーインスタンスを作成する必要があるため、コントローラーの作成に費用がかかる場合、欠点があります。
TodoListは、他のコンポーネントでインジェクションに使用できることに注意してください。 これは、ユースケースに応じて利点または欠点になる場合があります。 アプリケーション全体でBeanを使用できるようにすることが問題になる場合は、次の例で示すように、@SessionAttributesを使用する代わりに、インスタンスをコントローラーにスコープできます。
5. @SessionAttributesアノテーションの使用
5.1. セットアップ
この設定では、TodoListをSpring管理の@Beanとして定義していません。 代わりに、declare it as a @ModelAttribute and specify the @SessionAttributes annotation to scope it to the session for the controllerを使用します。
コントローラに初めてアクセスすると、Springはインスタンスをインスタンス化し、Modelに配置します。 Beanも@SessionAttributesで宣言するため、Springはインスタンスを格納します。
Springでの@ModelAttributeの詳細については、article on the topicを参照してください。
まず、コントローラーにメソッドを提供して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は、リダイレクトシナリオ用にModelの特殊なRedirectAttributes実装を使用して、URLパラメーターのエンコードをサポートします。 リダイレクト中、Modelに格納されている属性は通常、URLに含まれている場合にのみフレームワークで使用できます。
By using addFlashAttribute we are telling the framework that we want our TodoList to survive the redirectをURLにエンコードする必要はありません。
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戦略は、requires no additional context configuration or Spring-managed @Beansという単純なソリューションです。
最初の例とは異なり、@RequestMappingメソッド.にTodoListを挿入する必要があります
さらに、リダイレクトシナリオにはフラッシュ属性を使用する必要があります。
6. 結論
この記事では、Spring MVCでセッション属性を操作するための2つの戦略として、スコーププロキシと@SessionAttributesを使用する方法について説明しました。 この単純な例では、セッションに保存された属性はセッションが終了するまで存続することに注意してください。
サーバーの再起動またはセッションのタイムアウト間で属性を保持する必要がある場合は、Spring Sessionを使用して情報の保存を透過的に処理することを検討できます。 詳細については、Spring Sessionのour articleを参照してください。
いつものように、この記事で使用されているすべてのコードは利用可能なover on GitHubです。