Spring MVC笔记

发布于:2025-03-07 ⋅ 阅读:(177) ⋅ 点赞:(0)

01 什么是Spring MVC

Spring MVC 是 Spring 框架中的一个核心模块,专门用于构建 Web 应用程序。它基于经典的 MVC 设计模式(Model-View-Controller),但通过 Spring 的特性(如依赖注入、注解驱动)大幅简化了开发流程。


Spring MVC 是什么?

  1. 本质
    一个基于 Java 的 Web 框架,帮助开发者快速、结构化地开发动态网站或 RESTful API。
  2. 核心思想
    将应用程序拆分为 模型(Model)、视图(View)、控制器(Controller) 三个部分,实现职责分离,让代码更易维护和扩展。

Spring MVC 的作用

1. 处理用户请求和响应

• 用户通过浏览器发送请求(如点击链接、提交表单),Spring MVC 的控制器(Controller)接收请求,处理业务逻辑,最终返回响应(如 HTML 页面、JSON 数据)。

2. 解耦代码,分工协作

Model(模型):负责数据和业务逻辑(如数据库操作)。
View(视图):负责展示数据(如 HTML、JSP、Thymeleaf 模板)。
Controller(控制器):负责协调用户请求、调用模型、返回视图。
• 三者独立开发,修改某一层不会影响其他层。

3. 简化传统 Servlet 开发

• 传统 Servlet 需要手动处理 HTTP 请求参数、响应输出等底层细节,代码臃肿。
• Spring MVC 通过 注解(如 @RequestMapping自动绑定 机制,让开发者专注业务逻辑。
• 例如,直接通过注解将请求参数绑定到 Java 对象:

 @PostMapping("/user")
 public String createUser(User user) {  // 自动将表单参数封装到User对象
     userService.save(user);
     return "success";
 }
4. 灵活适配多种技术

视图技术:支持 JSP、Thymeleaf、FreeMarker 等模板引擎,甚至直接返回 JSON(适合前后端分离)。
数据交互:轻松处理 JSON、XML 等数据格式(配合 @RestController)。
整合其他框架:无缝集成 Spring Security(安全)、Spring Data(数据库)等模块。

5. 强大的扩展性

• 通过拦截器(Interceptor)、全局异常处理(@ControllerAdvice)等机制,可以统一处理日志、权限、异常等问题。
• 例如,全局拦截未登录用户:

 public class AuthInterceptor implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
         if (user未登录) {
             response.sendRedirect("/login");
             return false;
         }
         return true;
     }
 }

02 创建Spring MVC项目

以下是两种常用的 Spring MVC 项目搭建方式(传统 XML 配置Spring Boot 快速搭建):


一、传统方式:基于 Maven + XML 配置(适合学习底层原理)

1. 创建 Maven 项目
2. 添加依赖(pom.xml
<!-- Spring MVC 核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.29</version>
</dependency>

<!-- Servlet API -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

<!-- JSP 支持 -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>
3. 配置 web.xml(初始化 DispatcherServlet)
<web-app>
    <!-- 配置前端控制器 DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 映射所有请求到 DispatcherServlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
4. 创建 Spring MVC 配置文件(/WEB-INF/spring-mvc.xml
<!-- 开启注解驱动 -->
<mvc:annotation-driven/>

<!-- 配置视图解析器(JSP) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- 扫描 Controller 包 -->
<context:component-scan base-package="com.example.controller"/>
5. 创建 Controller 和 JSP 视图
// com.example.controller.HomeController.java
@Controller
public class HomeController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Spring MVC!");
        return "hello"; // 对应 /WEB-INF/views/hello.jsp
    }
}
<%-- /WEB-INF/views/hello.jsp --%>
<html>
<body>
    <h1>${message}</h1>
</body>
</html>
6. 部署到 Tomcat

• 将项目打包为 WAR 文件,部署到 Tomcat 服务器,访问 http://localhost:8080/项目名/hello


二、快速方式:基于 Spring Boot

1. 使用 Spring Initializr 创建项目

• 访问 https://start.spring.io,选择:
依赖Spring Web(已包含 Spring MVC)、Thymeleaf(模板引擎)。
打包方式:JAR(内嵌 Tomcat,无需手动部署)。

2. 项目结构
src/
  main/
    java/
      com.example.demo/
        DemoApplication.java  // 启动类
        controller/
          HomeController.java
    resources/
      templates/  // 存放视图(如HTML)
      static/     // 存放静态资源(CSS/JS)
3. 编写 Controller
@Controller
public class HomeController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Spring Boot MVC!");
        return "hello"; // 对应 resources/templates/hello.html
    }
}
4. 创建视图(Thymeleaf)
<!-- resources/templates/hello.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <h1 th:text="${message}"></h1>
</body>
</html>
5. 运行项目

• 直接运行 DemoApplication.javamain 方法,访问 http://localhost:8080/hello


三、关键配置说明

  1. 视图解析器
    • 传统方式需手动配置 JSP 路径(如 /WEB-INF/views/*.jsp)。
    • Spring Boot 默认使用 templates 目录存放视图(需配合模板引擎如 Thymeleaf)。

  2. 静态资源访问
    • Spring Boot 默认将 static 目录下的文件映射为静态资源(如 http://localhost:8080/css/style.css)。
    • 传统方式需在 XML 中配置 <mvc:resources location="/static/" mapping="/static/**"/>


四、常见问题

  1. 404 错误
    • 检查 Controller 是否被扫描(@ComponentScan 包路径是否正确)。
    • 视图文件是否放在正确目录(如 templatesWEB-INF/views)。

  2. 依赖冲突
    • 确保 Spring 版本与依赖库兼容(推荐使用 Spring Boot 自动管理版本)。


03 Spring MVC执行原理

Spring MVC 的执行原理基于前端控制器模式,其核心是 DispatcherServlet,负责协调各组件处理请求。


1. 请求接收

用户发起请求:客户端(浏览器)发送HTTP请求至Web应用。
前端控制器接管:请求首先被 DispatcherServlet(配置在 web.xml 中)拦截,作为统一入口处理所有请求。


2. 处理器映射(Handler Mapping)

查找处理器DispatcherServlet 通过 HandlerMapping 根据请求URL(如 /hello)找到对应的处理器(Handler),通常是@Controller 中标注 @RequestMapping 的方法。
返回处理器链:可能包含拦截器(Interceptor)和具体的控制器方法。


3. 处理器适配器(Handler Adapter)

执行处理器HandlerAdapter 调用具体的处理器方法(如 @GetMapping 方法),处理不同形式的控制器(如基于注解或实现 Controller 接口)。
参数解析与绑定:方法参数通过 ArgumentResolver 解析(如请求参数、模型、@PathVariable 等)。
数据转换/验证:使用 ConverterValidator 进行类型转换和数据校验。


4. 控制器处理

业务逻辑执行:控制器方法调用Service层处理业务,返回结果(如 String 视图名、ModelAndView@ResponseBody 数据)。
模型数据填充:将数据存储在 Model 对象中,供视图渲染使用。


5. 视图解析(View Resolution)

解析视图名ViewResolver 根据控制器返回的视图名(如 "home")解析为具体的 View 对象(如JSP、Thymeleaf模板)。
示例配置InternalResourceViewResolver 可能将 "home" 映射到 /WEB-INF/views/home.jsp


6. 视图渲染

模型数据传递DispatcherServlet 将模型数据传递给 View 对象。
生成响应内容:视图使用模板引擎(如JSP、Freemarker)渲染HTML,写入HTTP响应。


7. 返回响应

响应客户端:渲染后的内容通过 DispatcherServlet 返回给客户端,完成请求-响应周期。


关键组件与扩展点

拦截器(Interceptor):在请求处理前后执行逻辑(如权限检查、日志记录),通过实现 HandlerInterceptor 接口配置。
异常处理@ExceptionHandlerHandlerExceptionResolver 统一处理控制器抛出的异常。
文件上传MultipartResolver 解析 multipart 请求(如文件上传)。


执行流程图示

客户端 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ModelAndView → ViewResolver → View → 响应

示例配置

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    // 视图解析器
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    // 静态资源处理
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Spring MVC!");
        return "hello"; // 对应 /WEB-INF/views/hello.jsp
    }
}

04 RequestMapping

在 Spring MVC 中,@RequestMapping 是一个核心注解,用于将 HTTP 请求映射到特定的控制器方法。它提供了灵活的配置选项,支持定义请求路径、HTTP 方法、请求参数、请求头等条件。以下是其详细用法和功能说明:


1. 基本作用

定义请求映射规则:将 URL 请求路径与控制器方法绑定。
支持多种请求类型:GET、POST、PUT、DELETE 等。
支持路径变量、参数过滤、请求头过滤等高级条件。


2. 可用位置

类级别:定义共享的根路径(所有方法映射路径会继承该路径)。
方法级别:定义具体方法的映射规则。

示例
@Controller
@RequestMapping("/user") // 类级别路径:所有方法映射路径以 /user 开头
public class UserController {

    // 实际路径:/user/profile
    @RequestMapping("/profile")
    public String profile() { ... }

    // 实际路径:/user/list
    @RequestMapping("/list")
    public String list() { ... }
}

3. 核心属性

(1) valuepath

定义请求路径:支持字符串或字符串数组(允许多路径映射)。
支持路径变量{variable})和通配符(*, **)。

// 单路径
@RequestMapping("/detail")

// 多路径映射
@RequestMapping({"/info", "/detail"})

// 路径变量
@RequestMapping("/user/{id}")
public String getUser(@PathVariable Long id) { ... }

// 通配符匹配(如 /user/2023/order)
@RequestMapping("/user/*/order")

(2) method

指定允许的 HTTP 方法:如 RequestMethod.GET, RequestMethod.POST
默认支持所有 HTTP 方法

// 只允许 GET 请求
@RequestMapping(value = "/list", method = RequestMethod.GET)

// 允许多个 HTTP 方法
@RequestMapping(value = "/save", method = {RequestMethod.POST, RequestMethod.PUT})

(3) params

过滤请求参数:要求请求必须包含指定参数,或参数满足特定条件。
• 格式:param(必须存在)、!param(必须不存在)、param=value(值匹配)。

// 要求请求必须包含 id 参数
@RequestMapping(params = "id")

// 要求必须包含 id 且值为 100
@RequestMapping(params = "id=100")

// 要求不能包含 debug 参数
@RequestMapping(params = "!debug")

(4) headers

过滤请求头:要求请求头满足特定条件。
• 格式与 params 类似。

// 要求请求头包含 Content-Type=application/json
@RequestMapping(headers = "Content-Type=application/json")

// 要求请求头必须包含 Auth-Token
@RequestMapping(headers = "Auth-Token")

(5) consumes

指定请求的 Content-Type:要求请求体的媒体类型匹配。

// 仅处理 Content-Type 为 application/json 的请求
@RequestMapping(consumes = "application/json")

(6) produces

指定响应的 Content-Type:设置响应体的媒体类型。

// 返回 JSON 数据
@RequestMapping(produces = "application/json")

4. 组合注解

为了简化代码,Spring 提供了基于 @RequestMapping 的快捷组合注解:

组合注解 等效写法 作用
@GetMapping @RequestMapping(method = RequestMethod.GET) 处理 GET 请求
@PostMapping @RequestMapping(method = RequestMethod.POST) 处理 POST 请求
@PutMapping @RequestMapping(method = RequestMethod.PUT) 处理 PUT 请求
@DeleteMapping @RequestMapping(method = RequestMethod.DELETE) 处理 DELETE 请求
@PatchMapping @RequestMapping(method = RequestMethod.PATCH) 处理 PATCH 请求
示例
@GetMapping("/user/{id}") // 等价于 @RequestMapping(value="/user/{id}", method=RequestMethod.GET)
public String getUser(@PathVariable Long id) { ... }

5. 路径匹配规则

(1) 路径变量({variable}

• 使用 @PathVariable 获取路径中的动态值。

// 匹配路径如 /user/123
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long userId) { ... }

(2) 通配符

?:匹配单个字符(如 /user/2023? 匹配 /user/2023a)。
*:匹配同一层级的任意字符(如 /user/*/order 匹配 /user/123/order)。
**:匹配多层路径(如 /user/** 匹配 /user/123/order/456)。


(3) 正则表达式

• 路径变量中可使用正则表达式限制格式。

// 限制 id 必须为数字
@GetMapping("/user/{id:\\d+}")
public String getUser(@PathVariable Long id) { ... }

6. 示例代码

(1) 完整控制器
@Controller
@RequestMapping("/product")
public class ProductController {

    // 匹配 GET /product/detail?id=100
    @GetMapping(value = "/detail", params = "id")
    public String detail(@RequestParam Long id, Model model) {
        model.addAttribute("product", productService.findById(id));
        return "product/detail";
    }

    // 匹配 POST 或 PUT /product/save
    @RequestMapping(value = "/save", method = {RequestMethod.POST, RequestMethod.PUT})
    public String saveProduct(@ModelAttribute Product product) {
        productService.save(product);
        return "redirect:/product/list";
    }

    // 路径变量 + 正则匹配(如 /product/category/electronics)
    @GetMapping("/category/{type:[a-z]+}")
    public String listByCategory(@PathVariable String type, Model model) {
        model.addAttribute("products", productService.findByCategory(type));
        return "product/list";
    }
}

05 Resful风格

RESTful(REpresentational State Transfer)是一种基于 HTTP 协议的 API 设计风格,强调以资源为中心,通过统一的接口和标准方法(GET/POST/PUT/DELETE 等)操作资源。


一、RESTful 的核心原则

  1. 资源(Resource)
    • 所有数据或服务抽象为资源(如用户、订单),通过 URI(统一资源标识符) 唯一标识。
    • 示例:/users(用户集合)、/users/1001(ID 为 1001 的用户)。

  2. 统一接口(Uniform Interface)
    • 使用标准的 HTTP 方法 操作资源:
    GET:获取资源
    POST:创建资源
    PUT:更新资源(全量替换)
    PATCH:部分更新资源
    DELETE:删除资源

  3. 无状态(Stateless)
    • 服务端不保存客户端状态,每个请求必须包含所有必要信息。

  4. 表述(Representation)
    • 资源的表现形式(如 JSON、XML),客户端通过 HTTP 头(Accept/Content-Type)协商格式。

  5. 超媒体驱动(HATEOAS)
    • 响应中包含相关资源的链接,客户端通过链接导航(可选约束,实际使用较少)。


二、RESTful API 设计规范

1. URI 设计规范

使用名词,而非动词:URI 表示资源,动作由 HTTP 方法表达。

❌ 非 RESTful: /getUser?id=1001  
✅ RESTful: GET /users/1001

层级关系使用 / 分隔

GET /users/1001/orders  # 获取用户 1001 的所有订单

复数形式命名集合

GET /users       # 用户集合
POST /users      # 创建用户

过滤、排序、分页通过查询参数实现

GET /users?page=2&size=10&sort=name,asc  # 分页排序
GET /users?name=John&age=30              # 过滤
2. HTTP 方法使用规范
HTTP 方法 操作类型 幂等性 示例
GET 查询资源 GET /users/1001
POST 创建资源 POST /users
PUT 全量更新 PUT /users/1001
PATCH 部分更新 PATCH /users/1001
DELETE 删除资源 DELETE /users/1001
3. 状态码(Status Code)

2xx:成功
200 OK:常规成功
201 Created:资源创建成功
204 No Content:成功但无返回体(如删除操作)

4xx:客户端错误
400 Bad Request:请求参数错误
401 Unauthorized:未认证
403 Forbidden:无权限
404 Not Found:资源不存在

5xx:服务端错误
500 Internal Server Error:服务器内部错误

4. 数据格式

• 使用 JSON 作为主流数据交换格式。
• 请求头指定 Content-Type: application/json
• 响应头包含 Content-Type: application/json


三、在 Spring 中实现 RESTful API

1. 使用 @RestController 注解

• 替代 @Controller + @ResponseBody,直接返回 JSON 数据。

@RestController
@RequestMapping("/api/users")
public class UserController {
    // ...
}
2. 映射 HTTP 方法

• 使用组合注解:@GetMapping, @PostMapping, @PutMapping, @DeleteMapping

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... }

@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) { ... }
3. 处理请求和响应

路径变量@PathVariable

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... }

请求体@RequestBody

@PostMapping
public User createUser(@RequestBody User user) { ... }

返回 ResponseEntity:自定义状态码和响应头

@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
    User savedUser = userService.save(user);
    return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
4. 统一异常处理

• 使用 @ExceptionHandler@ControllerAdvice 返回标准错误响应。

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
        ErrorResponse error = new ErrorResponse(404, e.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
}

四、完整示例

1. 实体类
public class User {
    private Long id;
    private String name;
    private String email;
    // Getters and Setters
}
2. 控制器
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    // 获取所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    // 创建用户
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }

    // 获取单个用户
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));
    }

    // 更新用户(全量)
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.update(user);
    }

    // 删除用户
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
    }
}
3. 请求与响应示例

请求POST /api/users

{
  "name": "Alice",
  "email": "alice@example.com"
}

响应(状态码 201):

{
  "id": 1001,
  "name": "Alice",
  "email": "alice@example.com"
}

06 重定向和转发

在 Spring MVC 中,重定向(Redirect)转发(Forward) 是两种不同的请求跳转方式,理解它们的区别和适用场景对开发至关重要。以下是详细对比及具体用法:


一、核心区别

特性 转发(Forward) 重定向(Redirect)
发起方 服务端内部跳转(客户端无感知) 服务端通知客户端重新发起请求
请求次数 1 次请求,1 次响应 2 次请求,2 次响应
地址栏变化 地址栏 URL 不变 地址栏 URL 变为目标地址
数据共享 共享同一 Request 作用域(request.setAttribute 不共享 Request 作用域,需通过 URL 参数或 Session 传递
性能 更高(无额外请求) 较低(多一次网络往返)
使用场景 服务器内部资源跳转(如 JSP 间共享数据) 跨应用跳转、防止表单重复提交(POST-REDIRECT-GET)

二、在 Spring MVC 中的实现方式

1. 转发(Forward)

原理:服务端内部将请求转发到另一个资源(如 JSP、控制器方法)。
语法:在返回值前加 forward: 前缀。
示例

@GetMapping("/page1")
public String forwardDemo() {
    // 转发到 /page2(客户端 URL 仍为 /page1)
    return "forward:/page2";
}

@GetMapping("/page2")
public String page2() {
    return "page2"; // 视图名对应视图解析器路径
}

数据传递:通过 ModelHttpServletRequest 传递数据。

@GetMapping("/forward")
public String forward(Model model) {
    model.addAttribute("message", "Hello from forward!");
    return "forward:/targetPage";
}

2. 重定向(Redirect)

原理:服务端返回 302 状态码和 Location 头,客户端自动发起新请求。
语法:在返回值前加 redirect: 前缀。
示例

@PostMapping("/submit")
public String submitForm(FormData formData, RedirectAttributes redirectAttributes) {
    // 处理表单数据...
    // 重定向到 /result,传递参数
    redirectAttributes.addAttribute("status", "success");
    redirectAttributes.addFlashAttribute("message", "操作成功!");
    return "redirect:/result";
}

@GetMapping("/result")
public String resultPage(@RequestParam String status, Model model) {
    // 接收 URL 参数和 Flash 属性
    return "result";
}

数据传递
URL 参数RedirectAttributes.addAttribute("key", value) → 参数暴露在 URL 中。
Flash 属性RedirectAttributes.addFlashAttribute("key", value) → 数据暂存 Session,一次请求后自动删除。


三、适用场景

1. 转发(Forward)

共享请求数据:需要在多个视图或控制器间传递数据(如 JSP 到 JSP)。
隐藏实际资源路径:保护内部资源路径,客户端无法直接访问。
统一预处理:在转发前进行权限验证、日志记录等。

2. 重定向(Redirect)

防止表单重复提交:提交后重定向到结果页(POST-REDIRECT-GET 模式)。
跨应用跳转:跳转到外部网站或其他服务。
切换上下文路径:如从 HTTP 跳转到 HTTPS,或更换域名。


五、完整代码示例

1. 转发示例
@Controller
public class ForwardController {

    @GetMapping("/source")
    public String sourcePage(Model model) {
        model.addAttribute("data", "来自源页面的数据");
        return "forward:/target"; // 转发到 /target
    }

    @GetMapping("/target")
    public String targetPage(Model model) {
        // 可以访问 model 中的 data
        return "target-page"; // 视图模板路径
    }
}
2. 重定向示例
@Controller
public class RedirectController {

    @PostMapping("/save")
    public String saveData(User user, RedirectAttributes redirectAttributes) {
        userService.save(user);
        // 添加 URL 参数(暴露在地址栏)
        redirectAttributes.addAttribute("userId", user.getId());
        // 添加 Flash 属性(安全传递敏感数据)
        redirectAttributes.addFlashAttribute("message", "用户保存成功!");
        return "redirect:/user/detail";
    }

    @GetMapping("/user/detail")
    public String userDetail(@RequestParam Long userId, Model model) {
        User user = userService.findById(userId);
        model.addAttribute("user", user);
        return "user-detail";
    }
}

六、总结

转发(Forward):适合服务器内部资源跳转,共享请求数据,性能更高。
重定向(Redirect):适合客户端跳转、防止重复提交,需注意数据传递方式。
开发建议
• 表单提交后 必须使用重定向 避免重复提交。
• 优先使用 RedirectAttributes 传递数据,避免 URL 参数暴露敏感信息。

07 接收请求参数和数据回显

一、接收请求参数

1. 基本参数接收

@RequestParam:获取 URL 参数或表单字段(默认必传,可设 required=false)。

@GetMapping("/user")
public String getUser(@RequestParam("id") Long userId, 
                     @RequestParam(value = "name", defaultValue = "Guest") String userName) {
    // URL: /user?id=1001&name=Alice
    // userId=1001, userName=Alice(若未传name,默认为"Guest")
    return "user/detail";
}

@PathVariable:获取 URL 路径变量。

@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long userId) {
    // URL: /user/1001 → userId=1001
    return "user/detail";
}

2. 对象自动绑定

@ModelAttribute:自动将表单字段绑定到对象(支持级联属性)。

@PostMapping("/save")
public String saveUser(@ModelAttribute User user) {
    // 表单字段 name 和 email 自动绑定到 User 对象
    userService.save(user);
    return "redirect:/user/list";
}

无需注解:直接声明对象参数,Spring 自动绑定。

@PostMapping("/save")
public String saveUser(User user) { // 效果同上
    userService.save(user);
    return "redirect:/user/list";
}

3. 接收 JSON 数据

@RequestBody:将请求体中的 JSON 反序列化为对象。

@PostMapping("/api/user")
@ResponseBody
public ResponseEntity<User> createUser(@RequestBody User user) {
    User savedUser = userService.save(user);
    return ResponseEntity.ok(savedUser);
}

4. 接收原生对象

• 直接使用 HttpServletRequestHttpSession 等原生对象。

@GetMapping("/info")
public String getInfo(HttpServletRequest request, HttpSession session) {
    String param = request.getParameter("param");
    session.setAttribute("key", "value");
    return "info";
}

二、数据回显(传递到视图)

1. 使用 ModelModelMap

• 添加数据到模型,供视图(如 JSP、Thymeleaf)渲染。

@GetMapping("/user/edit")
public String editUser(@RequestParam Long id, Model model) {
    User user = userService.findById(id);
    model.addAttribute("user", user); // 回显到表单
    return "user/edit";
}

2. 使用 ModelAndView

• 同时返回视图名和数据。

@GetMapping("/user/detail")
public ModelAndView userDetail(@RequestParam Long id) {
    ModelAndView mav = new ModelAndView("user/detail");
    User user = userService.findById(id);
    mav.addObject("user", user);
    return mav;
}

08 JSON

在 Spring 生态中,JSON(JavaScript Object Notation) 是主流的轻量级数据交换格式,而 Jackson 是处理 JSON 序列化与反序列化的核心库。


一、JSON 简介

1. 什么是 JSON?

轻量级数据格式:以键值对(key: value)形式组织数据,易读且兼容性强。
数据结构
对象{ "key": value }
数组[ value1, value2 ]
值类型:字符串、数字、布尔值、null、对象、数组。

2. 应用场景

前后端数据交互:API 请求和响应。
配置文件(如 package.json)。
NoSQL 数据库(如 MongoDB)存储格式。

3. 对比 XML
特性 JSON XML
可读性 高(结构简洁) 较低(标签冗余)
数据体积 更小 更大
解析速度 更快 较慢
扩展性 弱(无命名空间、属性等概念) 强(支持复杂结构)

二、Jackson 核心功能

Jackson 是 Java 生态中最流行的 JSON 处理库,提供以下能力:

  1. 序列化:将 Java 对象转换为 JSON 字符串。
  2. 反序列化:将 JSON 字符串解析为 Java 对象。
  3. 数据绑定:支持注解驱动配置。
  4. 流式 API:高性能处理大 JSON 数据。

三、Spring 中集成 Jackson

Spring MVC 默认通过 MappingJackson2HttpMessageConverter 集成 Jackson,自动处理 JSON 转换。

1. 添加依赖
<!-- Maven 依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
2. 启用 JSON 支持

注解驱动:使用 @RestController@ResponseBody

@RestController // 等效于 @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

四、Jackson 核心注解

通过注解控制序列化/反序列化行为:

1. 字段映射

@JsonProperty:指定 JSON 字段名。

public class User {
    @JsonProperty("user_name")
    private String name;
}
// 序列化为 { "user_name": "Alice" }

@JsonIgnore:忽略字段。

public class User {
    @JsonIgnore
    private String password;
}
2. 日期格式化

@JsonFormat:自定义日期格式。

public class Order {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
}
// 序列化为 "2023-10-01 14:30:00"
3. 空值处理

@JsonInclude:忽略空值字段。

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    private String email; // 若 email 为 null,不序列化
}

五、序列化与反序列化示例

1. Java 对象转 JSON(序列化)
ObjectMapper mapper = new ObjectMapper();
User user = new User("Alice", 30);
String json = mapper.writeValueAsString(user);
// 输出:{"name":"Alice","age":30}
2. JSON 转 Java 对象(反序列化)
String json = "{\"name\":\"Alice\",\"age\":30}";
User user = mapper.readValue(json, User.class);

六、Spring 中高级配置

1. 自定义 ObjectMapper
@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper;
    }
}

七、常见问题与解决方案

1. 日期格式不生效

问题:日期字段未按预期格式序列化。
解决:检查 @JsonFormattimezone 配置或全局 ObjectMapper 日期格式。

2. 字段丢失

问题:JSON 中缺少字段导致反序列化失败。
解决:配置 ObjectMapper 忽略未知字段:

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

09 Fastjson

在 Java 开发中,Fastjson 是阿里巴巴开源的高性能 JSON 库,广泛用于序列化(Java 对象转 JSON)和反序列化(JSON 转 Java 对象)。与 Jackson 相比,Fastjson 在部分场景下性能更优,且 API 设计更简单易用。以下是其核心特性、使用方式及注意事项:


一、Fastjson 核心特性

  1. 高性能
    • 序列化/反序列化速度极快,尤其在大数据量场景下表现优异。
    • 依赖 ASM 字节码技术优化(无需反射),性能优于 Jackson 和 Gson。

  2. 功能丰富
    • 支持复杂对象(泛型、嵌套对象、循环引用)。
    • 支持 Java 8 时间 API(如 LocalDateTime)。
    • 支持自定义序列化规则和过滤器。

  3. 简单 API
    • 核心类 JSON 提供静态方法(如 JSON.toJSONString()JSON.parseObject())。

  4. 注解驱动
    • 通过 @JSONField 注解灵活控制字段映射和格式。


二、与 Jackson 对比

特性 Fastjson Jackson
性能 更高(尤其序列化) 较高
安全性 历史漏洞较多(需使用最新版本) 安全性较好
API 简洁性 极简(静态方法) 稍复杂(需 ObjectMapper 实例)
社区生态 国内流行,文档丰富 国际主流,生态更成熟
维护状态 阿里巴巴维护,更新频繁 长期稳定更新

三、Spring 中集成 Fastjson

Spring Boot 默认使用 Jackson,若需替换为 Fastjson,需手动配置消息转换器。

1. 添加依赖
<!-- Maven 依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.34</version> <!-- 使用最新版本(避免安全漏洞) -->
</dependency>
2. 配置 Fastjson 消息转换器
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 创建 Fastjson 消息转换器
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();

        // 配置序列化规则
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(
            SerializerFeature.PrettyFormat,       // 格式化输出
            SerializerFeature.WriteMapNullValue,  // 输出空值字段
            SerializerFeature.WriteDateUseDateFormat // 日期格式化
        );
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        converter.setFastJsonConfig(config);

        // 处理中文乱码
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        converter.setSupportedMediaTypes(mediaTypes);

        // 替换默认 Jackson 转换器
        converters.add(0, converter);
    }
}

四、核心 API 使用示例

1. 序列化(Java → JSON)
User user = new User("Alice", 25, LocalDateTime.now());

// 简单序列化
String json = JSON.toJSONString(user); 
// 输出:{"age":25,"name":"Alice","createTime":"2023-10-01 14:30:00"}

// 带格式化的序列化
String prettyJson = JSON.toJSONString(user, SerializerFeature.PrettyFormat);
2. 反序列化(JSON → Java)
String json = "{\"name\":\"Bob\",\"age\":30}";

// 基本反序列化
User user = JSON.parseObject(json, User.class);

// 处理泛型(如 List)
String listJson = "[{\"name\":\"Alice\"}, {\"name\":\"Bob\"}]";
List<User> users = JSON.parseArray(listJson, User.class);

五、注解 @JSONField

通过注解控制字段的序列化行为:

1. 字段重命名
public class User {
    @JSONField(name = "user_name")
    private String name; // 序列化为 "user_name"
}
2. 忽略字段
public class User {
    @JSONField(serialize = false)
    private String password; // 不序列化
}
3. 日期格式化
public class User {
    @JSONField(format = "yyyy-MM-dd")
    private LocalDate birthDate;
}
4. 顺序控制
@JSONType(orders = {"id", "name", "age"}) // 类级别定义字段顺序
public class User {
    private Long id;
    private String name;
    private Integer age;
}

10 ssm整合项目搭建

Spring MVC 图书管理系统开发流程与示例


一、项目目录结构(简化版)
src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           ├── controller/    # 控制器层
│   │           │   └── BookController.java
│   │           ├── dao/          # DAO层(MyBatis Mapper接口)
│   │           │   └── BookMapper.java
│   │           ├── service/      # 业务层
│   │           │   ├── BookService.java
│   │           │   └── impl/
│   │           │       └── BookServiceImpl.java
│   │           └── entity/       # 实体类
│   │               └── Book.java
│   └── resources/
│       ├── mapper/               # MyBatis Mapper XML
│       │   └── BookMapper.xml
│       ├── spring/              # Spring配置
│       │   ├── spring.xml
│       │   └── spring-mvc.xml
│       └── jdbc.properties      # 数据库配置
webapp/
├── WEB-INF/
│   └── views/                   # JSP视图
│       └── book/
│           └── list.jsp
└── index.jsp

二、开发流程与代码示例
1. 创建数据库表
CREATE TABLE book (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    author VARCHAR(50),
    price DECIMAL(10,2)
);

2. 实体类 Book.java(POJO层)
package com.example.entity;

public class Book {
    private Integer id;
    private String name;
    private String author;
    private Double price;
    
    // Getters and Setters
}

3. DAO层接口 BookMapper.java
package com.example.dao;

import com.example.entity.Book;
import java.util.List;

public interface BookMapper {
    List<Book> selectAll();
    int insert(Book book);
    int deleteById(Integer id);
}

4. MyBatis Mapper XML BookMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.BookMapper">
    <select id="selectAll" resultType="Book">
        SELECT id, name, author, price FROM book
    </select>
    
    <insert id="insert" parameterType="Book">
        INSERT INTO book(name, author, price)
        VALUES(#{name}, #{author}, #{price})
    </insert>

    <delete id="deleteById" parameterType="Integer">
        DELETE FROM book WHERE id = #{id}
    </delete>
</mapper>

5. Service层实现业务逻辑
// 接口 BookService.java
package com.example.service;
import com.example.entity.Book;
import java.util.List;

public interface BookService {
    List<Book> listBooks();
    void addBook(Book book);
    void deleteBook(Integer id);
}

// 实现类 BookServiceImpl.java
package com.example.service.impl;

import com.example.dao.BookMapper;
import com.example.entity.Book;
import com.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookMapper bookMapper;

    @Override
    public List<Book> listBooks() {
        return bookMapper.selectAll();
    }

    @Override
    @Transactional
    public void addBook(Book book) {
        bookMapper.insert(book);
    }

    @Override
    @Transactional
    public void deleteBook(Integer id) {
        bookMapper.deleteById(id);
    }
}

6. Controller层处理请求
package com.example.controller;

import com.example.entity.Book;
import com.example.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/book")
public class BookController {
    @Autowired
    private BookService bookService;

    // 查询所有书籍
    @GetMapping("/list")
    public String listBooks(Model model) {
        model.addAttribute("books", bookService.listBooks());
        return "book/list";
    }

    // 添加书籍(表单提交)
    @PostMapping("/add")
    public String addBook(Book book) {
        bookService.addBook(book);
        return "redirect:/book/list";
    }

    // 删除书籍
    @GetMapping("/delete/{id}")
    public String deleteBook(@PathVariable Integer id) {
        bookService.deleteBook(id);
        return "redirect:/book/list";
    }
}

7. 配置文件
(1) jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/book_db?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
(2) spring.xml(Spring核心配置)
<!-- 数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- MyBatis SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

<!-- Mapper接口扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.dao"/>
</bean>

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 开启注解扫描 -->
<context:component-scan base-package="com.example.service"/>
(3) spring-mvc.xml(Spring MVC配置)
<!-- 注解驱动 -->
<mvc:annotation-driven/>

<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- 扫描控制器 -->
<context:component-scan base-package="com.example.controller"/>

<!-- 静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/"/>

8. 视图层 list.jsp
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>图书列表</title>
</head>
<body>
    <h2>图书列表</h2>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>书名</th>
            <th>作者</th>
            <th>价格</th>
            <th>操作</th>
        </tr>
        <c:forEach items="${books}" var="book">
            <tr>
                <td>${book.id}</td>
                <td>${book.name}</td>
                <td>${book.author}</td>
                <td>${book.price}</td>
                <td>
                    <a href="/book/delete/${book.id}">删除</a>
                </td>
            </tr>
        </c:forEach>
    </table>

    <h3>添加新书</h3>
    <form action="/book/add" method="post">
        书名:<input type="text" name="name"><br>
        作者:<input type="text" name="author"><br>
        价格:<input type="number" step="0.01" name="price"><br>
        <button type="submit">提交</button>
    </form>
</body>
</html>

三、关键流程说明
  1. 请求处理流程

    浏览器请求 → DispatcherServlet → BookController → BookService → BookMapper → 数据库
    
  2. 分层职责
    DAO层:直接操作数据库,提供 insertdelete 等方法。
    Service层:处理业务逻辑(如事务管理 @Transactional)。
    Controller层:接收HTTP请求,返回视图或重定向。


四、启动与测试
  1. 部署到Tomcat,访问 http://localhost:8080/book/list
  2. 添加图书:填写表单提交,数据插入数据库。
  3. 删除图书:点击删除链接,触发 DELETE 操作。

11 整合SSM框架的配置文件

SSM 整合核心配置文件详解

以下是整合 Spring、Spring MVC 和 MyBatis(SSM)时 必须配置的核心内容,分步骤说明每个配置文件的作用和关键配置项。


一、项目结构
src/
├── main/
│   ├── java/
│   │   └── com/example/
│   │       ├── controller/   # Spring MVC 控制器
│   │       ├── service/      # 业务层接口和实现
│   │       ├── dao/          # MyBatis Mapper 接口
│   │       └── entity/       # 数据库实体类
│   └── resources/
│       ├── spring/
│       │   ├── spring.xml     # Spring 核心配置
│       │   └── spring-mvc.xml# Spring MVC 配置
│       ├── mapper/           # MyBatis Mapper XML 文件
│       └── jdbc.properties    # 数据库连接配置
webapp/
├── WEB-INF/
│   └── views/                # JSP 视图文件
└── index.jsp

二、Spring 核心配置 (spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 1. 加载数据库配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 2. 配置数据源(Druid) -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 连接池参数 -->
        <property name="initialSize" value="5"/>
        <property name="maxActive" value="20"/>
    </bean>

    <!-- 3. 配置 MyBatis 的 SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 绑定数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 指定 MyBatis Mapper XML 文件路径 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        <!-- 可选:MyBatis 全局配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- 4. 自动扫描 Mapper 接口 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.example.dao"/>
    </bean>

    <!-- 5. 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 6. 开启注解驱动的事务管理 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 7. 扫描 Service 层 -->
    <context:component-scan base-package="com.example.service"/>
</beans>
关键配置说明:
  1. <context:property-placeholder>
    加载 jdbc.properties 文件中的数据库连接参数。
    jdbc.driver: 数据库驱动类(如 com.mysql.cj.jdbc.Driver)。
    jdbc.url: 数据库连接 URL(如 jdbc:mysql://localhost:3306/test)。
    jdbc.username/jdbc.password: 数据库用户名和密码。

  2. DruidDataSource
    • 使用 Druid 连接池管理数据库连接。
    initialSize/maxActive: 连接池初始大小和最大连接数。

  3. SqlSessionFactoryBean
    • 核心类,用于创建 MyBatis 的 SqlSession
    mapperLocations: 指定 Mapper XML 文件的路径(支持通配符 *)。

  4. MapperScannerConfigurer
    • 自动扫描 DAO 层的 Mapper 接口,并注册为 Spring Bean。
    basePackage: Mapper 接口所在的包路径。

  5. DataSourceTransactionManager
    • 基于数据源的事务管理器,用于管理数据库事务。
    <tx:annotation-driven> 开启 @Transactional 注解支持。


三、Spring MVC 配置 (spring-mvc.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 1. 开启注解驱动(支持 @Controller、@RequestMapping 等) -->
    <mvc:annotation-driven/>

    <!-- 2. 静态资源处理(CSS/JS/图片) -->
    <mvc:resources mapping="/static/**" location="/static/"/>

    <!-- 3. 视图解析器(JSP) -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 4. 扫描 Controller 层 -->
    <context:component-scan base-package="com.example.controller"/>
</beans>
关键配置说明:
  1. <mvc:annotation-driven>
    • 启用 Spring MVC 的注解驱动功能(如 @Controller@RequestMapping)。
    • 自动注册 RequestMappingHandlerMappingRequestMappingHandlerAdapter

  2. <mvc:resources>
    • 处理静态资源请求(如 /static/css/style.css)。
    mapping: URL 映射规则。
    location: 静态资源在项目中的路径。

  3. InternalResourceViewResolver
    • 将逻辑视图名解析为具体的 JSP 文件路径。
    prefix: 视图文件的前缀路径(如 /WEB-INF/views/book/list.jsp)。
    suffix: 视图文件的后缀(如 .jsp)。


四、MyBatis 全局配置 (mybatis-config.xml,可选)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 1. 全局设置 -->
    <settings>
        <!-- 开启驼峰命名映射(数据库字段 user_name → Java 属性 userName) -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!-- 2. 实体类别名(简化 Mapper XML 中的类型书写) -->
    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>
</configuration>
关键配置说明:
  1. mapUnderscoreToCamelCase
    • 自动将数据库字段的下划线命名转换为 Java 属性的驼峰命名。

  2. typeAliases
    • 为实体类设置别名,例如 <select resultType="Book"> 代替全类名 com.example.entity.Book


五、数据库连接配置 (jdbc.properties)
# MySQL 驱动
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接 URL(注意时区设置)
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
注意事项:

serverTimezone=UTC: 解决 MySQL 8.0 时区问题。
useSSL=false: 禁用 SSL 连接(生产环境不建议)。


六、常见问题与解决
  1. 报错 NoSuchBeanDefinitionException
    原因: Spring 未扫描到 Service 或 Controller。
    解决: 检查 <context:component-scan>base-package 路径。

  2. 静态资源无法加载
    原因: 未配置 <mvc:resources> 或路径错误。
    解决: 确认静态资源目录与配置一致。

  3. 事务不生效
    原因: 未开启 <tx:annotation-driven> 或方法非 public
    解决: 确保 @Transactional 注解在 public 方法上。


七、总结

最少配置项:

  1. 数据源 (dataSource)
  2. MyBatis 的 SqlSessionFactory
  3. Mapper 接口扫描 (MapperScannerConfigurer)
  4. 事务管理器 (DataSourceTransactionManager)
  5. Spring MVC 注解驱动 (<mvc:annotation-driven>)
  6. 视图解析器 (InternalResourceViewResolver)

扩展建议:
• 添加拦截器 (HandlerInterceptor) 实现权限控制。
• 使用 @RestController 开发 RESTful API。
• 集成 Swagger 生成 API 文档。

12 Spring MVC 拦截器

一、拦截器的作用

拦截器(Interceptor)是 Spring MVC 提供的一种 请求预处理和后处理机制,允许在 控制器方法执行前后请求完成后的回调阶段 插入自定义逻辑。常用于以下场景:
权限验证(如登录检查)
日志记录(记录请求参数、执行时间)
性能监控(统计接口耗时)
全局数据处理(如设置请求/响应编码)


二、拦截器 vs. 过滤器
特性 拦截器(Interceptor) 过滤器(Filter)
依赖框架 Spring MVC 提供 Servlet 规范提供
作用范围 仅拦截 Spring MVC 处理的请求 拦截所有请求(包括静态资源)
访问对象 可获取 Handler(控制器方法)、ModelAndView 只能获取 Servlet API(Request/Response)
执行顺序 通过配置顺序控制 通过 @Orderweb.xml 配置顺序

三、拦截器的核心方法

实现 HandlerInterceptor 接口,重写以下三个方法:

1. preHandle():在控制器方法执行前调用

返回值
true:继续执行后续拦截器和控制器方法。
false:中断请求,直接返回响应。
典型用途:权限校验、请求参数预处理。

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    // 示例:检查用户是否登录
    if (request.getSession().getAttribute("user") == null) {
        response.sendRedirect("/login");
        return false;
    }
    return true;
}
2. postHandle():在控制器方法执行后、视图渲染前调用

可修改 ModelAndView:调整返回的模型数据或视图。
典型用途:向模型添加全局数据(如页面公共信息)。

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    if (modelAndView != null) {
        modelAndView.addObject("currentTime", System.currentTimeMillis());
    }
}
3. afterCompletion():在请求完成后调用(视图渲染完毕)

资源清理:关闭数据库连接、记录请求日志等。

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    // 示例:记录请求耗时
    long startTime = (Long) request.getAttribute("startTime");
    long cost = System.currentTimeMillis() - startTime;
    log.info("请求 {} 耗时 {}ms", request.getRequestURI(), cost);
}

四、配置拦截器

在 Spring MVC 配置文件(如 spring-mvc.xml)中注册拦截器,并指定拦截路径和排除路径。

1. 注册拦截器
<mvc:interceptors>
    <!-- 全局拦截器(对所有请求生效) -->
    <bean class="com.example.interceptor.LogInterceptor"/>

    <!-- 自定义拦截器(指定路径) -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**"/>      <!-- 拦截路径 -->
        <mvc:exclude-mapping path="/admin/login"/> <!-- 排除路径 -->
        <bean class="com.example.interceptor.AuthInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
2. 路径匹配规则

/**:匹配所有路径(包括子路径)。
/api/*:匹配一级路径(如 /api/user,不匹配 /api/user/1)。
/static/**:匹配静态资源路径。


五、示例:日志记录拦截器
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        request.setAttribute("startTime", System.currentTimeMillis());
        log.info("请求开始: {} {}", request.getMethod(), request.getRequestURI());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        long startTime = (Long) request.getAttribute("startTime");
        long cost = System.currentTimeMillis() - startTime;
        log.info("请求结束: {} {} 耗时 {}ms", request.getMethod(), request.getRequestURI(), cost);
    }
}

六、多拦截器的执行顺序

若有多个拦截器,执行顺序为:

  1. preHandle():按配置顺序依次执行。
  2. postHandle():按配置的逆序执行。
  3. afterCompletion():按配置的逆序执行。

示例配置

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.example.interceptor.InterceptorA"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.example.interceptor.InterceptorB"/>
    </mvc:interceptor>
</mvc:interceptors>

执行顺序

  1. InterceptorA.preHandle() → InterceptorB.preHandle()
  2. InterceptorB.postHandle() → InterceptorA.postHandle()
  3. InterceptorB.afterCompletion() → InterceptorA.afterCompletion()

七、常见问题
1. 拦截器不生效

原因:未正确配置拦截路径或拦截器未被 Spring 管理。
解决:检查 <mvc:mapping> 路径和包扫描配置。

2. 静态资源被拦截

原因:拦截路径配置为 /** 且未排除静态资源。
解决:使用 <mvc:exclude-mapping> 排除静态资源路径。

3. 修改响应后流程中断

解决:在 preHandle() 中手动调用 response.getWriter().write() 并返回 false


八、总结

核心作用:在请求生命周期的关键节点插入业务逻辑。
适用场景:权限控制、日志记录、性能监控等。
配置要点:路径匹配、执行顺序、排除规则。

13 文件上传和下载

一、文件上传
1. 添加依赖

pom.xml 中添加文件上传所需的依赖:

<!-- Apache Commons FileUpload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
2. 配置 Spring MVC 文件解析器

spring-mvc.xml 中配置 MultipartResolver

<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 指定上传文件的最大大小(单位:字节) -->
    <property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
    <!-- 指定默认编码 -->
    <property name="defaultEncoding" value="UTF-8"/>
</bean>
3. 编写上传 Controller
@Controller
@RequestMapping("/file")
public class FileController {

    /**
     * 文件上传处理
     * @param file 前端传递的 MultipartFile 对象
     * @param model 返回消息模型
     * @return 上传结果页面
     */
    @PostMapping("/upload")
    public String uploadFile(
            @RequestParam("file") MultipartFile file,
            Model model
    ) {
        try {
            // 1. 检查文件是否为空
            if (file.isEmpty()) {
                model.addAttribute("msg", "上传文件不能为空!");
                return "result";
            }

            // 2. 获取原始文件名并处理路径问题
            String originalFilename = file.getOriginalFilename();
            // 防止文件名中包含路径,只保留文件名
            String fileName = new File(originalFilename).getName();

            // 3. 指定文件存储路径(示例中存储在项目的 uploads 目录下)
            String uploadDir = "uploads/";
            File dir = new File(uploadDir);
            if (!dir.exists()) {
                dir.mkdirs(); // 创建目录
            }

            // 4. 生成唯一文件名(避免重复)
            String savedFileName = UUID.randomUUID() + "_" + fileName;
            File dest = new File(dir, savedFileName);

            // 5. 保存文件到服务器
            file.transferTo(dest);

            model.addAttribute("msg", "文件上传成功!路径:" + dest.getAbsolutePath());
        } catch (IOException e) {
            model.addAttribute("msg", "文件上传失败:" + e.getMessage());
        }
        return "result";
    }
}
4. 前端上传表单(JSP)
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
    <h2>文件上传</h2>
    <!-- 必须指定 enctype="multipart/form-data" -->
    <form action="/file/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <button type="submit">上传</button>
    </form>
</body>
</html>

二、文件下载
1. 编写下载 Controller
@Controller
@RequestMapping("/file")
public class FileController {

    /**
     * 文件下载处理
     * @param filename 要下载的文件名
     * @return 文件流响应
     */
    @GetMapping("/download")
    public ResponseEntity<byte[]> downloadFile(
            @RequestParam("filename") String filename
    ) {
        try {
            // 1. 指定文件存储路径
            String uploadDir = "uploads/";
            File file = new File(uploadDir + filename);

            // 2. 检查文件是否存在
            if (!file.exists()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
            }

            // 3. 读取文件内容到字节数组
            byte[] fileBytes = Files.readAllBytes(file.toPath());

            // 4. 设置响应头
            HttpHeaders headers = new HttpHeaders();
            // 指定 Content-Disposition 告诉浏览器以下载方式处理
            headers.add("Content-Disposition", "attachment;filename=" + filename);
            // 设置 Content-Type(根据实际文件类型调整)
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // 设置 Content-Length(可选)
            headers.setContentLength(fileBytes.length);

            // 5. 返回文件流
            return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }
    }
}
2. 前端下载链接(JSP)
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>文件下载</title>
</head>
<body>
    <h2>文件下载</h2>
    <!-- 示例:下载名为 example.txt 的文件 -->
    <a href="/file/download?filename=example.txt">下载文件</a>
</body>
</html>

三、关键注解与配置说明
  1. @RequestParam("file")
    • 用于接收前端表单中 name="file" 的文件输入。

  2. MultipartFile
    • Spring 提供的文件上传接口,提供 transferTo() 保存文件。

  3. ResponseEntity<byte[]>
    • 封装 HTTP 响应的实体类,可直接返回字节数组和响应头。

  4. HttpHeaders
    • 设置响应头,如 Content-Disposition(告诉浏览器下载文件)。


四、常见问题与解决
  1. 上传文件大小限制
    • 修改 CommonsMultipartResolvermaxUploadSize 属性。

  2. 中文文件名乱码
    • 确保 CommonsMultipartResolverdefaultEncoding 设置为 UTF-8

  3. 文件存储路径权限问题
    • 检查应用是否有权限写入目标目录。

  4. 安全建议
    • 使用 UUID 重命名文件,避免文件名冲突和路径遍历攻击。


五、完整流程示意图
客户端 → 上传表单 → Spring MVC → MultipartResolver 解析 → Controller 保存文件
客户端 → 下载链接 → Controller 读取文件 → 返回字节流 → 浏览器下载

网站公告

今日签到

点亮在社区的每一天
去签到