spring MVC

1. 学习目标


在这里插入图片描述

2. MVC 思想

2.1. 什么叫MVC?

模型-视图-控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计思想。它主要通过分

离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数

据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请

求,并调用后台服务(service或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据

在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界

面中分离出来,允许它们单独改变而不会相互影响。

2.2. 常见MVC框架运行性能比较

Jsp+servlet > struts1 > spring mvc > struts2+freemarker > struts2,ognl,值栈。

开发效率上,基本正好相反。值得强调的是,spring mvc开发效率和struts2不相上下,但从目前来

看,spring mvc 的流行度已远远超过struts2。

3. SpringMVC 框架概念与特点

3.1. Spring MVC是什么?

Spring MVC是Spring家族中的一个web成员,它是一种基于Java的实现了Web MVC设计思想的请求

驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指

的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring MVC也是要简化我们日常Web

开发的。

Spring MVC是服务到工作者思想的实现。中央控制器是DispatcherServlet;应用控制器拆为处理器

映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;支持本地化/国

际化(Locale)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大

的约定大于配置(惯例优先原则)的契约式编程支持。

3.2. Spring MVC能帮我们做什么?

  1. 让我们能非常简单的设计出干净的Web层;
  2. 进行更简洁的Web层的开发;
  3. 天生与Spring框架集成(如IOC容器、AOP等);
  4. 提供强大的约定大于配置的契约式编程支持;
  5. 能简单的进行Web层的单元测试;
  6. 支持灵活的URL到页面控制器的映射;
  7. 非常容易与其他视图技术集成,如jsp、Velocity、FreeMarker等等,因为模型数据不放在特定的

API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);

  1. 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架

的API;

  1. 支持灵活的本地化等解析;
  2. 更加简单的异常处理;
  3. 对静态资源的支持;
  4. 支持Restful风格。

4. SpringMVC 请求流程

4.1. Spring MVC 请求处理流程分析

