Merge pull request #3722 from chrisoberle/master

BAEL-1533 Session Attributes in Spring MVC
This commit is contained in:
Tom Hombergs 2018-02-25 17:19:07 +01:00 committed by GitHub
commit 56a6e06bdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 572 additions and 15 deletions

View File

@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
<version>2.0.0.RC2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
@ -39,10 +39,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>

View File

@ -1,14 +1,14 @@
package com.baeldung.execption;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@ComponentScan(basePackages = { "com.baeldung.execption" })
public class SpringExceptionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringExceptionApplication.class, args);
}
package com.baeldung.execption;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@ComponentScan(basePackages = { "com.baeldung.execption" })
public class SpringExceptionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringExceptionApplication.class, args);
}
}

View File

@ -0,0 +1,44 @@
package com.baeldung.sessionattrs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
@EnableWebMvc
@Configuration
public class Config implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@Bean
@Scope(
value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public TodoList todos() {
return new TodoList();
}
@Bean
public ITemplateResolver templateResolver() {
ClassLoaderTemplateResolver resolver
= new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/sessionattrs/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.sessionattrs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(
exclude = {SecurityAutoConfiguration.class,
DataSourceAutoConfiguration.class})
public class SessionAttrsApplication {
public static void main(String[] args) {
SpringApplication.run(SessionAttrsApplication.class, args);
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.sessionattrs;
import java.time.LocalDateTime;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/scopedproxy")
public class TodoControllerWithScopedProxy {
private TodoList todos;
public TodoControllerWithScopedProxy(TodoList todos) {
this.todos = todos;
}
@GetMapping("/form")
public String showForm(Model model) {
if (!todos.isEmpty()) {
model.addAttribute("todo", todos.peekLast());
} else {
model.addAttribute("todo", new TodoItem());
}
return "scopedproxyform";
}
@PostMapping("/form")
public String create(@ModelAttribute TodoItem todo) {
todo.setCreateDate(LocalDateTime.now());
todos.add(todo);
return "redirect:/scopedproxy/todos.html";
}
@GetMapping("/todos.html")
public String list(Model model) {
model.addAttribute("todos", todos);
return "scopedproxytodos";
}
}

View File

@ -0,0 +1,55 @@
package com.baeldung.sessionattrs;
import java.time.LocalDateTime;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
@Controller
@RequestMapping("/sessionattributes")
@SessionAttributes("todos")
public class TodoControllerWithSessionAttributes {
@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("/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");
}
@GetMapping("/todos.html")
public String list(
Model model,
@ModelAttribute("todos") TodoList todos) {
model.addAttribute("todos", todos);
return "sessionattributestodos";
}
@ModelAttribute("todos")
public TodoList todos() {
return new TodoList();
}
}

View File

@ -0,0 +1,39 @@
package com.baeldung.sessionattrs;
import java.time.LocalDateTime;
public class TodoItem {
private String description;
private LocalDateTime createDate;
public TodoItem(String description, LocalDateTime createDate) {
this.description = description;
this.createDate = createDate;
}
public TodoItem() {
// default no arg constructor
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
}
@Override
public String toString() {
return "TodoItem [description=" + description + ", createDate=" + createDate + "]";
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.sessionattrs;
import java.util.ArrayDeque;
@SuppressWarnings("serial")
public class TodoList extends ArrayDeque<TodoItem>{
}

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Session Scope in Spring MVC</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</head>
<body>
<div class="container">
<p>
<h3>Session Scope in Spring MVC - Example</h3>
</p>
</div>
<div class="container">
<div class="row">
<div class="col">
<a href="/scopedproxy/form" class="btn btn-sm btn-primary btn-block float-right" role="button">Session Scoped Proxy Example</a></div>
<div class="col">
<a href="/sessionattributes/form" class="btn btn-sm btn-primary btn-block float-right" role="button">Session Attributes Example</a></div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Session Scope in Spring MVC</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</head>
<body>
<div class="container">
<p>
<h3>Scoped Proxy Example</h3>
</p>
</div>
<div class="container">
<h5>Enter a TODO</h5>
<form action="#" th:action="@{/scopedproxy/form}" th:object="${todo}" method="post">
<div class="input-group">
<input type="text" th:field="*{description}" class="form-control" placeholder="TODO" required autofocus/>
<button class="btn btn-sm btn-primary form-control text-center" type="submit" style="max-width: 80px;">Create</button>
</div>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Session Scope in Spring MVC</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
crossorigin="anonymous">
</head>
<body>
<div class="container">
<p>
<h3>Scoped Proxy Example</h3>
</p>
</div>
<div class="container">
<br/>
<div class="row">
<a th:href="@{/scopedproxy/form}" class="btn btn-sm btn-primary btn-block" role="button"
style="width: 10em;">Add New</a>
</div>
<br/>
<div class="row">
<div class="col card" style="padding: 1em;">
<div class="card-block">
<h5 class="card-title">TODO List</h5>
<table>
<tbody>
<tr>
<th width="75%">Description</th>
<th width="25%" >Create Date</th>
</tr>
<tr th:each="todo : ${todos}">
<td th:text="${todo.description}">Description</td>
<td th:text="${#temporals.format(todo.createDate, 'MM-dd-yyyy HH:mm:ss')}">Create Date</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Session Scope in Spring MVC</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</head>
<body>
<div class="container">
<p>
<h3>Session Attributes Example</h3>
</p>
</div>
<div class="container">
<h5>Enter a TODO</h5>
<form action="#" th:action="@{/sessionattributes/form}" th:object="${todo}" method="post">
<div class="input-group">
<input type="text" th:field="*{description}" class="form-control" placeholder="TODO" required autofocus/>
<button class="btn btn-sm btn-primary form-control text-center" type="submit" style="max-width: 80px;">Create</button>
</div>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Session Scope in Spring MVC</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
crossorigin="anonymous">
</head>
<body>
<div class="container">
<p>
<h3>Session Attributes Example</h3>
</p>
</div>
<div class="container">
<br/>
<div class="row">
<a th:href="@{/sessionattributes/form}" class="btn btn-sm btn-primary btn-block" role="button"
style="width: 10em;">Add New</a>
</div>
<br/>
<div class="row">
<div class="col card" style="padding: 1em;">
<div class="card-block">
<h5 class="card-title">TODO List</h5>
<table>
<tbody>
<tr>
<th width="75%">Description</th>
<th width="25%" >Create Date</th>
</tr>
<tr th:each="todo : ${todos}">
<td th:text="${todo.description}">Description</td>
<td th:text="${#temporals.format(todo.createDate, 'MM-dd-yyyy HH:mm:ss')}">Create Date</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,16 @@
package com.baeldung.sessionattrs;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SessionAttrsApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@ -0,0 +1,17 @@
package com.baeldung.sessionattrs;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.SimpleThreadScope;
@Configuration
public class TestConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("session", new SimpleThreadScope());
return configurer;
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.sessionattrs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestConfig.class)
public class TodoControllerWithScopedProxyTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.build();
}
@Test
public void whenFirstRequest_thenContainsAllCategoriesAndUnintializedTodo() 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()));
}
@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());
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.sessionattrs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.FlashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class TodoControllerWithSessionAttributesTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.build();
}
@Test
public void whenFirstRequest_thenContainsUnintializedTodo() throws Exception {
MvcResult result = mockMvc.perform(get("/sessionattributes/form"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("todo"))
.andReturn();
TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
assertTrue(StringUtils.isEmpty(item.getDescription()));
}
@Test
public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo() 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());
}
}