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/[email protected]/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>

相关推荐
CN.LG6 分钟前
Java 乘号来重复字符串的功能
java·开发语言
萌新下岸多多关照11 分钟前
Java中synchronized 关键字
java·开发语言
中国lanwp13 分钟前
使用Maven部署WebLogic应用
java·maven
开开心心就好23 分钟前
Word图片格式调整与转换工具
java·javascript·spring·eclipse·pdf·word·excel
CGG921 小时前
【单例模式】
android·java·单例模式
苦学编程的谢1 小时前
多线程代码案例-1 单例模式
java·开发语言·单例模式
yaoxin5211231 小时前
80. Java 枚举类 - 使用枚举实现单例模式
java·开发语言·单例模式
夏季疯1 小时前
学习笔记:黑马程序员JavaWeb开发教程(2025.4.7)
java·笔记·学习
卡戎-caryon2 小时前
【C++】15.并发支持库
java·linux·开发语言·c++·多线程
头发那是一根不剩了3 小时前
怎么用idea分析hprof文件定位JVM内存问题
java·jvm