spring -MVC-02

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 ModelAndViewlistProducts() {

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>

相关推荐
缺点内向5 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅6 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看7 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程7 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t7 小时前
ZIP工具类
java·zip
lang201509288 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan9 小时前
第10章 Maven
java·maven
百锦再9 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说9 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多9 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring