当前位置: 首页 > news >正文

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 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>

http://www.xdnf.cn/news/497503.html

相关文章:

  • DeepSeek赋能电商,智能客服机器人破解大型活动人力困境
  • 数组集合互转问题
  • Ubuntu 安装 squid
  • 服装零售逆势密码:从4月英国7%增长看季节性消费新模型
  • 中国30米年度土地覆盖数据集及其动态变化(1985-2022年)
  • 一个指令,让任意 AI 快速生成思维导图
  • Unity序列化字段、单例模式(Singleton Pattern)
  • 通俗版解释CPU、核心、进程、线程、协程的定义及关系
  • 动态规划-64.最小路径和-力扣(LetCode)
  • c#车检车构客户管理系统软件车辆年审短信提醒软件
  • 系统架构设计(九):分布式架构与微服务
  • pytorch小记(二十二):全面解读 PyTorch 的 `torch.cumprod`——累积乘积详解与实战示例
  • Java求职面试:从核心技术到大数据与AI的场景应用
  • [Android] 安卓彩蛋:Easter Eggs v3.4.0
  • 第五项修炼:打造学习型组织
  • 前端基础之CSS
  • 大语言模型 11 - 从0开始训练GPT 0.25B参数量 MiniMind2 准备数据与训练模型 DPO直接偏好优化
  • 【诊所电子处方专用软件】佳易王个体诊所门诊电子处方开单管理系统:零售药店电子处方服务系统#操作简单#诊所软件教程#药房划价
  • Java 快速转 C# 教程
  • 30、WebAssembly:古代魔法——React 19 性能优化
  • 手撕四种常用设计模式(工厂,策略,代理,单例)
  • 设计模式Java
  • IDEA反斜杠路径不会显示JUnit运行的工作目录配置问题
  • 信奥赛-刷题笔记-栈篇-T2-P1981表达式求值0517
  • 在Maven中替换文件内容的插件和方法
  • 防范Java应用中的恶意文件上传:确保服务器的安全性
  • 【机器人】复现 WMNav 具身导航 | 将VLM集成到世界模型中
  • 结构化思考力_第一章_明确理念打基础
  • 西门子 Teamcenter13 Eclipse RCP 开发 1.2 工具栏 开关按钮
  • WPS JS宏实现去掉文档中的所有空行