SpringMVC学习笔记

1 SpringMVC 简介

1.1 MVC 架构
  • MVC 是模型(Model)、视(View)、控制器(Controller)的简写,是一种软件设计规范

  • MVC 架构可以将业务逻辑、视图、数据库三者清晰地分离开来,各司其职互不干扰

  • MVC 架构主要作用是降低了视图、业务逻辑之间的双向耦合

  • MVC 不是一种设计模式,而是一种架构模式

1.2 什么是 SpringMVC

SpringMVC 是 Spring 的一部分,是基于 Java 实现的 MVC 的轻量级 Web 框架

SpringMVC 有以下特点:

  • 轻量级,简单易学,功能强大

  • 是基于请求响应的 MVC 框架,效率较高

  • 与 Spring 的兼容性好,且约定大于配置

  • SpringMVC 的框架是围绕 DispatcherServlet 进行设计

  • 使用的人很多,范围很广

2 SpringMVC 原理

2.1 测试例子

接下来实现一遍 SpringMVC 的原理(实际开发不这样写)

首先在核心文件 web.xml 中配置 DispatcherServlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--注册DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--关联一个SpringMVC的配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动级别-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

然后要去 /resources/springmvc-servlet.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"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

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

    <!--Handler-->
    <bean id="/hello" class="com.jiuxiao.controller.HelloController"/>
</beans>

然后就是 Controller 的编写

package com.jiuxiao.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

/**
 * Hello
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/07 10:08
 * @since: 1.0.0
 */
public class HelloController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //模型视图
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "Hello SpringMVC!");
        //配置文件中有前缀和后缀,拼接之后就是 /WEB-INF/jsp/hello.jsp
        modelAndView.setViewName("hello");
        return modelAndView;
    }
}

在我们要测试的 hello 页面中,取到传过来的值

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

启动 Tomcat,访问 hello 页面,发现报错 404,资源未找到

image-20220507101934687

这是很常见的错误,IDEA 的问题,只要在项目的 Artifacts 中增加一个 lib 目录,然后将项目所需的 jar 包复制过去就行了

image-20220507102120717

image-20220507102221487

配置好之后,重启 Tomcat,已经可以正常访问

image-20220507102302271

2.2 原理分析

SpringMVC 大致原理如下:

  • 用户发起请求,被前置的控制器拦截到请求

  • 根据请求的参数生成代理请求,找到请求对应的实际控制器

  • 控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器

  • 控制器使用视图与视图渲染的结果,将结果返回给中心控制器

  • 最后,将结果返回给用户

image-20220505212250955

执行流程再分析

image-20220507150403018

  1. DispatcherServlet 表示前置控制器,是整个 SpringMVC 的控制中心,当用户发出请求时,DispatcherServlet 就会接受并且拦截该请求
  1. HandlerMapping 为处理器映射,DispatcherServlet 调用 HandlerMapping,HandlerMapping 根据请求的 url 查找 Handler

  2. HandlerExecution 表示具体的 Handler,主要作用是根据 url 查找出控制器

  3. HandlerExecution 将解析后的信息传递给 DispatcherServlet

  4. HandlerAdapter 表示处理适配器,按照特定的规则去执行 Handler

  5. Handler 让具体的 Controller 执行

  6. Controller 将具体的执行信息返回给 HandlerAdapter

  7. HandlerAdapter 将视图逻辑名或者模型传递给 DispatcherServelt

  8. DispatcherServelt 调用视图解析器(ViewResolver)解析 HandlerAdapter 传递的逻辑视图名

  9. 视图解析器将解析的逻辑视图名传递给 DispatcherServlet

  10. DispatcherServlet 根据视图解析器解析的视图结果,调用具体的视图

  11. 最终视图呈现给用户

3 使用注解正式开发

  1. 新建一个子模块,使用默认的 Maven 空项目

  2. 项目右键,添加该项目为 web 项目

  3. 然后因为 IDEA 的原因,要在项目的 Artifacts 的 lib 中加入此项目所需的 jar 包

  4. 配置 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <!--注册Servlet-->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--关联SpringMVC配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc-servlet.xml</param-value>
        </init-param>
    </servlet>
    <!--所有的请求都会被 DispatcherServlet拦截,注意 / 和 /* 区别-->
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  1. 配置 spring-mvc-servlet.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
       https://www.springframework.org/schema/beans/spring-beans.xsd>
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--自动扫描包,让指定位置下的注解生效,由 IOC 容器统一管理-->
    <context:component-scan base-package="com.jiuxiao.controller"/>
    <!--让SpringMVC不处理静态资源-->
    <mvc:default-servlet-handler/>
    <!--支持MVC注解驱动-->
    <mvc:annotation-driven/>
    
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  1. 编写控制器 HelloController
package com.jiuxiao.controller;

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

/**
 * Hello控制器
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/07 15:59
 * @since: 1.0.0
 */
@Controller
public class HelloController {
    
    @RequestMapping("/hello")
    public String hello(Model model){
        //封装数据
        model.addAttribute("msg", "Hello, SpringMVC Annotation!");
        //这里的返回值,就是我们所要请求的页面,会直接被视图解析器解析
        return "hello";     
    }
}
  1. 编写视图层 hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${msg}
</body>
</html>

到此为止,我们的使用注解开发的步骤完成,启动 Tomcat,成功访问

image-20220507163355723

4 控制器 Controller

  • 控制器负责提供访问应用程序的行为,通常通过实现接口或者注解定义两种方式实现

  • 控制器负责解析用户的行为,并且将其转换为一个模型

  • 在 SpringMVC 中,一个控制器可以包含多个方法

  • 在 SpringMVC 中,对于 Controller 的配置方式有很多种

  • 被他注解的类,会被 Spring 默认接管

  • 被它注解的类中,所有的返回值为 String,并且有具体的页面可以跳转的方法,都会被视图解析器解析

4.1 重写接口方式
  1. 在核心配置文件中配置 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  1. 在 spring-mxv.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"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  1. 实现 Controller 接口
public class ControllerTest01 implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "test01");
        modelAndView.setViewName("test01");
        return modelAndView;
    }
}
  1. 配置该控制器对应的 bean
<bean name="/t1" class="com.jiuixao.controller.ControllerTest01"/>
  1. 在对应的前端视图页面取值
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
	${msg}
</body>
</html>
5.2 注解方式
  1. 在核心配置文件中配置 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  1. 在 spring-mxv.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"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--扫描路径-->
    <context:component-scan base-package="com.jiuixao.controller"/>
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  1. 然后就是 Controller 类
@Controller
public class ControllerTest02 {

    @RequestMapping("/t2")
    public String test2(Model model){
        model.addAttribute("msg", "Hello SpringMVC 02");
        return "test01";
    }
}

5 RestFul 风格

RestFul 就是一个资源定位已经资源操作的风格,既不是标准也不是协议,只是一种风格

基于该风格设计的软件可以更加简洁、层次化,更易实现缓存,而且也更加具有安全性

现在我们假设,前端传递过来两个参数,我们实现两个数相加并显示

普通的方式:需要前端直接在 url 里进行传参,即 localhost:8088/add?a=2&b=5

@Controller
public class RestFukController {

    @RequestMapping("/add")
    public String test01(int a, int b, Model model){
        int res = a + b;
        model.addAttribute("msg", "结果为:" + res);
        return "test01";
    }
}

image-20220508162426527

RestFul 风格:使用 @PathVariable 注解,让方法参数的值对应绑定到一个 url 模板变量上,即 localhost:8088/add/2/5

@Controller
public class RestFulController {

    @RequestMapping("/add/{a}/{b}")
    public String test01(@PathVariable int a, @PathVariable int b, Model model){
        int res = a + b;
        model.addAttribute("msg", "结果为:" + res);
        return "test01";
    }
}

image-20220508163001183

6 结果跳转方式

6.1 SpringMVC 转发
@RequestMapping("/hello/t1")
public String test01(){
    return "forward:/index.jsp";
}
6.2 SpringMVC 重定向
@RequestMapping("/hello/t2")
public String test02(){
    return "redirect:/index.jsp";
}

7 数据处理

7.1 处理提交的数据
  1. 提交的参数名称和处理方法的参数名一致
//提交的数据:http://localhost:8088/test?name=jack

//处理方法
@RequestMapping("/test")
public String test01(String name){
    return "test";
}
  1. 提交的参数名称与处理方法的参数名不一致
//提交的数据:localhost:8088/test?username=jack

//处理方法
@RequestMapping("/test")
public String test01(@RequestParam("username") String name){
    return "test";
}
  1. 提交的是一个对象

该情况下需要提交的表单域和对象的属性名一致,参数使用对象即可

//实体类
public class User {
    private int id;
    private String name;
    private int age;
}

//提交的数据中的参数名必须与对象的属性名一致,否则会接收不到
//提交的数据:http://localhost:8088/test?id=1&name=jack&age=18

//处理方法
@RequestMapping("/test")
public String test02(User user){
    return "test";
}
7.2 数据显示到前端
  1. 通过 ModelAndView
public class ControllerTest01 implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "test01");
        modelAndView.setViewName("test01");
        return modelAndView;
    }
}
  1. 通过 ModelMap
@RequestMapping("/test")
public String test01(@RequestParam("username") String name, ModelMap modelMap) {
    modelMap.addAttribute("msg", name);
    return "test";
}
  1. 通过 Model
@RequestMapping("/test")
public String test02(@RequestParam("username") String name, Model model) {
    model.addAttribute("msg", name);
    return "test";
}
7.3 乱码处理

在前端提交中文数据是很常见的事情,既然出现了中文,那么乱码问题也就不可避免

首先建立一个 codetest.jsp 页面,用来模拟提交表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/code/t1" method="post">
    姓名:<input type="text" name="name">
    <input type="submit">
</form>
</body>
</html>

然后在后台接收前端输传回的数据

@Controller
public class EncodingController {

    @PostMapping("/code/t1")
    public String test01(@RequestParam("name") String name, Model model){
        System.out.println(name);		//在这里已经是乱码了
        model.addAttribute("msg", name);
        return "test01";
    }
}

启动 Tomcat,输入中文数据之后,点击提交,发现后端接收到的数据果然是乱码

image-20220509205129166

image-20220509205143420

怎么解决乱码呢?导致乱码的问题千奇百怪,需要一个个去排除

一般情况下,使用 SpringMVC 提供的过滤器的功能就可以解决乱码问题了,直接在 web.xml 中进行配置

<!--配置SpringMvC编码过滤器-->
<filter>
    <filter-name>encoding</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>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

配置好之后,清除浏览器缓存,再次测试,发现乱码问题被解决

image-20220509212341428

一般情况下 SpringMVC 自带的过滤器都会解决乱码问题,但也有例外,那么就要从 Tomcat 配置、项目总配置其他方向着排查了

8 JSON 对象

8.1 什么是 json
  • JSON 是一种轻量级的数据交换格式,使用特别广泛

  • 它采用完全独立于编程语言的文本格式来存储和表示数据

  • 简洁和清晰的结构使得 JSON 成为一个理想的数据交换语言,提高2网络传输的效率

let user = {
   name: '荒天帝',
   age: 18,
   sex: '男'
};

//对象转换为 json 字符串
let jsonUser = JSON.stringity(user);	// {"name":"荒天帝", "age":"18", "sex":"男"}

//json 字符串解析为对象
JSON.parse(jsonUser);		//{mame: '荒天帝', age: 18, sex: '男'}
8.2 Jackson

Jackson 应该是目前比较好的一种 json 解析工具了,他可以帮助我们使用 Controller 返回给前端 json 数据

  1. 首先,需要导入它所对应的依赖