Spring MVC框架也是一个基于请求驱动的Web框架,并且使用了前端控制器模式(是用来提供一个集

中的请求处理机制,所有的请求都将由一个单一的处理程序处理来进行设计,再根据请求映射规则分发

给相应的页面控制器(动作/处理器)进行处理。首先让我们整体看一下Spring MVC处理请求的流程:
在这里插入图片描述

  1. 首先用户发送请求,请求被SpringMvc前端控制器(DispatherServlet)捕获;
  2. 前端控制器(DispatherServlet)对请求URL解析获取请求URI,根据URI,调用HandlerMapping;
  3. 前端控制器(DispatherServlet)获得返回的HandlerExecutionChain(包括Handler对象以及

Handler对象对应的拦截器);

  1. DispatcherServlet 根据获得的 HandlerExecutionChain,选择一个合适的HandlerAdapter。(附

注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法);

  1. HandlerAdapter根据请求的Handler适配并执行对应的Handler;HandlerAdapter(提取Request

中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程

中,根据配置,Spring将做一些额外的工作:

HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定

的响应信息。

数据转换:对请求消息进行数据转换。如String转换成Integer、Double等数据格式化:

数据格式化。 如将字符串转换成格式化数字或格式化日期等

数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中)

  1. Handler执行完毕,返回一个ModelAndView(即模型和视图)给HandlerAdaptor
  2. HandlerAdaptor适配器将执行结果ModelAndView返回给前端控制器。
  3. 前端控制器接收到ModelAndView后,请求对应的视图解析器。
  4. 视图解析器解析ModelAndView后返回对应View;
  5. 渲染视图并返回渲染后的视图给前端控制器。
  6. 最终前端控制器将渲染后的页面响应给用户或客户端

4.2. Spring MVC 优势

  1. 清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、

处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器

(Controller)、验证器( Validator)、命令对象(Command 请求参数绑定到的对象就叫命令对

象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。

  1. 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;
  2. 和Spring 其他框架无缝集成,是其它Web框架所不具备的;
  3. 可适配,通过HandlerAdapter可以支持任意的类作为处理器;
  4. 可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;
  5. 功能强大的数据验证、格式化、绑定机制;
  6. 利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;
  7. 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
  8. 强大的JSP标签库,使JSP编写更容易。

还有比如RESTful(一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条

件。它主要用于客户端和服务器交互类的软件,目前了解即可)风格的支持、简单的文件上传、约定大

于配置的契约式编程支持、基于注解的零配置支持等等。

5. Spring MVC 环境搭建

5.1. 开发环境

Idea + Maven + Jdk11 + Jetty

5.2. 新建 Maven webApp

Idea 下创建 springmvc01 工程

5.3. pom.xml 坐标添加

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- spring web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!-- spring mvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!-- web servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <!-- 编译环境插件 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>11</source>
          <target>11</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <!-- jetty插件 -->
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>9.4.27.v20200227</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>
          <!-- 设置端口 -->
          <httpConnector>
            <port>8080</port>
          </httpConnector>
          <!-- 设置项目路径 -->
          <webAppConfig>
            <contextPath>/springmvc01</contextPath>
          </webAppConfig>
        </configuration>
      </plugin>
    </plugins>
  </build>

5.4. 配置 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

  <!-- 编码过滤 utf-8 -->
  <filter>
    <description>char encoding filter</description>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- servlet请求分发器 -->
  <servlet>
    <servlet-name>springMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring.xml</param-value>
    </init-param>
    <!-- 表示启动容器时初始化该Servlet -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <!-- 这是拦截请求, "/"代表拦截所有请求,"*.do"拦截所有.do请求 -->
    <url-pattern>/</url-pattern>
<!--        <url-pattern>*.do</url-pattern>-->
  </servlet-mapping>
</web-app>

要想启动我们的 SpringMVC 环境,目前对于 mvc 框架的配置还未进行。以上在 web.xml 中引用了

spring.xml 文件。

5.4.1. spring.xml 配置
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        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">

    <!-- 开启扫描器 -->
    <context:component-scan base-package="com.xxxx.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 开启注解驱动-->
    <mvc:annotation-driven/>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀:在WEB-INF目录下的jsp目录下 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 后缀:以.jsp结尾的资源 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>
5.4.2. 页面控制器的编写
@Controller
@RequestMapping("user")
public class HelloController {

    /**
     * 路径开头是否加 斜杠"/" 均可
     *		@RequestMapping("/请求路径") 与 @RequestMapping("请求路径")均可
     *     建议加上,如:@RequestMapping("/test02")
     *		访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/test02
     * @return
     */
    @RequestMapping("hello")
    public ModelAndView hello(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello springmvc");
        mv.setViewName("hello");
        return mv;
    }
}
5.4.3. 添加视图页面

在 WEB-INF 下新建 jsp 文件夹 ,并在文件夹下新建 hello.jsp

<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>${msg}</h2>
</body>
</html>

5.4.4. 启动 jetty 服务器

http://localhost:8080/springmvc01/hello

6. URL 地址映射配置

6.1. @RequestMapping

通过注解 @RequestMapping 将请求地址与方法进行绑定,可以在类级别和方法级别声明。类级别的

注解负责将一个特定的请求路径映射到一个控制器上,将 url 和类绑定;通过方法级别的注解可以细化

映射,能够将一个特定的请求路径映射到某个具体的方法上,将 url 和类的方法绑定。

6.1.1. 映射单个 URL

@RequestMapping(“”) 或 @RequestMapping(value=“”)

/**
     * 路径开头是否加 斜杠"/" 均可
     *		@RequestMapping("/请求路径") 与 @RequestMapping("请求路径")均可
     *     建议加上,如:@RequestMapping("/test02")
     *		访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/test02
     * @return
     */
    @RequestMapping("hello")
    public ModelAndView hello(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello springmvc");
        mv.setViewName("hello");
        return mv;
    }

    /**
     * @RequestMapping 声明在方法上面,映射单个 URL
     * 	访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/hello02
     * @return
     */
    @RequestMapping("hello2")
    public ModelAndView hello2(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello ssm");
        mv.setViewName("hello");
        return mv;
    }
6.1.2. 映射多个 URL

@RequestMapping({“”,“”}) 或 @RequestMapping(value={“”,“”})

/**
     * @RequestMapping 声明在方法上面,映射多个 URL
     *		支持一个方法绑定多个 url 的操作
     * 	访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/hello03
     *     	http://ip:port/springmvc01/hello3
     * @return
     */
    @RequestMapping(value = {"hello3","hello03"})
    public ModelAndView hello3(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 03的接口");
        mv.setViewName("hello");
        return mv;
    }
6.1.3. 映射 URL 在控制器上

用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

@Controller
@RequestMapping("user")
public class HelloController {

    /*** @RequestMapping 声明在类上面,类中的的方法都是以该地址作为父路径 
    * 声明级别: 
    * 类级别 + 方法级别 (/类路径/方法路径) 
    * 访问地址: 
    * http://ip:port/springmvc01/url/hello
    * @return 
    */
    @RequestMapping("hello")
    public ModelAndView hello(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello springmvc");
        mv.setViewName("hello");
        return mv;
    }
6.1.4. 设置 URL 映射的请求方式

默认没有设置请求方式,在HTTP 请求中最常用的请求方法是 GET、POST,还有其他的一些方法,

如:DELETE、PUT、HEAD 等。

可以通过 method 属性设置支持的请求方式,如 method=RequestMethod.POST;如设置多种请求

方式,以大括号包围,逗号隔开即可。

 /**
     * 设置请求方式
     *  通过 method 属性设置方法支持的请求方式,默认 GET请求和 POST等请求都支持。
     *  设置了请求方式,则只能按照指定的请求方式请求。
     *      访问地址:(只能使用POST请求访问)
     *           http://ip:port/springmvc01/url/hello05
     * @return
     */
    @RequestMapping(value = "hello5",method = RequestMethod.POST)
    public ModelAndView hello5(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 03的接口");
        mv.setViewName("hello");
        return mv;
    }
6.1.5. 通过参数名称映射 URL
/**
     * 通过参数名称访问
     *   通过参数的形式访问
     *   访问地址:
     *      http://ip:port/springmvc01/url?hello06
     * @return
     */
    @RequestMapping(params = "hello6")
    public ModelAndView hello6(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 参数地址");
        mv.setViewName("hello");
        return mv;
    }
6.16 代码
package com.xxxx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * @RequestMapping (请求地址映射)
 *      绑定给类或者方法
 */
@Controller
@RequestMapping("user")
public class HelloController {

    /**
     * 路径开头是否加 斜杠"/" 均可
     *		@RequestMapping("/请求路径") 与 @RequestMapping("请求路径")均可
     *     建议加上,如:@RequestMapping("/test02")
     *		访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/test02
     * @return
     */
    @RequestMapping("hello")
    public ModelAndView hello(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello springmvc");
        mv.setViewName("hello");
        return mv;
    }

    /**
     * @RequestMapping 声明在方法上面,映射单个 URL
     * 	访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/test01
     * @return
     */
    @RequestMapping("hello2")
    public ModelAndView hello2(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello ssm");
        mv.setViewName("hello");
        return mv;
    }

    /**
     * @RequestMapping 声明在方法上面,映射多个 URL
     *		支持一个方法绑定多个 url 的操作
     * 	访问地址:(如果有类路径需要写在方法路径前面)
     *     	http://ip:port/springmvc01/test03_01
     *     	http://ip:port/springmvc01/test03_02
     * @return
     */
    @RequestMapping(value = {"hello3","hello03"})
    public ModelAndView hello3(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 03的接口");
        mv.setViewName("hello");
        return mv;
    }

    /**
     * 设置请求方式
     *  通过 method 属性设置方法支持的请求方式,默认 GET请求和 POST等请求都支持。
     *  设置了请求方式,则只能按照指定的请求方式请求。
     *      访问地址:(只能使用POST请求访问)
     *           http://ip:port/springmvc01/url/test05
     * @return
     */
    @RequestMapping(value = "hello5",method = RequestMethod.POST)
    public ModelAndView hello5(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 03的接口");
        mv.setViewName("hello");
        return mv;
    }

    /**
     * 通过参数名称访问
     *   通过参数的形式访问
     *   访问地址:
     *      http://ip:port/springmvc01/url?test06
     * @return
     */
    @RequestMapping(params = "hello6")
    public ModelAndView hello6(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","hello 参数地址");
        mv.setViewName("hello");
        return mv;
    }
}

web下jsp

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 10:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>${msg}</h2>
</body>
</html>

7. 参数绑定

客户端请求的参数到控制器功能处理方法上的参数的绑定,对于参数绑定非常灵活。

7.1. 基本数据类型

  • 参数值必须存在。如果没有指定参数值,也没有设置参数默认值,则会报500异常。
  • 解决方法:设置默认值@RequestParam(defaultValue = “数据”),包装类
@RequestMapping("test2")
    public void test2(@RequestParam(defaultValue = "20") int age, @RequestParam(defaultValue = "zhangs") String name){
        System.out.println(age+"---"+name);
    }

@RequestMapping("test3")
    public void test3( Integer age, String name){
        System.out.println(age+"---"+name);
    }

7.2. 包装类型

  • 客户端请求参数名与方法形参名保持一致,默认参数值为null
  • 可以通过 @RequestParam 的name属性设置参数的别名,defaultValue属性设置参数默认值 (注解声明在形参的前面)
@RequestMapping("test4")
    public void test4(@RequestParam(name = "uage") Integer age,@RequestParam(name = "uname")String name){
        System.out.println(age+"---"+name);
    }

7.3. 字符串类型

  • 客户端请求参数名与方法形参名保持一致,默认参数值为null
  • 可以通过 @RequestParam 的name属性设置参数的别名,defaultValue属性设置参数默认值
@RequestMapping("data04") 
public void data04(String userName, String userPwd){
    System.out.println("userName:" + userName + ", userPwd:" + userPwd); 
}

7.4. 数组类型

  • 客户端传参形式:ids=1&ids=2&ids=3
 @RequestMapping("test5")
    public void test5(String[] ids){
        System.out.println(Arrays.toString(ids));
    }

7.5. JavaBean 类型

  • 客户端请求的参数名与JavaBean对象的属性字段名保持一致

User.java

package com.xxxx.po;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class User {
    
    private int id;
    private String userName;
    private String userPwd;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserPwd() {
        return userPwd;
    }
    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", userName=" + userName + ", userPwd="
                + userPwd + "]";
    }

}
    @RequestMapping("test6")
    public void test6(User user){
        System.out.println(user);
    }

7.6. ListMap类型

  • 此时 User 实体需要定义对应 list 属性。(对于集合的参数绑定,一般需要使用 JavaBean 对象进行包

装)

  • Map最为灵活,它也需要绑定在对象上,而不能直接写在Controller方法的参数中。

User.java

package com.xxxx.po;

import java.util.*;

public class User {

    private int id;
    private String userName;
    private String userPwd;


    private List<String> list = new ArrayList<>();

    private Map<String, User> map = new HashMap();

    public Map<String, User> getMap() {
        return map;
    }

    public void setMap(Map<String, User> map) {
        this.map = map;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", userName=" + userName + ", userPwd="
                + userPwd + "]";
    }

}

DataController.java

@RequestMapping("test7")
    public void test7(User user) {
        for (String uu : user.getList()) {
            System.out.println(uu);
        }
        Set<Map.Entry<String, User>> entries = user.getMap().entrySet();
        for (Map.Entry<String, User> entry : entries) {
            System.out.println(entry.getKey() + "--" + entry.getValue());

        }
    }

test.jsp

<form action="test7">
    <input type="text" name="list[0]">
    <input type="text" name="list[1]">
    <input type="text" name="map['1'].id">
    <button type="submit">提交</button>
</form>

7.7. Set 类型

Set 和 List 类似,也需要绑定在对象上,而不能直接写在 Controller 方法的参数中。但是,绑定Set数

据时,必须先在Set对象中add相应的数量的模型对象。

package com.xxxx.po;

import java.util.*;

public class User {

    private int id;
    private String userName;
    private String userPwd;

    private Set<User> users = new HashSet<User>();

    public User() {
        users.add(new User());
        users.add(new User());
        users.add(new User());
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", userName=" + userName + ", userPwd="
                + userPwd + "]";
    }

}

Phone

public class Phone {
    private String num;

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Phone [num=" + num + "]";
    }
}

Jsp 页面定义

<form action="test8" method="post">
    <input name="phones[0].num" value="123456"/>
    <input name="phones[1].num" value="4576"/>
    <button type="submit"> 提交</button>
</form>

Controller 方法

@RequestMapping("test8")
    public void test8(User user) {
        System.out.println(user);
    }

7.8 代码

package com.xxxx.controller;

import com.xxxx.po.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.lang.annotation.Retention;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

/**
 * 参数绑定
 */
@Controller
public class DataController {
    /**
     * 基本数据类型
     * 如果接收到空的数据会报错500
     * 基本数据类型接收不了null
     *
     * @param age
     * @param name
     */
    @RequestMapping("test1")
    public void test(int age, String name) {
        System.out.println(age + "---" + name);
    }

    /**
     * 基本数据类型
     *
     * @param age
     * @param name
     * @RequestParam(defaultValue = "数据")  给参数设置默认值
     */
    @RequestMapping("test2")
    public void test2(@RequestParam(defaultValue = "20") int age, @RequestParam(defaultValue = "zhangs") String name) {
        System.out.println(age + "---" + name);
    }

    /**
     * 包装数据类型
     * 可以接收到null数据
     *
     * @param age
     * @param name
     */

    @RequestMapping("test3")
    public void test3(Integer age, String name) {
        System.out.println(age + "---" + name);
    }

    /**
     * 包装类型数据绑定
     * 通过注解 @RequestParam 标记一个形参为请求参数。(注解声明在形参的前面)
     * 可以通过注解的属性设置相关内容
     * 设置参数的参数名(别名) name
     *
     * @param age
     * @param
     */

    @RequestMapping("test4")
    public void test4(@RequestParam(name = "uage") Integer age, @RequestParam(name = "uname") String name) {
        System.out.println(age + "---" + name);
    }

    /**
     * 数组类型数据绑定
     * 客户端传参形式:ids=1&ids=2&ids=3
     *
     * @param ids
     */

    @RequestMapping("test5")
    public void test5(String[] ids) {
        System.out.println(Arrays.toString(ids));
    }


    /**
     * JavaBean 数据绑定
     * 客户端请求的参数名与JavaBean对象的属性字段名保持一致
     *
     * @param user
     */

    @RequestMapping("test6")
    public void test6(User user) {
        System.out.println(user);
    }

    /**
     * list数据集合绑定
     * 客户端请求的参数名与JavaBean对象的属性字段名保持一致
     *
     * @param user
     */
    @RequestMapping("test7")
    public void test7(User user) {
        for (String uu : user.getList()) {
            System.out.println(uu);
        }
        Set<Map.Entry<String, User>> entries = user.getMap().entrySet();
        for (Map.Entry<String, User> entry : entries) {
            System.out.println(entry.getKey() + "--" + entry.getValue());

        }
    }

    @RequestMapping("test8")
    public void test8(User user) {
        System.out.println(user);
    }
}

8. 请求转发与重定向

SpringMVC 默认采用服务器内部转发的形式展示页面信息。同样也支持重定向页面。

8.1. 重定向

重定向是发一个302的状态码给浏览器,浏览器自己去请求跳转的网页。地址栏会发生改变。

重定向以 redirect: 开头

url地址栏会变化

  • redirect: 重定向

  • redirect:xxx.jsp 重定向跳转到xxx.jsp页面(页面存在于项目的根目录)

  • redirect:xxx 重定向跳转到controller接口

  • 传递参数 return “redirect:view.jsp?uname=admin”,有中文时通过 RedirectAttributes 对象设置重定向参数,避免中文乱码且不显示问题

@RequestMapping("red05")
    public String test5(RedirectAttributes redirectAttributes){
        redirectAttributes.addAttribute("uname","张三");
        return "redirect:view.jsp";
    }
  • 获取参数 ${param.参数名}
<%=request.getParameter("uname")%><br>

8.2. 请求转发

请求转发,直接调用跳转的页面,让它返回。对于浏览器来说,它无法感觉服务器有没有forward。

地址栏不发生改变。可以获取请求域中的数据。

请求转发以 forward: 开头

服务器默认使用请求转发的方式

url地址栏不发生变化

  • forward: 请求转发
  • redirect:xxx.jsp 请求转发到xxx.jsp页面(页面存在于项目的根目录)
  • redirect:xxx 请求转发到controller接口
  • return “hello” model.setViewName("hello);* 都会经过视图解析器,添加前缀和后缀
  • 有跳转行为的关键词存在,那么如果是视图就必须在后面加上.jsp,如果不是回去找接口
  • 设置参数
 @RequestMapping("/view09")
    public String view09(){
        return "forward:view.jsp?uname=张三&upwd=123456";
    }
  • 设置请求域
 @RequestMapping("/view10")
    public String view10(Model model){
        model.addAttribute("uname","张三");
        return "forward:view.jsp";
    }
  • 传递参数
  @RequestMapping("/view13")
    public ModelAndView view13(ModelAndView modelAndView){
        modelAndView.setViewName("forward:view15?uname=admin");
        return  modelAndView;
    }


    @RequestMapping("/view15")
    public void view15(String uname, HttpServletRequest request){
        String uname1 = request.getParameter("uname");
        System.out.println(uname1);//控制台输出
        System.out.println(uname);//控制台输出
    }
  • 获取参数与请求域

    获取传递的参数:${param.参数名}

    获取请求域的数据:${请求域中设置的名称}

 <%=request.getParameter("uname")%><br>

 作用域:<%=request.getAttribute("uname")%>

8.3 代码

JumpController

package com.xxxx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 重定向
 *      url地址栏会变化
 *      redirect:  重定向
 *          redirect:xxx.jsp      重定向跳转到xxx.jsp页面(页面存在于项目的根目录)
 *          redirect:xxx          重定向跳转到controller接口
 *
 * 请求转发
 *      服务器默认使用请求转发的方式
 *      url地址栏不发生变化
 *      forward:  请求转发
 *          redirect:xxx.jsp      请求转发到xxx.jsp页面(页面存在于项目的根目录)
 *  *       redirect:xxx          请求转发到controller接口
 *
 *
 *          return "hello"  model.setViewName("hello);
 *          都会经过视图解析器,添加前缀和后缀
 *          有跳转行为的关键词存在,那么如果是视图就必须在后面加上.jsp,如果不是回去找接口
 */
@Controller
public class JumpController {
    /**
     * 重定向到jsp页面
     *      返回modelandview
     */
    @RequestMapping("red01")
    public ModelAndView test(ModelAndView mv){
        mv.setViewName("redirect:view.jsp");
        return mv;
    }

    /**
     * 重定向到controller接口
     *      返回modelandview
     */
    @RequestMapping("red02")
    public ModelAndView test2(ModelAndView mv){
        mv.setViewName("redirect:user/hello");
        return mv;
    }


    /**
     * 重定向到jso页面
     *      返回视图字符串
     */
    @RequestMapping("red03")
    public String test3(){
        return "redirect:view.jsp";
    }

    /**
     * 重定向到jso页面
     *      传递参数
     *      返回视图字符串
     */
    @RequestMapping("red04")
    public String test4(){
        return "redirect:view.jsp?uname=admin";
    }

    /**
     * 重定向到jso页面
     *      传递参数 (通过 RedirectAttributes 对象设置重定向参数,避免中文乱码且不显示问题)
     *      返回视图字符串
     */
    @RequestMapping("red05")
    public String test5(RedirectAttributes redirectAttributes){
        redirectAttributes.addAttribute("uname","张三");
        return "redirect:view.jsp";
    }

    /**
     * 请求转发到JSP页面
     */
    @RequestMapping("/for01")
    public String view08(){
        return "forward:view.jsp";
    }


    /**
     * 请求转发到JSP页面
     *      设置参数
     */
    @RequestMapping("/view09")
    public String view09(){
        return "forward:view.jsp?uname=张三&upwd=123456";
    }


    /**
     * 请求转发到JSP页面
     *      设置请求域
     */
    @RequestMapping("/view10")
    public String view10(Model model){
        model.addAttribute("uname","张三");
        return "forward:view.jsp";
    }

    /**
     * 请求转发到JSP页面
     *      设置请求域
     */
    @RequestMapping("/view11")
    public String view11(){
        return "../../view";//根目录
//        return "view";//web下
    }


    /**
     * 请求转发到 Controller
     * @return
     */
    @RequestMapping("/view12")
    public ModelAndView view12(ModelAndView modelAndView){
        modelAndView.setViewName("forward:user/hello");
        return  modelAndView;
    }

    /**
     * 请求转发到 Controller
     *      传递参数
     * @return
     */
    @RequestMapping("/view13")
    public ModelAndView view13(ModelAndView modelAndView){
        modelAndView.setViewName("forward:view15?uname=admin");
        return  modelAndView;
    }


    @RequestMapping("/view15")
    public void view15(String uname, HttpServletRequest request){
        String uname1 = request.getParameter("uname");
        System.out.println(uname1);//控制台输出
        System.out.println(uname);//控制台输出
    }


    /**
     * 请求转发到 Controller
     *      传递参数
     * @return
     */
    @RequestMapping("/view16")
    public String view16(ModelAndView modelAndView){
        modelAndView.setViewName("forward:view");
        return  "view";
    }
}

与web同级jsp

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 10:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>根目录view jsp视图</h2>
    <%=request.getParameter("uname")%><br>

    作用域:<%=request.getAttribute("uname")%>

</body>
</html>

web下面jsp

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 10:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>web-info/jsp/ 目录view jsp视图</h2>
    <%=request.getParameter("uname")%><br>

    作用域:<%=request.getAttribute("uname")%>

</body>
</html>

9. JSON 数据开发

9.1. 基本概念

Json 在企业开发中已经作为通用的接口参数类型,在页面(客户端)解析很方便。SpringMVC 对于

json 提供了良好的支持,这里需要修改相关配置,添加 json 数据支持功能

9.1.1. @ResponseBody

该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式

后,写入到 Response 对象的 body 数据区。

返回的数据不是 html 标签的页面,而是其他某种格式的数据时(如 json、xml 等)使用(通常用于

ajax 请求)。

访问springmvc架构的接收,默认返回的是页面,使用该注解返回的是json数据

注解的声明:

  • 1.定义在接口方法上
  • 2.定义在方法修饰符 和 返回值类型的中间
  • 3.定义在类上:当前类中所有接口的返回数据类型是json的

返回类型

  • javabean
  • 集合

9.1.2. @RequestBody

该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行

解析,然后把相应的数据绑定到要返回的对象上 ,再把 HttpMessageConverter 返回的对象数据绑定到

controller 中方法的参数上。

  • 通常要求前台传来的数据格式是json,一定是一个字符串

  • 通过 @RequestBody 可以将请求体中的 JSON 字符串绑定到相应的 bean 上,当然,也可以将其分别绑定到对应的字符串上。

  • @RequestBody与post请求的关系

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。

注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。

注:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、
数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时,原SpringMVC接收
参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value
里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。
即:如果参数时放在请求体中,传入后台的话,那么后台要用@RequestBody才能接收到;如果不是放在
请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或则形参前
什么也不写也能接收。

注:如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通
过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。

注:如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名
的话,那么就会自动匹配;没有的话,请求也能正确发送。
追注:这里与feign消费服务时不同;feign消费服务时,如果参数前什么也不写,那么会被默认是
@RequestBody的。

如果后端参数是一个对象,且该参数前是以@RequestBody修饰的,那么前端传递json参数时,必须满足以下要求:

后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为),这一条我会在下面详细分析,其他的都可简单略过,但是本文末的核心逻辑代码以及几个结论一定要看! 实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。

json字符串中,如果value为"“的话,后端对应属性如果是String类型的,那么接受到的就是”",如果是后端属性的类型是Integer、Double等类型,那么接收到的就是null。

json字符串中,如果value为null的话,后端对应收到的就是null。

9.2. 使用配置

9.2.1. 添加 json相关坐标

pom.xml

 <!-- 添加json 依赖jar包 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.10.0</version>
    </dependency>
9.2.2. 修改配置文件

spring.xml

<!-- mvc 请求映射 处理器与适配器配置 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
        </mvc:message-converters>
    </mvc:annotation-driven>

9.2.3. 注解使用

9.2.3.1. @ResponseBody
  • javanbean
  • 集合
package com.xxxx.controller;

import com.xxxx.po.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.List;

/**
 * @ResponseBody 访问springmvc架构的接收,默认返回的是页面,使用该注解返回的是json数据
 * 注解的声明:
 * 1.定义在接口方法上
 * 2.定义在方法修饰符 和 返回值类型的中间
 * 3.定义在类上:当前类中所有接口的返回数据类型是json的
 * @RequestBody 通常要求前台传来的数据格式是json,一定是一个字符串
 * 通过 @RequestBody 可以将请求体中的 JSON 字符串绑定到相应的 bean 上,当然,也可以将其分别绑定到对应的字符串上。
 */
@Controller
public class JsonController {
    @RequestMapping("json1")
    @ResponseBody()
    public User JUser() {
        User user = new User();
        user.setId(10);
        user.setUserName("admin");
        user.setUserPwd("123456");
        return user;
    }

    @RequestMapping("json2")
    public @ResponseBody
    User jUser2() {
        User user = new User();
        user.setId(10);
        user.setUserName("admin");
        user.setUserPwd("123456");
        return user;
    }

    @RequestMapping("json3")
    @ResponseBody
    public List<User> jUser3() {
        List<User> list = new ArrayList<>();
        User user = new User();
        user.setId(10);
        user.setUserName("admin");
        user.setUserPwd("123456");
        User user2 = new User();
        user2.setId(12);
        user2.setUserName("admin2");
        user2.setUserPwd("12dsa56");
        User user3 = new User();
        user3.setId(13);
        user3.setUserName("admin3");
        user3.setUserPwd("1sad56");

        list.add(user);
        list.add(user2);
        list.add(user3);
        return list;
    }
}
package com.xxxx.po;

import java.util.*;

public class User {

    private int id;
    private String userName;
    private String userPwd;

    private List<String> list = new ArrayList<>();

    private Map<String, User> map = new HashMap();

    public Map<String, User> getMap() {
        return map;
    }

    public void setMap(Map<String, User> map) {
        this.map = map;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", userName=" + userName + ", userPwd="
                + userPwd + "]";
    }

}
9.2.3.2. @RequestBody

@RequestBody 注解常用来处理 content-type 不是默认的 application/x-www-form-urlcoded 类型

的内容,比如说:application/json 或者是application/xml 等。一般情况下来说常用其来处理

application/json 类型。@RequestBody接受的是一个 json 格式的字符串,一定是一个字符串。

通过 @RequestBody 可以将请求体中的 JSON 字符串绑定到相应的 bean 上,当然,也可以将其分别

绑定到对应的字符串上。

  • 导入js

  • @RequestBody 规定请求的参数是JOSN格式的字符串

    注解设置在形参前面

  • 请求传递JSON格式的数据

    返回JSON格式的数据 ,请求方式使用post

  • 访问json.jsp

@RequestMapping("json5")
    @ResponseBody
    public User jUser5(@RequestBody User user){

        return user;
    }
<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 10:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <button type="button" οnclick="fun()">提交</button>
    <script type="text/javascript" src="js/jquery-3.5.1.min.js"></script>
    <script type="text/javascript">
        var params = '{"id":"10","userName":"admin","userPwd":"123456"}';

        function fun(){
            $.ajax({
                type:"post",
                url:"json5",
                data:params,
                //设置服务器请求类型的数据类型为JSON格式
                contentType:"application/json;charset=utf-8",
                dataType:"json",
                success:function (data){
                    console.log(data);
                }
            });
        }
    </script>
</body>
</html>

10 拦截器

10.1. 基本概念

SpringMVC 中的 Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并

进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。对于 SpringMVC

拦截器的定义方式有两种:

实现接口:org.springframework.web.servlet.HandlerInterceptor

继承适配器:org.springframework.web.servlet.handler.HandlerInterceptorAdapter

10.2. 拦截器实现

  • 实现 HandlerInterceptor 接口
  • 继承 HandlerInterceptorAdapter

全部都要重写preHandle,postHandle,afterCompletion三个方法。

如果有多个拦截器满足拦截处理的要求,则依据配置的先后顺序来执行

  • 先配置的拦截器的 preHandle 方法先执行
  • 先配置的拦截器的 postHandle、afterCompletion 方法后执行

preHandle

  • 在 handler(controller) 运行之前 执行
  • 返回值boolean
  • return false 目标方法handler 不会执行
  • return true 目标方法正常执行

postHandle

  • 在 handler(controller) 运行之后,视图生成之前 执行

afterCompletion

  • 在 视图生成之后 执行

拦截器的配置

<!--配置一个拦截器-->
    <mvc:interceptors>
        <!--
    	使用bean定义一个Interceptor
    	直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->
        <bean class="com.xxxx.interceptors.MyInterceptor1"></bean>
        <bean class="com.xxxx.interceptors.MyInterceptor2"></bean>
    </mvc:interceptors>
<!--   多个拦截器实现      配置拦截器规则-->
    <mvc:interceptors>
        <!--定义一个拦截器-->
        <mvc:interceptor>
            <!--设置当前拦截器拦截的资源   /** 表示拦截所有-->
            <mvc:mapping path="/**"/>
            <!--设置拦截器忽略某些资源-->
            <mvc:exclude-mapping path="/user/*"/>  <!-- "/url/*"表示放行url路径下的请求。 -->
            <bean class="com.xxxx.interceptors.MyInterceptor1"></bean>
        </mvc:interceptor>

    </mvc:interceptors>
package com.xxxx.interceptors;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器
 * 实现方式一:实现HandlerInterceptor,重写对应方法
 */
public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * 在 handler(controller) 运行之前 执行
     * 返回值boolean
     * return false  目标方法handler 不会执行
     * return true   目标方法正常执行
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在 handler(controller) 运行之前 执行 -------HandlerInterceptor");
        return true;
    }

    /**
     * 在 handler(controller) 运行之后,视图生成之前 执行
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在 handler(controller) 运行之后,视图生成之前 执行 -------HandlerInterceptor");
    }

    /**
     * 在 视图生成之后 执行
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在 视图生成之后 执行 -------HandlerInterceptor");
    }
}
package com.xxxx.interceptors;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器
 *      实现方式一:实现HandlerInterceptor,重写对应方法
 */
public class MyInterceptor2 extends HandlerInterceptorAdapter {

    /**
     * 在 handler(controller) 运行之前 执行
     *         返回值boolean
     *              return false  目标方法handler 不会执行
     *              return true   目标方法正常执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在 handler(controller) 运行之前 执行 -------HandlerInterceptorAdapter");
        return true;
    }

    /**
     * 在 handler(controller) 运行之后,视图生成之前 执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在 handler(controller) 运行之后,视图生成之前 执行 -------HandlerInterceptorAdapter");
    }

    /**
     * 在 视图生成之后 执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在 视图生成之后 执行 -------HandlerInterceptorAdapter");
    }
}

10.3拦截器应用 - 非法请求拦截

使用拦截器完成用户是否登录请求验证功能

10.3.1. 用户控制器

UserInfo 定义

package com.xxxx.controller;

import com.xxxx.po.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
public class UserInfo {

    @RequestMapping("login")
    public String login(HttpSession session){
        System.out.println("登录成功");
        User user = new User();
        user.setId(10);
        user.setUserName("xiaoming");
        user.setUserPwd("123");
        //登录成功后将用户的基本信息存入session作用域中
        session.setAttribute("user",user);
        return "success";
    }

    @RequestMapping("add")
    public String add(){
        System.out.println("添加成功");
        return "success";
    }

    @RequestMapping("update")
    public String update(){
        System.out.println("修改成功");
        return "success";
    }

    @RequestMapping("delete")
    public String delete(){
        System.out.println("删除成功");
        return "success";
    }

}
10.3.2. 页面定义

success.jsp 定义

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 10:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>success 成功</h2>
</body>
</html>

10.3.3. 非法请求拦截器定义

LoginInterceptor 定义

package com.xxxx.interceptors;

import com.xxxx.po.User;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 在 handler(controller) 运行之前 执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //通过session对象获取作用域用户的状态
        User user = (User) request.getSession().getAttribute("user");

        //判断user数据是否存在
        if(null == user){
            //未登录状态 提示用户去访问登录接口
            // response.sendRedirect(request.getContextPath()+"/WEB-INF/jsp/error.jsp"); //重定向  客户端行为
            request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request,response); //请求转发  服务器端行为
            return false;
        }

        return true;
    }

}
10.3.4. 拦截器xml配置

spring.xml 配置

  <!--模拟登录拦截-->
    <mvc:interceptors>
        <!--定义一个拦截器-->
        <mvc:interceptor>
            <!--设置当前拦截器拦截的资源   /** 表示拦截所有-->
            <mvc:mapping path="/**"/>
            <!--设置拦截器忽略某些资源-->
            <mvc:exclude-mapping path="/login"/>  <!-- "/url/*"表示放行url路径下的请求。 -->
            <bean class="com.xxxx.interceptors.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

11 文件上传

11.1. 环境配置

11.1.1. pom.xml文件修改
 <!-- 添加 commons-fileupload 依赖 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.2</version>
    </dependency>
11.1.2. spring.xml修改
 <!-- 文件上传 -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 允许文件上传的最大尺寸 -->
        <property name="maxUploadSize">
            <value>104857600</value>
        </property>
        <!--
          设置文件放入临时文件夹的最大大小限制。
          此值是阈值,低于此值,则保存在内存中,如高于此值,则生成硬盘上的临时文件。
      -->
        <property name="maxInMemorySize">
            <value>4096</value>
        </property>
    </bean>

11.2. 代码实现

11.2.1. 单文件上传
11.2.1.1. 页面表单
  • input 的type设置为file
  • form 表单的method设为post,
  • form 表单的enctype设置为multipart/form-data,以二进制的形式传输数据

fileUpload.jsp

 <form action="uploadFile" method="post" enctype="multipart/form-data">
        <input type="file" name="file" />
        <button type="submit"> 提交</button>
    </form>
11.2.1.2. 代码实现

上传文件

  1. 为上传的文件准备一个存放路径
  2. 重新创建文件的名称*
  3. 上传
package com.xxxx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 上传文件
 *  1.为上传的文件准备一个存放路径
 *  2.重新创建文件的名称
 *  3.上传
 */
@Controller
public class FileUpload {
    
    @RequestMapping("uploadFile")
    public void uploadFile(MultipartFile file, HttpServletRequest request){

        //判断是否上传了文件
        if(!file.isEmpty()){
            //为上传的文件准备一个存放路径
            String path = request.getServletContext().getRealPath("/");
            //准备目录
            File filePath = new File(path + "/upload");
            //判断目录是否存在
            if(!filePath.exists()){
                filePath.mkdir();//创建目录
            }

            //重新创建文件的名称
            //获取文件原名称
            String filename = file.getOriginalFilename();
            //截取文件的后缀  从最后一个点开始
            String hzName = filename.substring(filename.lastIndexOf("."));
            //以时间戳为名称创建一个新的文件名字
            String newFileName = System.currentTimeMillis() + hzName;

            try {
                //上传
                file.transferTo(new File(filePath, newFileName));
                System.out.println("上传成功");
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("上传失败");
            }
        }
    }
}
11.2.2. 多文件上传
11.2.2.1. 页面表单
<h2>多文件上传</h2>
    <form action="uploadFile" method="post" enctype="multipart/form-data">
        <input type="file" name="files" />
        <input type="file" name="files" />
        <input type="file" name="files" />
        <button type="submit"> 提交</button>
    </form>
11.2.2.2 代码实现
package com.xxxx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 上传文件
 *  1.为上传的文件准备一个存放路径
 *  2.重新创建文件的名称
 *  3.上传
 */
@Controller
public class FileUpload {

    @RequestMapping("uploadFile")
    public void uploadFiles(@RequestParam("files") List<MultipartFile> files, HttpServletRequest request){
        for(MultipartFile file:files){
            uploadFile(file,request);
        }
    }

    //@RequestMapping("uploadFile")
    public void uploadFile(MultipartFile file, HttpServletRequest request){

        //判断是否上传了文件
        if(!file.isEmpty()){
            //为上传的文件准备一个存放路径
            String path = request.getServletContext().getRealPath("/");
            //准备目录
            File filePath = new File(path + "/upload");
            //判断目录是否存在
            if(!filePath.exists()){
                filePath.mkdir();//创建目录
            }

            //重新创建文件的名称
            //获取文件原名称
            String filename = file.getOriginalFilename();
            //截取文件的后缀  从最后一个点开始
            String hzName = filename.substring(filename.lastIndexOf("."));
            //以时间戳为名称创建一个新的文件名字
            String newFileName = System.currentTimeMillis() + hzName;

            try {
                //上传
                file.transferTo(new File(filePath, newFileName));
                System.out.println("上传成功");
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("上传失败");
            }
        }
    }
}

12 SSM 框架集成与测试

12.1. 环境配置

12.1.1. IDEA 下创建项目

创建Maven对应的Web项目

12.1.2. 配置 pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xxxx</groupId>
  <artifactId>SSM</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>SSM Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- junit 测试 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!-- spring 核心jar -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring 测试jar -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring事务 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- aspectj切面编程的jar -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>


    <!-- c3p0 连接池 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>

    <!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.3</version>
    </dependency>

    <!-- 添加mybatis与Spring整合的核心包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
    </dependency>

    <!-- mysql 驱动包 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
    </dependency>

    <!-- 日志打印相关的jar -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.2</version>
    </dependency>

    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.10</version>
    </dependency>

    <!-- spring web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring mvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- web servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>

    <!-- 添加json 依赖jar包(注意版本问题) -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.10.0</version>
    </dependency>

    <!-- commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.2</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>ssm</finalName>

    <!--
      Maven 项目:如果源代码(src/main/java)存在xml、properties、tld 等文件
      Maven 默认不会自动编译该文件到输出目录,如果要编译源代码中xml properties tld 等文件
      需要显式配置 resources 标签
   -->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
          <include>**/*.tld</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>

    <plugins>
      <!-- 编译环境插件 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>11</source>
          <target>11</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <!-- jetty插件 -->
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>9.4.27.v20200227</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>
          <!-- 设置端口 -->
          <httpConnector>
            <port>8080</port>
          </httpConnector>
          <!-- 设置项目路径 -->
          <webAppConfig>
            <contextPath>/ssm</contextPath>
          </webAppConfig>
        </configuration>
      </plugin>
    </plugins>

  </build>
</project>

12.1.3 配置 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

  <!-- 启动spring容器-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
  </context-param>
  <!-- 设置监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 编码过滤 utf-8 -->
  <filter>
    <description>char encoding filter</description>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- servlet请求分发器 -->
  <servlet>
    <servlet-name>springMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:servlet-context.xml</param-value>
    </init-param>
    <!-- 表示启动容器时初始化该Servlet -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <!-- 这是拦截请求, "/"代表拦截所有请求,"*.do"拦截所有.do请求 -->
    <url-pattern>/</url-pattern>
    <!--<url-pattern>*.do</url-pattern>-->
  </servlet-mapping>
</web-app>
12.1.4 配置 servlet-context.xml

在项目的 src/main/resources 下创建 servlet-context.xml 文件, 内容如下

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        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">

    <!-- 开启扫描器 -->
    <context:component-scan base-package="com.xxxx.ssm.controller" />

    <!-- mvc 注解驱动 并添加json 支持 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <!-- 返回信息为字符串时 处理 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <!-- 将对象转换为json 对象 -->
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>


    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>


    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀:在WEB-INF目录下的jsp目录下 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 后缀:以.jsp结尾的资源 -->
        <property name="suffix" value=".jsp"/>
    </bean>


    <!-- 文件上传 -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 允许文件上传的最大尺寸 -->
        <property name="maxUploadSize">
            <value>104857600</value>
        </property>
        <!--
          设置文件放入临时文件夹的最大大小限制。
          此值是阈值,低于此值,则保存在内存中,如高于此值,则生成硬盘上的临时文件。
        -->
        <property name="maxInMemorySize">
            <value>4096</value>
        </property>
    </bean>
</beans>
12.1.5 配置 spring.xml

在项目的 src/main/resources 下创建 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:aop="http://www.springframework.org/schema/aop" 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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 扫描基本包 -->
    <context:component-scan base-package="com.xxxx.ssm" >
        <!--  context:exclude-filter标签:排除对某个注解的扫描 (过滤controller层) -->
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- 加载properties 配置文件 -->
    <context:property-placeholder location="classpath:db.properties" />

    <!-- aop -->
    <aop:aspectj-autoproxy />

    <!-- 配置c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

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

    <!-- 设置事物增强 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!-- aop 切面配置 -->
    <aop:config>
        <aop:pointcut id="servicePointcut"
                      expression="execution(* com.xxxx.ssm.service..*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
    </aop:config>

    <!-- 配置 sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis.xml" />
        <property name="mapperLocations" value="classpath:com/xxxx/ssm/mapper/*.xml" />
    </bean>

    <!-- 配置扫描器 -->
    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描com.xxxx.ssm.dao这个包以及它的子包下的所有映射接口类 -->
        <property name="basePackage" value="com.xxxx.ssm.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
</beans>
12.1.6 配置 mybatis.xml

在项目的 src/main/resources 下创建 mybatis.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>
    <typeAliases>
        <package name="com.xxxx.ssm.po"/>
    </typeAliases>
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
</configuration>
12.1.7 配置 db.properties

在项目的 src/main/resources 下创建 db.properties 文件,内容如下(mysql 创建数据库ssm)

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/s?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username=root
jdbc.password=123456
12.1.8 添加 log4j.properties

在项目的 src/main/resources 下创建 log4j.properties 文件,内容如下

log4j.rootLogger=DEBUG, Console
# Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

12.2 添加源代码

12.2.1. 添加包

在项目的 src/main/java 下创建对应的包结构

com.yjxxt.ssm.controller

com.yjxxt.ssm.service

com.yjxxt.ssm.mapper

com.yjxxt.ssm.dao

com.yjxxt.ssm.po

12.2.2. 添加 User.java

在 com.xxxx.ssm.po 包下创建 JavaBean 文件 User.java (数据库字段对应如下)
在这里插入图片描述

package com.xxxx.ssm.po;

import java.util.Date;

public class User {
  
    private Integer userId;
    private String userName;
    private String userPwd;
    private String userEmail;
    private Date createDate;
    private Date updateDate;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }
}
12.2.3 添加UserDao.java 接口

com.xxxx.ssm.dao 包下创建 UserDao.java 文件,提供对应的用户详情查询功能

package com.xxxx.ssm.dao;

import com.xxxx.ssm.po.User;

public interface UserDao {
    public User queryUserById(Integer id);
}

12.2.4 添加UserMapper.xml 映射文件

com.xxxx.ssm.mapper 包下创建 UserMapper.xml 文件,提供select 查询标签配置

<?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.xxxx.ssm.dao.UserDao">
    <select id="queryUserById" parameterType="int" resultType="com.xxxx.ssm.po.User">
        select
            empno as userId,ename userName,job userPwd,mgr userEmail
        from
            emp
        where empno = #{id}
    </select>
</mapper>
12.2.5 添加 UserService.java

com.xxxx.ssm.service 包下创建UserService.java 文件,提供用户详情查询方法

package com.xxxx.ssm.service;

import com.xxxx.ssm.dao.UserDao;
import com.xxxx.ssm.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public User queryUserById(Integer id){
        return userDao.queryUserById(id);
    }
}

12.2.6 添加 HelloController.java

在 com.xxxx.ssm.controller 包下创建 HelloController.java 文件 (userService有红色波浪线,不影响运行)

package com.xxxx.ssm.controller;

import com.xxxx.ssm.po.User;
import com.xxxx.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.net.http.HttpRequest;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("queryUser")
    public String queryUserById(Integer id, HttpServletRequest request){
        User user = userService.queryUserById(id);
        request.setAttribute("user",user);
        return "ssm";
    }
}

12.2.7 添加 ssm.jsp 视图文件

在src/main/webapp/WEB-INF 创建jsp 目录,并在该目下创建ssm.jsp ,展示查询的用户信息

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 20:10
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h2>${user.userName}</h2>
</body>
</html>

12.3 执行测试

12.3.1. Idea 下配置 jetty 启动命令
12.3.2 启动 jetty 浏览器访问http://localhost:8080/ssm/queryUser?id=7369查看结果

13 RestFul URL

13.1 基本概念

模型 - 视图 - 控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计思想。

Restful 风格的 API 是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条

件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易

于实现缓存等机制。

在Restful 风格中,用户请求的 url 使用同一个 url,用请求方式:get,post,delete,put…等方式

对请求的处理方法进行区分,这样可以在前后台分离式的开发中使得前端开发人员不会对请求的资源地

址产生混淆和大量的检查方法名的麻烦,形成一个统一的接口。

在 Restful 风格中,现有规定如下:

  • GET(SELECT):从服务器查询,可以在服务器通过请求的参数区分查询的方式。

  • POST(CREATE):在服务器端新建一个资源,调用 insert 操作。

  • PUT(UPDATE):在服务器端更新资源,调用 update 操作。

  • PATCH(UPDATE):在服务器端更新资源(客户端提供改变的属性)。(目前 jdk7 未实现,

tomcat7 不支持)。

  • DELETE(DELETE):从服务器端删除资源,调用 delete 语句。

13.2 SpringMVC 支持 RestFul URL 风格设计

案例:如何使用 Java 构造没有扩展名的RESTful url,如 /forms/1?

SpringMVC 是通过 @RequestMapping 及 @PathVariable 注解提供的。

通过如@RequestMapping(value=“/blog /{id}”, method = RequestMethod.DELETE),即可处理

/blog/1 的 delete 请求。

@PathVariable放在形参前,将其变为地址参数

13.3 RestFul URL 映射地址配置

13.3.1. 准备环境
  • 注意:表换成了ssm需要改db.properties里面的路径
  • json格式需要在postman上输入
13.3.1.1. 添加 Account

在 src/resources/java 对应的 com.xxxx.ssm.po 目录下新建 Account.java 实体类

package com.xxxx.ssm.po;

import java.util.Date;

public class Account {
    private Integer accountId;
    private String accountName;
    private String accountType;
    private Double money;
    private Integer userId;
    private Date createDate;
    private Date updateDate;
    private String remark;

    public Integer getAccountId() {
        return accountId;
    }

    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountType() {
        return accountType;
    }

    public void setAccountType(String accountType) {
        this.accountType = accountType;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

}
13.3.1.2 添加 AccountDao

在 src/resources/java 对应的 com.xxxx.ssm.dao 目录下新建 AccountDao.java 接口类

package com.xxxx.ssm.dao;

import com.xxxx.ssm.po.Account;

public interface AccountDao {

    //查询对象信息
    public Account queryAccountById(Integer id);

    //修改对象信息
    public Integer updateAccount(Account account);

    //添加对象信息
    public Integer addAccount(Account account);

    //删除对象信息
    public Integer deleteAccount(Integer id);
}

13.3.1.3 添加 AccountMapper

在 src/resources/java 对应的 com.xxxxt.ssm.mapper 目录下新建 account.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.xxxx.ssm.dao.AccountDao">
    <select id="queryAccountById" parameterType="int" resultType="com.xxxx.ssm.po.Account">
        select
            account_id as accountId,account_name accountName,money,account_type accountType, create_date createDate,update_date updateDate,remark
        from
            tb_account
        where account_id = #{id}
    </select>

    <update id="updateAccount" parameterType="com.xxxx.ssm.po.Account">
        update
            tb_account
        set
            account_name = #{accountName},
            money = #{money},
            account_type = #{accountType},
            create_date = now(),
            update_date = now()
        where
            account_id = #{accountId}
    </update>

    <insert id="addAccount" parameterType="com.xxxx.ssm.po.Account">
        insert into
            tb_account
            (account_name,money,account_type,create_date,update_date)
        values
            (#{accountName},#{money},#{accountType},now(),now())
    </insert>

    <delete id="deleteAccount" parameterType="int">
        delete from tb_account where account_id = #{id}
    </delete>
</mapper>
13.3.1.4 添加 AccountService

在 src/resources/java 对应的 com.xxxx.ssm.service 目录下新建 AccountService.java

package com.xxxx.ssm.service;

import com.xxxx.ssm.dao.AccountDao;
import com.xxxx.ssm.po.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;

    //查询对象信息
    public Account queryAccountById(Integer id){
        return accountDao.queryAccountById(id);
    }

    //修改对象信息
    public Integer updateAccount(Account account){
        return accountDao.updateAccount(account);
    }

    //添加对象信息
    public Integer addAccount(Account account){
        return accountDao.addAccount(account);
    }

    //删除对象信息
    public Integer deleteAccount(Integer id){
        return accountDao.deleteAccount(id);
    }
}

13.3.1.5 添加AccountController
package com.xxxx.ssm.controller;

import com.xxxx.ssm.po.Account;
import com.xxxx.ssm.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

@Controller
public class AccountController {

    @Autowired
    private AccountService accountService;

    //查询对象信息
    @GetMapping("account/{id}")
    public String queryAccountById(@PathVariable Integer id, HttpServletRequest request){
        Account account = accountService.queryAccountById(id);
        if(account == null){
            request.setAttribute("code","400");
            request.setAttribute("msg","数据不存在");
        }else{
            request.setAttribute("code","200");
            request.setAttribute("msg","数据查询成功");
            request.setAttribute("account",account);
        }
        return "result";
    }

    //修改对象信息
    @PutMapping("account")
    public String updateAccount(Account account,HttpServletRequest request){
        //返回受影响行数
        Integer index = accountService.updateAccount(account);
        if(index > 0){
            request.setAttribute("code","200");
            request.setAttribute("msg","修改成功");
        }else{
            request.setAttribute("code","400");
            request.setAttribute("msg","修改失败");
        }
        return "result";
    }

    //添加对象信息
    @PostMapping("account")
    public String addAccount(@RequestBody Account account, HttpServletRequest request){
        Integer index = accountService.addAccount(account);
        if(index > 0){
            request.setAttribute("code","200");
            request.setAttribute("msg","修改成功");
        }else{
            request.setAttribute("code","400");
            request.setAttribute("msg","修改失败");
        }
        return "result";
    }

    //删除对象信息
    @DeleteMapping("account/{id}")
    public String deleteAccount(@PathVariable Integer id,HttpServletRequest request){
        Integer index = accountService.deleteAccount(id);
        if(index > 0){
            request.setAttribute("code","200");
            request.setAttribute("msg","删除成功");
        }else{
            request.setAttribute("code","400");
            request.setAttribute("msg","删除失败");
        }
        return "result";
    }
}

13.3.1.6 添加result.jsp

web下面

<%--
  Created by IntelliJ IDEA.
  User: 龙
  Date: 2022/4/28
  Time: 20:10
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    状态码:${code}
    msg:${msg}

    查询到的数据:${account.accountName}
</body>
</html>

14 全局异常统一处理

14.1. 全局异常概念

在 JavaEE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处

理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统

的代码耦合度高,工作量大且不好统一,维护的工作量也很大。

SpringMVC 对于异常处理这块提供了支持,通过 SpringMVC 提供的全局异常处理机制,能够将所有

类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统

一处理和维护。

全局异常实现方式 Spring MVC 处理异常有 3 种方式:

  1. 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver

使用 SimpleMappingExceptionResolver 进行异常处理,具有集成简单、有良好的扩展性、对已有代

码没有入侵性等优点,但该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据

的情况不适用。

  1. 实现 Spring 的异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器

使用实现 HandlerExceptionResolver 接口的异常处理器进行异常处理,具有集成简单、有良好的扩

展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更

详细的异常处理信息。

  1. 使用 @ExceptionHandler 注解实现异常处理

利用 @ExceptionHandler 注解完成全局异常处理,不需要依赖spring配置环境 ,

但是必须要求 需要异常处理的接口/类继承当前类,对代码有严重的侵略性

14.2. 异常处理实现

14.2.1. 全局异常处理方式一
14.2.1.1. 配置简单异常处理器

servlet-context.xml

配置 SimpleMappingExceptionResolver 对象

<!-- 配置全局异常统一处理的 Bean (简单异常处理器) -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- 页面在转发时出现异常,设置默认的错误页面 (error代表的是一个视图) -->
        <property name="defaultErrorView" value="error"></property>
        <!-- 异常发生时,设置异常的变量名 -->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>

error.jsp(webapp下)

可以在处理异常的页面获取异常信息${ex}

<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>页面异常</h2>
${ex}
</body>
</html>

测试

ExceptionController.java

package com.xxxx.ssm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {

    @RequestMapping("testEx")
    public void test(){
        int i = 1/0;
    }
}
14.2.1.2 使用自定义异常
  • 添加参数异常,业务异常

  • 设置自定义异常与页面的映射

  • 添加jsp页面

  • 获取参数(${ex.msg}, ${ex.message})(msg自己设置,message是RuntimeException自带的)

BusinessException.java

package com.xxxx.ssm.exceptions;

/**
 * 自定义异常:业务异常
 */
public class BusinessException extends RuntimeException {
    private Integer code=400;
    private String msg="业务异常!";


    public BusinessException() {
        super("业务异常!");
    }

    public BusinessException(String msg) {
        super(msg);
        this.msg = msg;
    }

    public BusinessException(Integer code) {
        super("业务异常!");
        this.code = code;
    }

    public BusinessException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

ParamsException.java

package com.xxxx.ssm.exceptions;

/**
 * 自定义异常:业务异常
 */
public class BusinessException extends RuntimeException {
    private Integer code=400;
    private String msg="业务异常!";


    public BusinessException() {
        super("业务异常!");
    }

    public BusinessException(String msg) {
        super(msg);
        this.msg = msg;
    }

    public BusinessException(Integer code) {
        super("业务异常!");
        this.code = code;
    }

    public BusinessException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

params_error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>参数异常页面</h2>
    ${ex.msg}
    ${ex.message}

    <%-- ${msg}
    ${code}--%>
</body>
</html>

buss_error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>业务异常页面</h2>
    <%-- ${msg}
    ${code}--%>
</body>
</html>

测试,ExceptionController.java

package com.xxxx.ssm.controller;

import com.xxxx.ssm.exceptions.ParamsException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {

    @RequestMapping("testEx")
    public void test(Integer id){
        //int i = 1/0;
        if(id == null){
            throw  new ParamsException("参数未接受到");
            // throw  new BusinessException("业务正在开发中.....");
        }
    }
}
14.2.2全局异常处理方式二(推荐)
14.2.2.1. 实现 HandlerExceptionResolver 接口
/**
* 全局异常统一处理 
*/
@Component
public class ExceptionController implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("ex", "默认错误信息");
        return mv;
    }
}

14.2…2.2 自定义异常处理
@Controller
public class ExceptionController implements HandlerExceptionResolver {

    @RequestMapping("testEx")
   /* public void test(Integer id) {
        //int i = 1/0;
        if (id == null) {
            throw new ParamsException("参数未接受到");
            // throw  new BusinessException("业务正在开发中.....");
        }
    }*/
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("ex", "默认错误信息");
        // 判断是否是自定义异常
        if (ex instanceof ParamsException) {
            mv.setViewName("params_error");
            ParamsException e = (ParamsException) ex;
            mv.addObject("ex", e.getMsg());
        }
        if (ex instanceof BusinessException) {
            mv.setViewName("buss_error");
            BusinessException e = (BusinessException) ex;
            mv.addObject("ex", e.getMsg());
        }
        return mv;
    }
}
14.2.3 全局异常处理方式三

页面处理器继承 BaseExceptionController

package com.xxxx.ssm.controller;

import com.xxxx.ssm.exceptions.BusinessException;
import com.xxxx.ssm.exceptions.ParamsException;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 利用 @ExceptionHandler 注解完成全局异常处理,不需要依赖spring配置环境 ,
 *      但是必须要求 需要异常处理的接口/类继承当前类,对代码有严重的侵略性
 */
public class BaseExceptionController {

    @ExceptionHandler
    public String exc(HttpServletRequest request, HttpServletResponse response, Exception ex){
        request.setAttribute("ex", ex);
        if(ex instanceof ParamsException){
            return "params_error";
        }
        if(ex instanceof BusinessException){
            return "buss_error";
        }
        return "error";
    }
}
package com.xxxx.ssm.controller;

import com.xxxx.ssm.exceptions.BusinessException;
import com.xxxx.ssm.exceptions.ParamsException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class ExceptionController extends BaseExceptionController {
    @RequestMapping("testEx")
    public void test(Integer id) {
        //int i = 1/0;
        if (id == null) {
            throw new ParamsException("参数未接受到");
            // throw  new BusinessException("业务正在开发中.....");
        }
    }
}

14.3 未捕获异常的处理

对于 Unchecked Exception 而言,由于代码不强制捕获,往往被忽略,如果运行期产生了

Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的 404、

500……等服务器内部错误提示页面。

此时需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在 web.xml 中通过

(Websphere/Weblogic)或者(Tomcat)节点配置特定异常情况的显示页面。修改 web.xml 文件,增加以

下内容:

 <!-- 出错页面定义 -->
  <error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/500.jsp</location>
  </error-page>
  <error-page>
    <error-code>300</error-code>
    <location>/300.jsp</location>
  </error-page>
  <error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
  </error-page>

jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>404</h2>
    <h2>功能正在开发中</h2>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>500</h2>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>数据异常,请重试</h2>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值