diff --git a/spring-5/pom.xml b/spring-5/pom.xml
index 19dd65d78f..3b21f86e60 100644
--- a/spring-5/pom.xml
+++ b/spring-5/pom.xml
@@ -14,7 +14,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.0.0.M7
+ 2.0.0.RC2
@@ -39,10 +39,14 @@
org.springframework.boot
spring-boot-starter-webflux
-
+
org.springframework.boot
spring-boot-starter-hateoas
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
org.projectreactor
reactor-spring
diff --git a/spring-5/src/main/java/com/baeldung/execption/SpringExceptionApplication.java b/spring-5/src/main/java/com/baeldung/execption/SpringExceptionApplication.java
index 287356256c..1670da54c3 100644
--- a/spring-5/src/main/java/com/baeldung/execption/SpringExceptionApplication.java
+++ b/spring-5/src/main/java/com/baeldung/execption/SpringExceptionApplication.java
@@ -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);
+ }
}
\ No newline at end of file
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/Config.java b/spring-5/src/main/java/com/baeldung/sessionattrs/Config.java
new file mode 100644
index 0000000000..9d5c9d9f42
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/Config.java
@@ -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;
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/SessionAttrsApplication.java b/spring-5/src/main/java/com/baeldung/sessionattrs/SessionAttrsApplication.java
new file mode 100644
index 0000000000..7b9f8a700f
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/SessionAttrsApplication.java
@@ -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);
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxy.java b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxy.java
new file mode 100644
index 0000000000..0c3bd6c8b6
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxy.java
@@ -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";
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributes.java b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributes.java
new file mode 100644
index 0000000000..fc7e57b1db
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributes.java
@@ -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();
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/TodoItem.java b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoItem.java
new file mode 100644
index 0000000000..619d61d5f0
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoItem.java
@@ -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 + "]";
+ }
+}
diff --git a/spring-5/src/main/java/com/baeldung/sessionattrs/TodoList.java b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoList.java
new file mode 100644
index 0000000000..cad7811da4
--- /dev/null
+++ b/spring-5/src/main/java/com/baeldung/sessionattrs/TodoList.java
@@ -0,0 +1,8 @@
+package com.baeldung.sessionattrs;
+
+import java.util.ArrayDeque;
+
+@SuppressWarnings("serial")
+public class TodoList extends ArrayDeque{
+
+}
diff --git a/spring-5/src/main/resources/templates/sessionattrs/index.html b/spring-5/src/main/resources/templates/sessionattrs/index.html
new file mode 100644
index 0000000000..72427cd62b
--- /dev/null
+++ b/spring-5/src/main/resources/templates/sessionattrs/index.html
@@ -0,0 +1,27 @@
+
+
+
+ Session Scope in Spring MVC
+
+
+
+
+
+
+
+
+
+
Session Scope in Spring MVC - Example
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-5/src/main/resources/templates/sessionattrs/scopedproxyform.html b/spring-5/src/main/resources/templates/sessionattrs/scopedproxyform.html
new file mode 100644
index 0000000000..e72651556b
--- /dev/null
+++ b/spring-5/src/main/resources/templates/sessionattrs/scopedproxyform.html
@@ -0,0 +1,27 @@
+
+
+
+ Session Scope in Spring MVC
+
+
+
+
+
+
+
+
+
+
Scoped Proxy Example
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-5/src/main/resources/templates/sessionattrs/scopedproxytodos.html b/spring-5/src/main/resources/templates/sessionattrs/scopedproxytodos.html
new file mode 100644
index 0000000000..5493b5cf64
--- /dev/null
+++ b/spring-5/src/main/resources/templates/sessionattrs/scopedproxytodos.html
@@ -0,0 +1,48 @@
+
+
+
+ Session Scope in Spring MVC
+
+
+
+
+
+
+
+
+
+
Scoped Proxy Example
+
+
+
+
+
+
+
+
+
+
TODO List
+
+
+
+ Description |
+ Create Date |
+
+
+ Description |
+ Create Date |
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-5/src/main/resources/templates/sessionattrs/sessionattributesform.html b/spring-5/src/main/resources/templates/sessionattrs/sessionattributesform.html
new file mode 100644
index 0000000000..28e1d5d2c1
--- /dev/null
+++ b/spring-5/src/main/resources/templates/sessionattrs/sessionattributesform.html
@@ -0,0 +1,27 @@
+
+
+
+ Session Scope in Spring MVC
+
+
+
+
+
+
+
+
+
+
Session Attributes Example
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-5/src/main/resources/templates/sessionattrs/sessionattributestodos.html b/spring-5/src/main/resources/templates/sessionattrs/sessionattributestodos.html
new file mode 100644
index 0000000000..4bae12ffb9
--- /dev/null
+++ b/spring-5/src/main/resources/templates/sessionattrs/sessionattributestodos.html
@@ -0,0 +1,48 @@
+
+
+
+ Session Scope in Spring MVC
+
+
+
+
+
+
+
+
+
+
Session Attributes Example
+
+
+
+
+
+
+
+
+
+
TODO List
+
+
+
+ Description |
+ Create Date |
+
+
+ Description |
+ Create Date |
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-5/src/test/java/com/baeldung/sessionattrs/SessionAttrsApplicationTests.java b/spring-5/src/test/java/com/baeldung/sessionattrs/SessionAttrsApplicationTests.java
new file mode 100644
index 0000000000..0b15a2114d
--- /dev/null
+++ b/spring-5/src/test/java/com/baeldung/sessionattrs/SessionAttrsApplicationTests.java
@@ -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() {
+ }
+
+}
diff --git a/spring-5/src/test/java/com/baeldung/sessionattrs/TestConfig.java b/spring-5/src/test/java/com/baeldung/sessionattrs/TestConfig.java
new file mode 100644
index 0000000000..07d65dd7c2
--- /dev/null
+++ b/spring-5/src/test/java/com/baeldung/sessionattrs/TestConfig.java
@@ -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;
+ }
+}
diff --git a/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxyTest.java b/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxyTest.java
new file mode 100644
index 0000000000..3db7c183ce
--- /dev/null
+++ b/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithScopedProxyTest.java
@@ -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());
+ }
+
+}
diff --git a/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributesTest.java b/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributesTest.java
new file mode 100644
index 0000000000..a09fac9699
--- /dev/null
+++ b/spring-5/src/test/java/com/baeldung/sessionattrs/TodoControllerWithSessionAttributesTest.java
@@ -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());
+ }
+
+}