<!--Jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2.2</version>
</dependency>
  1. 在 spring-mvc.xml 中配置编码,这样可以解决转换的 json 中文乱码问题
<?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
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--包扫描-->
    <context:component-scan base-package="com.jiuxiao.controller"/>
    <!--注解支持,包括 json 乱码问题-->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!--静态资源过滤-->
    <mvc:default-servlet-handler/>

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  1. 一个 SpringMVC 支持的项目,配置必不可少
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--配置SpringMVC编码过滤器-->
    <filter>
        <filter-name>encoding</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>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
  1. 然后创建一个 User 实体类用于测试
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}
  1. 编写 Controller 类
@Controller
public class UserController {

    @RequestMapping("/t1")
    @ResponseBody   //加上该注解后,就不会走视图解析器,会直接返回一个字符串
    public String test01() throws JsonProcessingException {
        List<User> list = new ArrayList<User>();

        list.add(new User(1, "林枫", 24));
        list.add(new User(2, "梦情", 18));
        list.add(new User(3, "段欣叶", 20));
        list.add(new User(4, "唐幽幽", 22));
        return new ObjectMapper().writeValueAsString(list);
    }
}

启动项目,成功将对象转为 json 数据对象

image-20220510105501086

8.3 fastjson

fastJson 是阿里的针对 Json 解析的一个工具类库

  1. 首先要导入依赖,其余就是常规的 web.xml、spring-mvc.xml 配置
<!--fastjson依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.2</version>
</dependency>
  1. 仍然使用上方例子中的 user 对象
@RequestMapping("/t2")
@ResponseBody
public String test02(){
    List<User> userList = new ArrayList<User>();

    userList.add(new User(1, "林枫", 24));
    userList.add(new User(2, "梦情", 18));
    userList.add(new User(3, "段欣叶", 20));
    userList.add(new User(4, "唐幽幽", 22));
    return JSON.toJSONString(userList);
}

启动项目,成功转换为 json 对象

image-20220510195647236

9 Ajax 技术

9.1 Ajax 简介
  • Ajax 全名为 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)

  • Ajax 是一种在无需重新加载整个网页的情况下,能够异步刷新部分网页的技术

  • Ajax 不是一种语言,而是一种可以更好、更快速的、交互性更强的 Web 应用技术

  • 使用了 Ajax 的网页,通过在后台服务器进行少量的数据交换,就可以实时异步更新

9.2 Ajax 接收数据
  1. 首先创建一个测试用的实体类 User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
    private String sex;
}
  1. 然后要有一个前端页面,用来展示数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/statics/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#btn").click(function () {
                $.post("${pageContext.request.contextPath}/test01", function (data) {
                    let newHtml = "";
                    for (let i = 0; i < data.length; i++){
                        newHtml += "<tr>" +
                                "<td>" + data[i].name + "</td>" +
                                "<td>" + data[i].age + "</td>" +
                                "<td>" + data[i].sex + "</td>" +
                                "</tr>"
                    }
                    $("#content").html(newHtml);
                });
            });
        });
    </script>
</head>

<body>
<input type="button" value="加载数据" id="btn">
<table>
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>性别</td>
    </tr>
    <tbody id="content">
    </tbody>
</table>
</body>
</html>
  1. 接下来就是控制器
@RestController
public class UserController {

    @RequestMapping("/test01")
    public List<User> test01(){
        List<User> userList = new ArrayList<User>();
        userList.add(new User("荒天帝", 21, "男"));
        userList.add(new User("伊人泪", 20, "女"));
        userList.add(new User("洛璃", 17, "女"));
        return userList;
    }
}

启动项目,访问 jsp 页面,初始状态如下

image-20220510205332661

然后点击加载数据按钮,我们发现,网页并没有重新加载,但是却通过 Ajax 异步刷新了数据并且展示

image-20220510205437839

9.3 Ajax 验证用户信息
  1. 首先是后台控制类的编写
@RestController
public class UserController {

