SpringMVC-11 - 響應
在 SpringMVC 中,響應是服務器對客戶端請求的反饋,它可以以多種形式呈現,包括視圖名稱、ModelAndView 對象、JSON 數據以及重定向等。以下是對 SpringMVC 中不同響應類型的詳細介紹:
1. 視圖名稱
通過返回視圖名稱,SpringMVC 會將請求轉發到對應的視圖資源進行渲染。視圖資源可以是 JSP、Thymeleaf 模板、FreeMarker 模板等。
@RequestMapping("/home")
public String home(Model model) {
????model.addAttribute("message", "Welcome to Spring MVC");
? ?return "home"; // 返回視圖名稱
}
在上述示例中,home方法返回一個字符串home,SpringMVC 會根據配置的視圖解析器,將該字符串解析為實際的視圖資源(例如/WEB-INF/views/home.jsp),并將模型數據(message)傳遞給視圖進行渲染。最終,渲染后的視圖將作為響應返回給客戶端。
視圖解析器配置
視圖解析器負責將邏輯視圖名稱解析為實際的視圖資源。常見的視圖解析器有InternalResourceViewResolver、ThymeleafViewResolver等。
InternalResourceViewResolver
@Bean
public ViewResolver viewResolver() {
????InternalResourceViewResolver resolver = new InternalResourceViewResolver();
????resolver.setPrefix("/WEB-INF/views/");
????resolver.setSuffix(".jsp");
return resolver;
}
ThymeleafViewResolver
@Bean
public ViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
????ThymeleafViewResolver resolver = new ThymeleafViewResolver();
????resolver.setTemplateEngine(templateEngine);
????resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
????SpringTemplateEngine engine = new SpringTemplateEngine();
????engine.setTemplateResolver(templateResolver);
return engine;
}
@Bean
public TemplateResolver templateResolver() {
????ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
????resolver.setPrefix("/WEB-INF/templates/");
????resolver.setSuffix(".html");
????resolver.setTemplateMode("HTML5");
????resolver.setCharacterEncoding("UTF-8");
return resolver;
}
2. ModelAndView
ModelAndView對象包含了模型數據和視圖信息,通過返回ModelAndView,可以在一個對象中同時指定模型數據和要渲染的視圖。
@RequestMapping("/products")
public ModelAndView listProducts() {
????ModelAndView mav = new ModelAndView("products/list");
????mav.addObject("products", productService.getAllProducts());
return mav;
}
在上述示例中,listProducts方法創建了一個ModelAndView對象,指定了視圖名稱為products/list,并添加了一個名為products的模型數據,該數據包含了所有產品的列表。SpringMVC 會根據視圖名稱解析視圖資源,并將模型數據傳遞給視圖進行渲染。
使用 ModelAndView 的優勢
靈活性高:可以在一個對象中同時處理模型數據和視圖邏輯。
方便傳遞復雜數據結構:適合傳遞多個模型數據或復雜的對象。
3. JSON 響應
在現代 Web 應用中,JSON 是一種常用的數據交換格式。
SpringMVC 提供了對 JSON 響應的支持,通過使用@RestController注解或@ResponseBody注解,可以將方法的返回值直接轉換為 JSON 格式并返回給客戶端。
@RestController
@RequestMapping("/api/products")
public class ProductRestController {
????@GetMapping
????public List<Product> getAllProducts() {
????????return productService.getAllProducts();
}
}
在上述示例中,ProductRestController類使用了@RestController注解,該注解等價于@Controller和@ResponseBody的組合。因此,getAllProducts方法的返回值會被自動轉換為 JSON 格式并返回給客戶端。
JSON 序列化與反序列化
SpringMVC 使用 Jackson 庫來進行 JSON 的序列化和反序列化。Jackson 會自動將 ?對象轉換為 JSON 字符串,并在需要時將 JSON 字符串轉換回 ?對象。
配置 JSON 序列化選項
可以通過配置 Jackson 的ObjectMapper來定制 JSON 的序列化行為,例如:
@Configuration
public class WebConfig implements WebMvcConfigurer {
????@Override
????public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
????????MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
????????ObjectMapper mapper = new ObjectMapper();
????????// 配置序列化選項
????????mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 不序列化null值
????????mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 忽略未知屬性
????????converter.setObjectMapper(mapper);
????????converters.add(converter);
}
}
4. 重定向
重定向是指服務器返回一個 HTTP 狀態碼 302(或其他重定向狀態碼),并在響應頭中指定一個新的 URL,客戶端會根據這個新的 URL 再次發送請求。
@RequestMapping("/save")
public String saveProduct(@ModelAttribute Product product) {
????productService.save(product);
? ? return "redirect:/products";
}
在上述示例中,saveProduct方法在保存產品后,返回一個redirect:/products的字符串,SpringMVC 會將其解析為重定向指令,客戶端會被重定向到/products路徑。
重定向的作用
防止表單重復提交:用戶刷新頁面時不會再次提交表單。
引導用戶到新的頁面:例如注冊成功后重定向到登錄頁面。
重定向傳遞參數
可以使用RedirectAttributes來在重定向過程中傳遞參數:
@RequestMapping("/save")
public String saveProduct(@ModelAttribute Product product, RedirectAttributes attrs) {
????productService.save(product);
????attrs.addFlashAttribute("message", "產品保存成功");
return "redirect:/products";
}
在上述示例中,addFlashAttribute方法添加了一個臨時屬性message,這個屬性只會在重定向后的下一次請求中有效。
總結
SpringMVC 提供了多種響應類型,包括視圖名稱、ModelAndView、JSON 響應和重定向。開發者可以根據具體需求選擇合適的響應方式,以實現靈活、高效的 Web 應用開發。
視圖名稱:適用于傳統的頁面渲染場景,通過視圖解析器將邏輯視圖名映射到實際視圖資源。
ModelAndView:提供了更靈活的方式來處理模型數據和視圖邏輯,適合傳遞多個模型數據或復雜對象。
JSON 響應:用于返回 JSON 格式的數據,適用于前后端分離的架構,通過 Jackson 庫進行 JSON 的序列化和反序列化。
重定向:用于引導用戶到新的頁面,防止表單重復提交,可通過RedirectAttributes傳遞臨時參數。
SpringMVC-12-REST 風格簡介
REST (Representational State Transfer) 是一種軟件架構風格,它使用 HTTP 協議的標準方法 (GET、POST、PUT、DELETE) 來操作資源。
REST 的主要特點:
資源導向:每個 URL 代表一種資源
使用標準 HTTP 方法:GET (獲取)、POST (創建)、PUT (更新)、DELETE (刪除)
無狀態:每個請求都是獨立的,不依賴于之前的請求
統一接口:所有資源都通過統一的接口進行操作
SpringMVC-13-RESTful 入門案例
下面是一個簡單的 RESTful API 示例:
@RestController
@RequestMapping("/api/products")
public class ProductRestController {
????@Autowired
????private ProductService productService;
????// 獲取所有產品
????@GetMapping
????public List<Product> getAll() {
????????return productService.getAll();
????}
????// 獲取單個產品
????@GetMapping("/{id}")
????public Product getById(@PathVariable Long id) {
????????return productService.getById(id);
????}
????// 創建產品
????@PostMapping
????public Product create(@RequestBody Product product) {
????????return productService.save(product);
????}
????// 更新產品
????@PutMapping("/{id}")
????public Product update(@PathVariable Long id, @RequestBody Product product) {
????????return productService.update(id, product);
????}
????// 刪除產品
????@DeleteMapping("/{id}")
????public void delete(@PathVariable Long id) {
????????productService.delete(id);
}
}
SpringMVC-14-RESTful 快速開發
可以使用 Spring Data JPA 和 Spring HATEOAS 來加速 RESTful API 的開發。
添加依賴:
xml
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency>
????<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
創建實體和 Repository:
@Entity
public class Product {
????@Id
????@GeneratedValue(strategy = GenerationType.IDENTITY)
????private Long id;
????private String name;
????private double price;
// getters and setters....
}
public interface ProductRepository extends JpaRepository<Product, Long> {}
創建資源控制器:
@RestController
@RequestMapping("/api/products")
public class ProductController {
????private final ProductRepository repository;
????private final EntityLinks entityLinks;
????@Autowired
????public ProductController(ProductRepository repository, EntityLinks entityLinks) {
????????this.repository = repository;
????????this.entityLinks = entityLinks;
????}
????@GetMapping
????public Resources<Resource<Product>> getAll() {
????????List<Resource<Product>> products = repository.findAll().stream()
????????????.map(product -> new Resource<>(product,
????????????????linkTo(methodOn(ProductController.class).getOne(product.getId())).withSelfRel(),
????????????????linkTo(methodOn(ProductController.class).getAll()).withRel("products")))
????????????.collect(Collectors.toList());
????????return new Resources<>(products,
????????????linkTo(methodOn(ProductController.class).getAll()).withSelfRel());
????}
????@GetMapping("/{id}")
????public Resource<Product> getOne(@PathVariable Long id) {
????????Product product = repository.findById(id)
????????????.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id));
????????return new Resource<>(product,
????????????linkTo(methodOn(ProductController.class).getOne(id)).withSelfRel(),
????????????linkTo(methodOn(ProductController.class).getAll()).withRel("products"));
}
}
SpringMVC-15 - 案例:基于 RESTful 頁面數據交互 (后臺接口開發)
假設我們要開發一個簡單的產品管理系統,下面是后臺接口的實現:
@RestController
@RequestMapping("/api/products")
public class ProductApiController {
????@Autowired
????private ProductService productService;
????@GetMapping
????public Page<ProductDTO> listProducts(
????????????@RequestParam(name = "page", defaultValue = "0") int page,
????????????@RequestParam(name = "size", defaultValue = "10") int size) {
????????return productService.getProducts(page, size);
????}
????@PostMapping
public ResponseEntity<ProductDTO> createProduct
(@RequestBody ProductDTO productDTO) {
????????ProductDTO createdProduct = productService.createProduct(productDTO);
????????URI location = ServletUriComponentsBuilder
????????????????.fromCurrentRequest()
????????????????.path("/{id}")
????????????????.buildAndExpand(createdProduct.getId())
????????????????.toUri();
????????return ResponseEntity.created(location).body(createdProduct);
????}
????@GetMapping("/{id}")
????public ResponseEntity<ProductDTO> getProduct(@PathVariable Long id) {
????????ProductDTO productDTO = productService.getProduct(id);
????????return ResponseEntity.ok(productDTO);
????}
????@PutMapping("/{id}")
????public ResponseEntity<ProductDTO> updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
????????ProductDTO updatedProduct = productService.updateProduct(id, productDTO);
????????return ResponseEntity.ok(updatedProduct);
????}
????@DeleteMapping("/{id}")
????public ResponseEntity<?> deleteProduct(@PathVariable Long id) {
????????productService.deleteProduct(id);
????????return ResponseEntity.noContent().build();
}
}
SpringMVC-16 - 案例:基于 RESTful 頁面數據交互 (頁面訪問處理)
下面是一個使用 Thymeleaf 模板引擎的前端頁面示例:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
????<title>Product Management</title>
????<script src="https://cdn.tailwindcss.com">
</script>
????<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
????<div class="container mx-auto px-4 py-8">
????????<h1 class="text-3xl font-bold mb-6">Product Management</h1>
????????
????????<div class="mb-6">
????????????<button id="addProductBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
????????????????<i class="fa fa-plus"></i> Add Product
????????????</button>
????????</div>
????????
????????<div class="bg-white rounded-lg shadow-md p-6 mb-6">
????????????<table class="min-w-full">
????????????????<thead>
????????????????????<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
????????????????????????<th class="py-3 px-6 text-left">ID</th>
????????????????????????<th class="py-3 px-6 text-left">Name</th>
????????????????????????<th class="py-3 px-6 text-left">Price</th>
????????????????????????<th class="py-3 px-6 text-left">Actions</th>
????????????????????</tr>
????????????????</thead>
????????????????<tbody id="productsTableBody" class="text-gray-600 text-sm font-light">
????????????????????<!-- Products will be loaded here via Script -->
????????????????</tbody>
????????????</table>
????????</div>
????????
????????<!-- Pagination -->
????????<div id="pagination" class="flex items-center justify-between bg-white rounded-lg shadow-md p-6">
????????????<!-- Pagination controls will be loaded here via Script -->
????????</div>
????????
????????<!-- Add/Edit Modal -->
????????<div id="productModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
????????????<div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
????????????????<h2 id="modalTitle" class="text-2xl font-bold mb-4">Add Product</h2>
????????????????<form id="productForm">
????????????????????<input type="hidden" id="productId">
????????????????????<div class="mb-4">
????????????????????????<label for="productName" class="block text-gray-700 font-bold mb-2">Name:</label>
????????????????????????<input type="text" id="productName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" required>
????????????????????</div>
????????????????????<div class="mb-4">
????????????????????????<label for="productPrice" class="block text-gray-700 font-bold mb-2">Price:</label>
????????????????????????<input type="number" id="productPrice" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" step="0.01" required>
????????????????????</div>
????????????????????<div class="flex justify-end">
????????????????????????<button type="button" id="cancelBtn" class="mr-2 px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded">Cancel</button>
????????????????????????<button type="submit" class="px-4 py-2 bg-blue-500 hover:bg-blue-700 text-white rounded">Save</button>
????????????????????</div>
????????????????</form>
????????????</div>
????????</div>
????</div>
????
????<script>
????????// Script code for handling API calls and UI interactions
????????document.addEventListener('DOMContentLoaded', function() {
????????????// Load products on page load
????????????loadProducts(0);
????????????
????????????// Add product button click
????????????document.getElementById('addProductBtn').addEventListener('click', function() {
????????????????document.getElementById('modalTitle').textContent = 'Add Product';
????????????????document.getElementById('productId').value = '';
????????????????document.getElementById('productName').value = '';
????????????????document.getElementById('productPrice').value = '';
????????????????document.getElementById('productModal').classList.remove('hidden');
????????????});
????????????
????????????// Cancel button click
????????????document.getElementById('cancelBtn').addEventListener('click', function() {
????????????????document.getElementById('productModal').classList.add('hidden');
????????????});
????????????
????????????// Product form submission
????????????document.getElementById('productForm').addEventListener('submit', function(e) {
????????????????e.preventDefault();
????????????????
????????????????const productId = document.getElementById('productId').value;
????????????????const productName = document.getElementById('productName').value;
????????????????const productPrice = document.getElementById('productPrice').value;
????????????????
????????????????const productData = {
????????????????????name: productName,
????????????????????price: parseFloat(productPrice)
????????????????};
????????????????
????????????????if (productId) {
????????????????????// Update existing product
????????????????????fetch(`/api/products/${productId}`, {
????????????????????????method: 'PUT',
????????????????????????headers: {
????????????????????????????'Content-Type': 'application/json'
????????????????????????},
????????????????????????body: JSON.stringify(productData)
????????????????????})
????????????????????.then(response => response.json())
????????????????????.then(() => {
????????????????????????document.getElementById('productModal').classList.add('hidden');
????????????????????????loadProducts(currentPage);
????????????????????})
????????????????????.catch(error => console.error('Error:', error));
????????????????} else {
????????????????????// Create new product
????????????????????fetch('/api/products', {
????????????????????????method: 'POST',
????????????????????????headers: {
????????????????????????????'Content-Type': 'application/json'
????????????????????????},
????????????????????????body: JSON.stringify(productData)
????????????????????})
????????????????????.then(response => response.json())
????????????????????.then(() => {
????????????????????????document.getElementById('productModal').classList.add('hidden');
????????????????????????loadProducts(0);
????????????????????})
????????????????????.catch(error => console.error('Error:', error));
????????????????}
????????????});
????????});
????????
????????let currentPage = 0;
????????
????????function loadProducts(page) {
????????????currentPage = page;
????????????
????????????fetch(`/api/products?page=${page}`)
????????????????.then(response => response.json())
????????????????.then(data => {
????????????????????const productsTableBody = document.getElementById('productsTableBody');
????????????????????productsTableBody.innerHTML = '';
????????????????????
????????????????????data.content.forEach(product => {
????????????????????????const row = document.createElement('tr');
????????????????????????row.className = 'border-b border-gray-200 hover:bg-gray-100';
????????????????????????
????????????????????????row.innerHTML = `
????????????????????????????<td class="py-3 px-6">${product.id}</td>
????????????????????????????<td class="py-3 px-6">${product.name}</td>
????????????????????????????<td class="py-3 px-6">$${product.price.toFixed(2)}</td>
????????????????????????????<td class="py-3 px-6">
????????????????????????????????<button οnclick="editProduct(${product.id})" class="text-blue-500 hover:text-blue-700 mr-2">
????????????????????????????????????<i class="fa fa-pencil"></i>
????????????????????????????????</button>
????????????????????????????????<button οnclick="deleteProduct(${product.id})" class="text-red-500 hover:text-red-700">
????????????????????????????????????<i class="fa fa-trash"></i>
????????????????????????????????</button>
????????????????????????????</td>
????????????????????????`;
????????????????????????
????????????????????????productsTableBody.appendChild(row);
????????????????????});
????????????????????
????????????????????// Update pagination
????????????????????updatePagination(data);
????????????????})
????????????????.catch(error => console.error('Error:', error));
????????}
????????
????????function editProduct(id) {
????????????fetch(`/api/products/${id}`)
????????????????.then(response => response.json())
????????????????.then(product => {
????????????????????document.getElementById('modalTitle').textContent = 'Edit Product';
????????????????????document.getElementById('productId').value = product.id;
????????????????????document.getElementById('productName').value = product.name;
????????????????????document.getElementById('productPrice').value = product.price;
????????????????????document.getElementById('productModal').classList.remove('hidden');
????????????????})
????????????????.catch(error => console.error('Error:', error));
????????}
????????
????????function deleteProduct(id) {
????????????if (confirm('Are you sure you want to delete this product?')) {
????????????????fetch(`/api/products/${id}`, {
????????????????????method: 'DELETE'
????????????????})
????????????????.then(() => {
????????????????????loadProducts(currentPage);
????????????????})
????????????????.catch(error => console.error('Error:', error));
????????????}
????????}
????????
????????function updatePagination(data) {
????????????const pagination = document.getElementById('pagination');
????????????pagination.innerHTML = '';
????????????
????????????const prevButton = document.createElement('button');
????????????prevButton.className = 'px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded';
????????????prevButton.disabled = !data.first;
????????????prevButton.innerHTML = '<i class="fa fa-chevron-left"></i> Previous';
????????????prevButton.onclick = () => loadProducts(currentPage - 1);
????????????pagination.appendChild(prevButton);
????????????
????????????const pageInfo = document.createElement('span');
????????????pageInfo.className = 'px-4';
????????????pageInfo.textContent = `Page ${data.number + 1} of ${data.totalPages}`;
????????????pagination.appendChild(pageInfo);
????????????
????????????const nextButton = document.createElement('button');
????????????nextButton.className = 'px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded';
????????????nextButton.disabled = !data.last;
????????????nextButton.innerHTML = 'Next <i class="fa fa-chevron-right"></i>';
????????????nextButton.onclick = () => loadProducts(currentPage + 1);
????????????pagination.appendChild(nextButton);
????????}
</script>
</body>
</html>