    @RequestMapping("/t2")
    public String test02(String name, String pwd){
        String msg = "";
        if (name != null){
            msg = "admin".equals(name) ? "OK" : "用户名有误!";
        }
        if (pwd != null){
            msg = "123".equals(pwd) ? "OK" : "密码有误!";
        }
        return msg;
    }
}
  1. 然后是前端 jsp 页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/statics/jquery-3.6.0.js"></script>
    <script>
        function test01() {
            $.post({
                url: "${pageContext.request.contextPath}/t2",
                data: {"name": $("#username").val()},
                success: function (data) {
                    if (data.toString() === 'OK') {
                        $("#nameInfo").html(data).css("color", "green");
                    } else {
                        $("#nameInfo").html(data).css("color", "red");
                    }
                }
            });
        }

        function test02() {
            $.post({
                url: "${pageContext.request.contextPath}/t2",
                data: {"pwd": $("#password").val()},
                success: function (data) {
                    if (data.toString() === 'OK') {
                        $("#pwdInfo").css("color", "green").html(data);
                    } else {
                        $("#pwdInfo").css("color", "red").html(data);
                    }
                }
            });
        }
    </script>
</head>
<body>
<p>
    用户名 : <input type="text" id="username" onblur="test01()">
    <span id="nameInfo"></span>
</p>
<p>
    密码 : <input type="text" id="password" onblur="test02()">
    <span id="pwdInfo"></span>
</p>
</body>
</html>

启动项目,当我们输入错误的用户名、密码之后,只要鼠标失去焦点,就会提示信息错误

image-20220510214012230

然后输入正确的用户名、密码之后,就会显示正确的信息

image-20220510214059780

10 拦截器

  • SpringMVC 的拦截器类似于 Servlet 中的过滤器,对于处理器进行预处理和后处理,开发者可以自定义一些自己的拦截器

  • 拦截器只会拦截访问控制器的方法,如果访问的是 jsp、html、css、js、image 等是不会拦截的

  • SpringMVC 的拦截器是基于 AOP 思想的具体实现

10.1 拦截器执行顺序
  1. 首先新建一个拦截器,实现 HandlerInterceptor 接口,重写该接口的三个方法
/**
 * 自定义拦截器
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/11 09:48
 * @since: 1.0.0
 */
public class MyInterceptor implements HandlerInterceptor {

    //return true : 会放行,执行下一个拦截器
    //return false : 会拦截,不会执行下一个拦截器
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截前");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截后");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("清理");
    }
}
  1. 然后去 spring-mvc 配置拦截器及其拦截路径
<!--拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.jiuxiao.config.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  1. 使用测试类测试
@RestController
public class UserController {
    @GetMapping("/a2")
    public String test03(){
        System.out.println("UserController执行了...");
        return "OK";
    }
}

启动项目,访问 /a2 路径,发现拦截器就是基于 AOP 思想实现的

image-20220511100335008

10.2 登录拦截器

一般情况下,用户不登录是无法直接访问后台首页的,那么怎么用拦截器实现?

  1. 首先依次创建测试页面

index 主页面,就两个链接:后台首页和登录页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h1><a href="${pageContext.request.contextPath}/user/goLogin">点击登录</a></h1>
<h1><a href="${pageContext.request.contextPath}/user/goMain">后台首页</a></h1>
</body>
</html>

login 登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="post">
    用户名: <input type="`text`" name="username">
    密码: <input type="password" name="password">
    <input type="submit" value="提交">
</form>
</body>
</html>

main 后台首页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>这是后台首页</h2>
<span>欢迎用户 : ${username}</span>
<p><a href="${pageContext.request.contextPath}/user/goOut">注销</a></p>
</body>
</html>
  1. 然后是控制器
@Controller
@RequestMapping("/user")
public class LoginController {

    @RequestMapping("/login")
    public String login(HttpSession session, String username, String password, Model model){
        session.setAttribute("userInfo", username);
        model.addAttribute("username", username);
        return "main";
    }

    @RequestMapping("/goMain")
    public String main(){
        return "main";
    }

    @RequestMapping("/goLogin")
    public String login(){
        return "login";
    }

    @RequestMapping("/goOut")
    public String logout(HttpSession session){
        session.removeAttribute("userInfo");
        return "login";
    }
}

此时,我们启动服务后,在 index 页面点击后台管理,他会直接跳转到后台主页,显然这种情况下,用户可以跳过登录直接访问后台页面,这不符合常理

所以就需要使用拦截器进行拦截

public class MyInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        if (request.getRequestURI().contains("goLogin")){
            return true;
        }
        if (request.getRequestURI().contains("login")){
            return true;
        }
        if (session.getAttribute("userInfo") != null){
            return true;
        }
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
        return false;
    }
}

再次启动服务,在主界面点击后台首页,并不会进入,而是跳转到了登录页面

image-20220511110449466

image-20220511110506636

然后输入用户名和密码,点击登录后,表示登陆成功,会跳转到后台首页并显示用户信息

image-20220511110604973

然后不点击注销,直接返回 index 页面,再次点击后台首页链接,发现不用登陆直接进来,这是因为浏览器还没关闭,用户的 Session 还在,所以可以直接进入

当我们点击注销链接后,用户的 Session 会被移除,并且自动跳转到登录页面,此时如果再点击后台首页,并不会进入,会让重新登录

通过拦截器实现的用户登录的简单功能实现了

11 文件上传和下载

11.1 文件上传
  1. 首先需要导入文件上传的依赖
<!--文件上传-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  1. 然后是前端的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="upload">
  </form>
  </body>
</html>
  1. 接下来实现文件上传的 Controller
@RestController
public class FileController {

    //第一种文件上传方式
    @RequestMapping("/upload01")
    public String fileUpload01(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
        //获取文件名
        String uploadFileName = file.getOriginalFilename();
        //文件名为空,直接返回首页
        if ("".equals(uploadFileName)) {
            return "redirect:/index.jsp";
        }
        System.out.println("上传的文件名为:" + uploadFileName);

        //上传路径保存设置
        String path = request.getRealPath("/upload01");
        //如果路径不存在就创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:" + realPath);

        InputStream is = file.getInputStream();
        FileOutputStream os = new FileOutputStream(new File(realPath, uploadFileName));
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len = is.read(buffer)) != -1){
            os.write(buffer, 0, len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }
    
    //第二种文件上传模式
     @RequestMapping("/upload02")
    public String fileUpload02(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
        //上传路径设置
        File realPath = new File(request.getRealPath("/upload02"));
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件的地址是:" + realPath);
        //通过 CommonsMultipartFile 的方法直接写文件
        file.transferTo(new File(realPath + "/" + file.getOriginalFilename()));
        return "redirect:/index.jsp";
    }
}
  1. SpringMVC自带有文件上传,所以要去 spring-mvc.xml 配置
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="maxUploadSize" value="10485706"/>
    <property name="maxInMemorySize" value="40960"/>
</bean>

启动服务,选择一个文件上传,上传成功后重定向到主页,后端控制台输出了文件的信息,只在项目中找到了上传的文件,表示两种方式均上传成功

image-20220511145418630

11.2 文件下载
@RequestMapping("/download")
public String downloadFile(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //要下载图片的地址
    String path = request.getRealPath("/upload01");
    String fileName = "test.jpg";

    //设置响应头
    response.reset();
    response.setCharacterEncoding("UTF-8");
    response.setContentType("multipart/form-data");
    response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));

    File file = new File(path, fileName);
    FileInputStream input = new FileInputStream(file);
    ServletOutputStream output = response.getOutputStream();
    byte[] buffer = new byte[1024];
    int len = 0;
    while ((len = input.read(buffer)) != -1){
        output.write(buffer, 0, len);
        output.flush();
    }
    output.close();
    input.close();
    return "OK";
}
posted @ 2022-05-11 15:21  悟道九霄  阅读(88)  评论(0)    收藏  举报