You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

69 KiB

title author top cover toc mathjax summary tags categories reprintPolicy abbrlink date coverImg img password
SpringMVC TianZD true true true false SpringMVC框架学习笔记,粗略学了一下,没有参考价值 [SpringMVC Java 学习笔记] [java] cc_by ae0f95e0 2022-04-29 11:09:28 <nil> <nil> <nil>

[toc]

SpringMVC

ssm:Spring+SpringMVC+Mybatis

1、MVC

参考

1.1、MVC概述

MVC是一种设计模式,用于应用程序的分层开发

MVC为:

  • Model模型:提供要展示的数据,可以带有逻辑,包含数据和行为,通常将数据和行为分离
    • 数据DAO层
    • 行为Service层,服务层
  • View视图jsp,负责模型包含的数据的可视化,即模型的展示,代表用户界面
  • Controller控制器servlet,接收视图层的用户请求,委托给模型进行处理,将处理完毕返回的数据返回给视图,相当于一个调度员

最典型的MVC模式:JSP+servlet+javaBean

1.2、Model1时代

早期Web开发中,采用Model1模型,分为视图层和模型层。

  • 视图层:负责展示模型、接收请求并调用业务逻辑方法;
  • 模型层:提供要展示的数据;

image-20210814101309684

  • **优点:**架构简单;
  • 缺点:模型层职责不单一,既展示数据,又接收请求并处理。不利于维护!

1.3、Model2时代

将项目分为 M-V-C 三层,即目前使用的 MVC 模式。

  1. 用户输入,发起请求;
  2. 控制层(Servlet)接收请求数据;
  3. 控制层(Servlet)调用模型层(Service)中相应的业务逻辑方法;
  4. 模型层(Service)处理业务,与数据库交互,将数据返回到控制层(Servlet);
  5. 控制层(Servlet)将数据返回给视图层,由视图层渲染页面;
  6. 将页面响应到前端。

img

优点:职责分明,利于维护

  • Model
    • 处理业务逻辑(Service 层);
    • 保存数据的状态(DAO 层);
  • Controller
    • 获取请求;
    • 调用业务逻辑;
    • 响应页面(跳转指定页面);
  • View
    • 显示页面

2、Servlet

参考

新建Maven无模板父工程

删除src,作为父工程

添加依赖

  • SpringMVC
  • JUnit
  • Servlet、JSP
<!--导入依赖-->
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

新建Module,添加webapp支持

新建Module时选择普通Maven项目

新建完成后右键-添加框架支持,勾选web

img

编写代码

  • 登陆页面
<html>
<head>
    <title>Title</title>
</head>
<body>
<div id="app">
    <form action="login" method="post">
        <label>
            用户名:<input type="text" name="username">
        </label>
        <input type="submit">
    </form>
</div>
</body>
</html>
  • servlet类
package com.tian.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginServlet extends HttpServlet {

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      // 1.获取前端参数
      String username = req.getParameter("username");
      if ("admin".equals(username)) {
         req.setAttribute("msg", "管理员");
      } else {
         req.setAttribute("msg", "普通用户");
      }
      // 2.调用业务层

      // 3.视图转发或者重定向
      req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp);
   }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      doGet(req, resp);
   }
}
  • servlet注册
<servlet>
    <servlet-name>loginServlet</servlet-name>
    <servlet-class>com.tian.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>loginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>
  • 成功页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
   <h1>登陆成功:${msg}</h1>
</body>
</html>

3、SpringMVC入门

参考

SpringMVC 官方文档

3.1、简介

Spring Web MVC,简称 SpringMVC

  • Spring Framework 的一部分,是基于 Java Servlet API 实现 MVC 的轻量级 Web 框架。
  • 围绕 DispatcherServlet 设计,将请求分发到不同的处理器。

特点:

  1. 轻量级,简洁灵活,简单易学;
  2. 高效 , 基于请求响应的MVC框架;
  3. Spring 兼容性好,无缝结合;
  4. 约定大于配置
  5. 功能强大:RESTful、数据验证、格式化、本地化、主题等

3.2、中心控制器

  • SpringMVC 围绕 DispatcherServlet 设计,DispatcherServlet 的作用是将请求分发到不同的处理器。
  • Spring 2.5 + Java 5 以上版本,可以采用基于注解Controller 声明。
  • 从继承图可以看出,DispatcherServlet 本质上就是 Servlet

img

3.3、SpringMVC执行原理

  • 前端控制器:即 DispatcherServlet
  • 实际控制器:即实际处理请求的 Controller
  • 模型层:即 Model 层,包括 Service 层和 DAO 层;
  • 视图层:即 View 层。

原理图

img

  1. 用户:发起请求;
  2. 前端控制器:拦截请求,根据请求参数生成代理请求,将请求调度给对应的实际控制器
  3. 控制器:处理请求,调用模型层中相应的业务逻辑方法;
  4. 模型层Service 层处理业务,调用 DAO 层与数据库交互,创建并返回数据模型;
  5. 控制器:将 ModelAndView 返回给前端控制器
  6. 前端控制器:传递 Model视图层
  7. 视图层:渲染视图并返回前端控制器
  8. 前端控制器:响应给用户。

底层源码关系

image-20210815161309751

  • 实线部分不用程序员做,程序员只需要做虚线部分

image-20210815161440258

image-20210815161525228

3.4、HelloSpringMVC-基于xml配置

  1. 新建Module,添加web支持
  1. 导入SpringMVC依赖
  1. 在web.xml中配置DispatchServlet(前端控制器,请求分发器)

这个是SpringMVC的核心

  • 插入dispatcherServlet:alt+ins键,选择servlet,搜索dispatchservlet

  • 关联配置文件

  • 启动优先级

  • <url-pattern>
    
    • /:匹配所有请求,不包括 JSP
    • /*:匹配所有请求及 JSP
    • 如果填写了/*Controller 处理业务后返回的 JSP页面会再次被拦截请求,会无限嵌套。
<!--配置DispatchServlet:请求分发器、前端控制器-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 关联配置文件 -->
    <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>dispatcherServlet</servlet-name>
            <!--只能用/,不能用/*-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

注意

  • /:只匹配所有的请求,不会匹配jsp页面
  • /*:匹配所有的请求,包括jsp页面
  1. 编写springmvc-servlet.xml
  • 处理器映射
  • 处理器适配器
  • 内部资源视图解析器
  • 后续:将 Controller 注册到 IOC 容器中:相当于原来在 web.xml 中注册 Servlet
<?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
       http://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">
        <!-- 前缀,必须有斜杠 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 相当于原来在 web.xml 中注册 Servlet -->
    <bean id="/hello" class="com.tian.controller.HelloController"/>
</beans>
  1. Controller

HelloController.class

最后返回给视图解析器,进行hello名字拼接,拼接成/WEB-INF/jsp/hello.jsp

然后跳转得到hello.jsp页面

public class HelloController implements Controller {
   @Override
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
      ModelAndView modelAndView = new ModelAndView();

      // 业务代码
      String result = "HelloSpringMVC";
      modelAndView.addObject("msg", result);

      //视图跳转
      modelAndView.setViewName("hello");

      return modelAndView;
   }
}
  1. 跳转页面hello.jsp
<%--
  Created by IntelliJ IDEA.
  User: 12038
  Date: 2021/8/15
  Time: 16:17
  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>
<h1>${msg}</h1>
</body>
</html>

过程分析

  1. 用户:发起请求;
  2. web.xml中:
    1. 前端控制器:拦截请求,根据请求参数生成代理请求,根据关联的配置文件springmvc-servlet.xml,将请求调度给对应的实际控制器
    2. 处理器映射器映射到hello
    3. 处理器适配器找到com.tian.controller.HelloController进行处理
  3. 控制器:处理请求,调用模型层中相应的业务逻辑方法HelloController.class;
  4. 模型层Service 层处理业务,调用 DAO 层与数据库交互,创建并返回数据模型ModeAndView对象;
  5. 控制器:将 ModelAndView 返回给前端控制器
  6. 前端控制器:传递 Model视图层
  7. 视图层:渲染视图并返回前端控制器
  8. 前端控制器:响应给用户。

img

易错

  1. 注册 *DispatcherServlet* 的 *url-pattern* 不能是 /*
  2. springmvc-servlet.xml 中:
    • 视图解析器的前缀的结尾必须有/:如/WEB-INF/jsp/
    • Controller 的注册 Bean 必须以 / 开头,如/hello
  3. 以上操作均无误,仍报 404500
    • 创建的 web 模板的Maven项目,理论上不会报错;
    • 创建不带模板的 Maven 项目 + 手动添加 web 框架支持,可能会报错;
    • 需要手动在项目结构中添加 lib 目录:

img

4、基于注解开发SpringMVC

SpringMVC 实际开发中,我们通常都会采用注解开发。

  1. 新建Module,添加web支持
  1. 导入SpringMVC依赖:Spring框架核心库、SpringMVC、servlet、JSTL等
  1. 在web.xml中配置DispatchServlet(前端控制器,请求分发器)

这个是SpringMVC的核心

  • 关联配置文件

  • 启动优先级

  • <url-pattern>
    
    • /:匹配所有请求,不包括 JSP
    • /*:匹配所有请求及 JSP
    • 如果填写了/*Controller 处理业务后返回的 JSP页面会再次被拦截请求,会无限嵌套。

alt+ins键,选择servlet,搜索dispatchservlet

<!--配置DispatchServlet:请求分发器、前端控制器-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 关联配置文件 -->
    <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>dispatcherServlet</servlet-name>
            <!--只能用/,不能用/*-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

注意

  • /:只匹配所有的请求,不会匹配jsp页面
  • /*:匹配所有的请求,包括jsp页面
  1. springmvc-servlet.xml

基于配置的SpringMVC的配置相比:

  • 添加注解支持,不再需要手动注册 Controller
  • 内部资源视图解析器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 扫描包 -->
    <context:component-scan base-package="com.tian.controller"/>
    <!-- 过滤静态资源 -->
    <mvc:default-servlet-handler/>
    <!-- 注解驱动 -->
    <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. Controller

HelloController.class

最后返回给视图解析器,进行hello名字拼接,拼接成/WEB-INF/jsp/hello.jsp

然后跳转得到hello.jsp页面

@Controller
@RequestMapping("/hello")       //配置总地址,可以有可无
public class HelloController {

   /**
    * 使用 @RequestMapping注解来处理映射关系,后接请求路径(如"/hello")
    * 方式一:使用 ModelAndView来封装模型和视图,返回 ModelAndView
    */
   @RequestMapping("/hello")
   public ModelAndView hello(ModelAndView mv) {
      // 可以调用Service层方法,获取模型对象

      // 添加数据模型
      mv.addObject("msg", "Hello Annotation:ModelAndView");
      // 封装要渲染、跳转的视图
      mv.setViewName("hello");

      return mv;
   }

   /**
    * 使用 @RequestMapping注解来处理映射关系,后接请求路径(如"/hello")
    * 方式二:使用 Model来封装模型,返回 ViewName
    */
   @RequestMapping("/hello1")      //真实访问地址:localhost:8080/项目名/hello/hello1
   public String hello(Model model) {
      // 可以调用Service层方法,获取模型对象

      // 添加数据模型
      model.addAttribute("msg", "Hello Annotation:Model");
      // 返回视图
      return "hello"; //会被视图解析器处理
   }
}
  1. 跳转页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>

总结

  1. 新建项目
  2. 导入jar包
  3. 编写web.xml,注册DispatchServlet
  4. 编写Springmvc配置文件
  5. 创建对应的控制类controller
  6. 完善前端视图和controller之间的对应
  7. 测试运行

后续开发只需要进行5和6

注解和配置对比

基于配置 VS 基于注解

  1. web.xml 完全相同;

  2. springmvc-servlet.xml

    • 基于配置:处理器映射、处理器适配器、内部资源视图解析器;

      • 处理器映射:默认配置,不需要显式配置;
      • 处理器适配器:默认配置,不需要显式配置;
      • 内部资源视图解析器:必须显式配置;
      • Controller 注册到 IOC 容器中:相当于原来在 web.xml 中注册 Servlet
    • 基于注解Spring 注解开发

      • 扫描包:扫描指定包下的 Bean ,被扫描的 Bean 中包含的类级别的注解才会生效,Bean 才会被注册到容器中;
      • 过滤静态资源:如 CSSJSHTMLMP3MP4...
      • 注解驱动:在 Spring 中一般使用 @RequestMapping 注解来处理映射关系,使用该注解需要注册处理器映射处理器适配器annotation-driven 自动注入以上两个实例;
      • 内部资源视图解析器。
  3. Controller

    • 基于配置
      • 继承 Controller 接口,重写 handleRequest 方法;
      • 通过 ModelAndView 封装模型和视图,返回 ModelAndView
      • 需要在 springmvc-servlet.xml 中注册 Bean 并映射请求路径;
      • 如果有多个请求,通过获取参数判断方法名实现 Controller 复用,或编写多个 Controller
    • 基于注解
      • 使用 @Controller 注解来注册 Bean,自定义方法;
      • 通过 ModelAndView 封装模型和视图,返回 ModelAndView。或通过 Model 封装模型,返回视图名;
      • 在方法上使用@RequestMapping("/xxx")来映射请求路径
      • 如果有多个请求,自定义编写多个方法。

5、控制器Controller

  • 控制器提供访问应用程序的行为,通过接口定义或注解两种方式实现
  • 负责解析用户的请求并将其转换为一个模型

5.1、实现Controller接口

缺点是一个控制器中只有一个方法,如果要有多个方法需要定义多个controller,比较麻烦

//实现接口的类能获得控制器功能
public interface Controller {
   @Nullable
    //处理请求并返回一个模型与视图对象
   ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

编写实现类

public class HelloController implements Controller {
   @Override
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
      ModelAndView modelAndView = new ModelAndView();
      // 业务代码
      String result = "HelloSpringMVC";
      modelAndView.addObject("msg", result);
      //视图跳转
      modelAndView.setViewName("hello");
      return modelAndView;
   }
}

配置文件

  • 视图解析器
  • 注册servlet

配置文件中处理器映射和处理器适配器部分可以去掉,spring会自动加

<?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
       http://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">
        <!-- 前缀,必须有斜杠 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 相当于原来在 web.xml 中注册 Servlet -->
    <bean id="/hello" class="com.tian.controller.HelloController"/>
</beans>

5.2、使用注解@Controller

配置文件

  • 添加包扫描
  • 视图解析器
<!-- 扫描包 -->
<context:component-scan base-package="com.tian.controller"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
      id="internalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <!-- 后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

增加一个controller类,使用注解i实现

@Controller
@RequestMapping("/hello")       //配置总地址,可以有可无
public class HelloController {

   /**
    * 使用 @RequestMapping注解来处理映射关系,后接请求路径(如"/hello")
    * 方式一:使用 ModelAndView来封装模型和视图,返回 ModelAndView
    */
   @RequestMapping("/hello")		
   public ModelAndView hello(ModelAndView mv) {
      // 可以调用Service层方法,获取模型对象

      // 添加数据模型
      mv.addObject("msg", "Hello Annotation:ModelAndView");
      // 封装要渲染、跳转的视图
      mv.setViewName("hello");

      return mv;
   }

   /**
    * 使用 @RequestMapping注解来处理映射关系,后接请求路径(如"/hello")
    * 方式二:使用 Model来封装模型,返回 ViewName
    */
   @RequestMapping("/hello1")      //真实访问地址:localhost:8080/项目名/hello/hello1
   public String hello(Model model) {
      // 可以调用Service层方法,获取模型对象

      // 添加数据模型
      model.addAttribute("msg", "Hello Annotation:Model");
      // 返回视图
      return "hello"; //会被视图解析器处理
   }
}

6、RestFul风格

6.1、概念

RESTful 是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。

基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

6.2、功能

  • 资源:互联网所有的事物都可以被抽象为资源
  • 资源操作:使用不同方法操作资源,POST(添加)、DELETE(删除)、PUT(更新)、GET(查询);

传统方法操作资源:通过不同的请求参数来实现不同的功能,请求地址不同。

  • 添加:http://127.0.0.1/item/insertItem.action
  • 删除:http://127.0.0.1/item/deleteItem.action?id=1
  • 更新:http://127.0.0.1/item/updateItem.action
  • 查询:http://127.0.0.1/item/getItem.action?id=1

RESTful风格操作资源:通过不同的请求方式来实现不同的功能,请求地址相同。

  • 添加:http://127.0.0.1/item
  • 删除:http://127.0.0.1/item/1
  • 更新:http://127.0.0.1/item
  • 查询:http://127.0.0.1/item/1

6.3、实现

使用 @PathVariable 注解,将参数值绑定到 URI 模板变量上。

@Controller
public class RestfulController {

    @RequestMapping("/addition/{a}/{b}")
    public String addition(@PathVariable("a") int a, @PathVariable("b") int b, Model model) {
        int result = a + b;
        model.addAttribute("result", "结果:" + result);

        return "test";
    }
}

使用 method 属性约束请求的类型,指定类型的请求才可访问。

@RequestMapping(value = "/multiplication/{a}/{b}", method = RequestMethod.POST)
public String multiplication(@PathVariable("a") int a, @PathVariable("b") int b, Model model) {
    int result = a * b;
    model.addAttribute("result", "结果:" + result);

    return "test";
}

也可以使用 @RequestMapping 的衍生注解:@PostMapping

@PostMapping("/multiplication/{a}/{b}")
public String multiplication(@PathVariable("a") int a, @PathVariable("b") int b, Model model) {
    int result = a * b;
    model.addAttribute("result", "结果:" + result);

    return "test";
}

6.4、请求类型

@RequestMapping 注解能够处理 HTTP 请求的方法,包括 GET, PUT, POST, DELETE , PATCH 等等。

6.5、优点

  1. 使路径变得更加简洁:
    • 传统方式http://localhost:8080/springmvc_03/multiplication?a=7&b=3
    • RESTfulhttp://localhost:8080/springmvc_03/multiplication/7/3
  2. 获得参数更加方便,框架会自动进行类型转换;
  3. 使用 @PathVariable 注解来绑定 URI 模板变量,路径变量的类型可以约束请求参数。如果类型不一致,则访问不到对应的请求方法;

7、转发和重定向

7.1、SpringMVC使用视图解析器

通过 ModelAndView 对象,根据 view 名称视图解析器,映射到指定页面。

  • 使用视图解析器的方式属于请求转发,地址栏 URL 不会发生改变。
  • 实际页面 URL :(视图解析器前缀) + viewName +(视图解析器后缀)

视图解析器

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
      id="internalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <!-- 后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

Controller

z@RequestMapping("/h1")
public String hello(Model model) {

    model.addAttribute("msg", "Hello Annotation:Model");
	// viewName
    return "hello";
}

**结果:**访问http://localhost:8080/springmvc_03/h1,地址栏 URL 不变。

请求转发到http://localhost:8080/springmvc_03/WEB-INF/jsp/hello.jsp

7.2、SpringMVC不使用视图解析器

请求转发

forward:表示请求转发,后接转发路径。

可以省略,即直接返回 /index.jsp,默认为请求转发。

@RequestMapping("/mvc/t1")
public String test1(Model model){

    model.addAttribute("msg","SpringMvcController请求转发");

    return "forward:/index.jsp";
}

@RequestMapping("/mvc/t2")
public String test2(Model model){

    model.addAttribute("msg","SpringMvcController请求转发");

    return "/index.jsp";
}

**结果:**访问http://localhost:8080/springmvc_03/mvc/t1,地址栏 URL 不变。

请求转发到http://localhost:8080/springmvc_03/index.jsp

请求重定向

forward:表示请求重定向。

@RequestMapping("/mvc/t3")
public String test3() {
    return "redirect:/index.jsp";
}

**结果:**访问http://localhost:8080/springmvc_03/mvc/t3,地址栏 URL 改变。

重定向到http://localhost:8080/springmvc_03/index.jsp

7.3、使用ServletAPI

通过HttpServletResponse进行输出、重定向、转发

原生 Servlet API 实现,不需要视图解析器。

  • HttpServletRequest:请求转发
  • HttpServletResponse:请求重定向、页面打印、。

请求转发

Controller

@RequestMapping("/api/t1")
public void test1(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 携带参数
    req.setAttribute("result", "ServletApiController:请求转发");
    // 请求转发
    req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp);
}

**结果:**访问http://localhost:8080/springmvc_03/api/t1,地址栏 URL 不变。

请求转发到http://localhost:8080/springmvc_03/WEB-INF/jsp/test.jsp

请求重定向

Controller

@RequestMapping("/api/t2")
public void test2(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // 请求重定向
    resp.sendRedirect(req.getContextPath() + "/index.jsp");
}

**结果:**访问http://localhost:8080/springmvc_03/api/t2,地址栏 URL 改变。

请求重定向到http://localhost:8080/springmvc_03/index.jsp

页面打印

Controller

@RequestMapping("/api/t1")
public void test1(HttpServletRequest request, HttpServletResponse response) throws IOException {
    response.getWriter().println("ServletApiController被访问了");
}

**结果:**页面打印内容。

img

8、数据接收和回显

8.1、处理提交数据

获取的请求参数,可能是一个字段(域),也可能是一个对象。

  • 提交的域名称处理方法的参数名是否一致,决定了参数的获取方式;
  • 提交的域名称实体类的属性名是否一致,决定了参数的获取方式。

提交的域名称和处理方法的参数名一致

提交的域名称处理方法的参数名一致,会将域名称自动映射到处理方法的参数。

@RequestMapping("/user/t1")
public String test1(Model model, String name) {

    // 1、获取请求参数
    System.out.println("请求参数:" + name);
    // 2、数据回显
    model.addAttribute("msg", name);
    // 3、设置视图
    return "test";
}

提交数据:

http://localhost:8080/springmvc_03/user/t1?name=tian

提交的域名称和处理方法的参数名不一致

使用 @RequestParam 注解,设置提交的域名称。

@RequestMapping("/user/t2")
public String test2(Model model, @RequestParam("username") String name) {
    // 1、获取请求参数
    System.out.println("请求参数:" + name);
    // 2、数据回显
    model.addAttribute("msg", name);
    // 3、设置视图
    return "test";
}

提交数据:

http://localhost:8080/springmvc_03/user/t1?username=tian

提交对象

处理方法使用实体类接收请求参数,提交的域名称实体类的属性名必须一致,才能自动映射。

  • 实体类:User
private int id;
private String name;
private int age;
  • Controller
@PostMapping("/user/t3")
public String test3(Model model, User user) {
    // 获取请求参数
    System.out.println("请求参数:" + user);
    // 设置视图
    return "test";
}
  • 表单:用于提交参数

域名称和与实体类的属性名必须一致!

<form action="user/t3" method="post">
    <label> ID:<input type="text" name="id"> </label><br>
    <label> 姓名:<input type="text" name="name"> </label><br>
    <label> 年龄:<input type="text" name="age"> </label><br>
    <input type="submit">
</form>

8.2、数据显示到前端

将数据显示到前端,有三种方式:

  • ModelAndView:既可以存储数据模型,又可以设置返回视图;
  • Model:用于存储数据模型;
  • ModelMap:继承 LinkedMap,具有 LinkedMap 的方法和特性。
@RequestMapping("/data/t1")
public ModelAndView test1() {
    ModelAndView mv = new ModelAndView();
    // 数据模型
    mv.addObject("msg", "DataController ModelAndView");
    // 设置视图
    mv.setViewName("test");

    return mv;
}

@RequestMapping("/data/t2")
public String test2(Model model){
    // 数据模型
    model.addAttribute("msg","DataController Model");
    // 返回视图
    return "test";
}

@RequestMapping("/data/t3")
public String test3(ModelMap modelMap){
    // 数据模型
    modelMap.addAttribute("msg","DataController ModelMap");
    // 返回视图
    return "test";
}

9、乱码问题

web 开发中经常会遇到页面乱码问题,编写一个 Filter 过滤器解决乱码问题。

CharacterEncodingFilter

public class CharacterEncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=UTF-8");

        chain.doFilter(req, resp);
    }

    @Override
    public void destroy() {}
}

web.xml:注册Filter

注意

  • /:匹配所有的请求,不包括 JSP
  • /*:匹配所有的请求和 JSP
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>indi.tian.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

10、JSON

JSONJavaScript Object Notation):JavaScript 对象标记。

前后端分离时代:

  • 后端部署后端,提供接口,提供数据
  • 前端独立部署,负责渲染后端的数据

json用于数据交换

10.1、简介

什么是 JSON

  • 是轻量级的文本数据交换格式
  • 独立于编程语言
  • 使用 JavaScript 语法来描述数据对象(使用文本表示 JS 对象);
  • 具有自我描述性,易于理解。

10.2、JSON vs XML

JSONXML 都用于接收 web 服务端的数据,通常使用字符串。

对比

相同点

  • 具有自我描述性;
  • 具有层级结构;
  • 可以被大多数编程语言使用;
  • 可通过 JavaScript 解析;
  • 可使用 AJAX 传输;

不同点

  • JSON 不需要结束标签;
  • JSON 更加简短;
  • JSON 读写速度更快;
  • JSON 可以使用数组;

JSON优于XML

  • JSONXML 更容易解析;
  • JSON 可以使用现有的 JavaScript 对象解析;
  • 对于AJAX来说,JSON 比XML数据加载更快、更简单:
    • 使用 XML
      • 获取 XML 文档;
      • 使用 XML DOM 来循环遍历文档;
      • 将数据解析并存储在变量中;
    • 使用 JSON
      • 获取 JSON 字符串;
      • 解析 JSON 字符串;

10.3、语法

JSON 语法是 JavaScript 语法的子集。

JSON语法规则

  • 数据用 K-V 键值对表示;
  • 数据之间用逗号分隔;
  • 大括号{}保存对象;
  • 中括号[]保存数组。
{
    "name" : "tian",
    "age"  : "17",
    "hobby": ["java","music","basketball"]
}

JSON数据类型

JSON 值可以是:

  • 数字:整数或浮点数;
  • 字符串:写在双引号中;
  • 逻辑值:truefalse
  • 对象:写在大括号{}中;
  • 数组:写在中括号[]中;
  • 空值:null

JSON对象

用大括号{}表示 JSON 对象,JSON 对象可以包含多个 K-V 键值对。

  • Key 必须是字符串,Value 可以是合法的 JSON 数据类型(数字, 字符串, 布尔值, 对象, 数组或 null);
  • KeyValue 中使用冒号:分割;
  • 多个 K-V 键值对之间使用逗号,分割。

访问 JSON 对象属性

通过点号.或中括号[]访问 JSON 对象,可以进行以下操作;

  • 获取、修改属性值;
  • 使用 delete 关键字删除属性。
var person = {
    "name" : "tian",
    "age"  : "17",
    "hobby": ["java","music","basketball"]
}

var obj1 = person.name;
var obj2 = person["name"];

person.age = 10;
person["age"] = 10;

delete person.hobby;
delete person[hobby];

JSON数组

用中括号[]表示 JSON 数组。

  • 数组值必须是合法的 JSON 数据类型(数字, 字符串, 布尔值, 对象, 数组或 null);
  • 多个数组值之间使用逗号,分割。

访问 JSON 数组值

通过索引值(下标从0开始)访问数组,可以进行以下操作;

  • 获取、修改属性值;
  • 使用 delete 关键字删除属性。
var person = {
    "name" : "tian",
    "age"  : "17",
    "hobby": ["java","music","basketball"]
}

var obj = person.hobby[0];

person.hobby[0] = "spring";

delete person.hobby[0];

JSON文件

  • JSON 文件的 文件 类型:.json
  • JSON 文本的 MIME 类型:application/json

10.4、JSON和JS对象转换

JSON 通常用于与服务端交换数据。

  • 从服务器接收数据:将 JSON 字符串解析为 JavaScript 对象;
  • 向服务器发送数据:将 JavaScript 对象 转换为 JSON 字符串。

parse

使用 JSON.parse() 方法将 JSON 字符串转换为 JavaScript 对象:即字符串 → 对象。

JSON解析实例

  • 假设从服务器接收到以下数据

    { "name":"tian", "age":"17" }
    
  • 使用 JSON.parse() 方法解析数据,将其转化为 JavaScript 对象

    var obj = JSON.parse('{ "name":"tian", "age":"17" }');
    

stringify

使用 JSON.stringify() 方法将 JavaScript 对象转换为 JSON 字符串:即对象→ 字符串。

JSON对象转换实例

  • 假设要向服务器发送以下数据

    var obj = { "name":"tian", "age":"17" }
    
  • 使用 JSON.stringify() 方法将 JavaScript 对象转换为 JSON 字符串

    var myJson = JSON.stringify(obj);
    

10.5、测试

  • 编写一个 JS 对象

    let person = {
        "name": "tian",
        "age": 17,
        "hobby": ["java", "music", "basketball"]
    }
    console.log(person)
    

    img

  • JS 对象转换为 JSON 字符串

    let myJson = JSON.stringify(person)
    console.log(myJson)
    

    img

  • JSON 字符串转换为 JS 对象

    let obj = JSON.parse(myJson)
    console.log(obj)
    

    img

11、Java中使用JSON

Java 中常用的 JSON 类库: jacksonfastjsonGsonJSON-lib 等。

11.1、Jackson

环境搭建

  1. 新建 module,添加 web 框架支持:在项目结构中手动添加 lib 目录;

  2. 导入依赖

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.4</version>
    </dependency>
    
  3. web.xml:注册 DispatcherServlet

    • 关联配置文件;
    • 启动优先级;
  4. springmvc-servlet.xml:添加注解支持、内部资源视图解析器。

  5. 实体类:User

    private int id;
    private String name;
    private int age;
    

@ResponseBody

@ResponseBody 注解的作用:将处理方法返回的对象写到页面中。

  1. Controller 方法返回的对象,转换为指定的格式(通常是 JSONXML);
  2. 写入到 response 对象的 body 区。

举例

@Controller
public class UserController {
    
    @ResponseBody
    @RequestMapping("/user/t1")
    public String test1() {
        // 创建User
        User user = new User(7, "tian", 17);
        // 返回User字符串
        return user.toString();
    }
}

结果

img

@RestController

@RestController 相当于 @ResponseBody@Controller 合在一起的作用。

  • Controller 中的所有方法都会返回 return 中的内容;
  • 视图解析器 InternalResourceViewResolver 不起作用,无法返回视图( JSPHTML)。

举例

@RestController
public class UserRestController {
    
    @RequestMapping("/ur/t1")
    public String test1() {
        // 创建User
        User user = new User(7, "tian", 17);
        // 返回User字符串
        return user.toString();
    }
}

结果

img

ObjectMapper

要使用 Jackson 处理 JSON 数据,需要一个 ObjectMapper 对象。

ObjectMapper mapper = new ObjectMapper();
Object obj = new Object();

// 转换为字符串
String objJson = mapper.writeValueAsString(obj);
// 转换为字节流
byte[] objBytes = mapper.writeValueAsBytes(obj);
// 转换为文件
mapper.writeValue(new File("obj.json"), user);

// 读取字符串解析对象
User obj1 = mapper.readValue(objJson, Object.class);
// 读取字节流解析对象
User obj2 = mapper.readValue(objBytes, Object.class);
// 读取文件解析对象
User obj3 = mapper.readValue(new File("obj.json"), Object.class);
  • 除了对 Java 类进行转换,还可以对集合日期类进行转换。

解决乱码问题

乱码问题有两种解决方案。

  1. produces属性
@RequestMapping(value = "/user/j1", produces = "application/json;charset=utf-8")
  • 在处理方法的 @RequestMapping 注解使用 produces 属性;
  • 如果多个方法要使用,则需要在每个处理方法上写。
  1. 配置文件(推荐)

    <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>
    
    • 在配置文件中统一配置;
    • 统一配置后,就可以在全局使用。

测试

使用 @ResponseBody@RestController 可以达到一样的效果,以下测试采用第一种注解。

Java类

  • j1:将 User 对象转换为 JSON 字符串
@ResponseBody
@RequestMapping("/user/j1")
public String json1() throws JsonProcessingException {
    // 创建ObjectMapper
    ObjectMapper mapper = new ObjectMapper();
    // 创建User
    User user = new User(7,"路人甲",10);
    // 返回JSON字符串
    return mapper.writeValueAsString(user);
}

集合

  • j2:将 List 集合转换为 JSON 字符串
  • j3:将 Map 集合转换为 JSON 字符串
@ResponseBody
@RequestMapping("/user/j2")
public String json2() throws JsonProcessingException {
    // 创建ObjectMapper
    ObjectMapper mapper = new ObjectMapper();
    // 创建List集合
    List<User> list = new ArrayList<>();

    User user1 = new User(3, "张三", 20);
    User user2 = new User(4, "李四", 17);
    User user3 = new User(5, "王五", 30);
    list.add(user1);
    list.add(user2);
    list.add(user3);

    return mapper.writeValueAsString(list);
}

@ResponseBody
@RequestMapping("/user/j3")
public String json3() throws JsonProcessingException {
    // 创建ObjectMapper
    ObjectMapper mapper = new ObjectMapper();
    // 创建List集合
    HashMap<String, Object> map = new HashMap<>();

    map.put("name", "tian");
    map.put("age", 17);

    return mapper.writeValueAsString(map);
}

日期

  • j4:将日期对象转换为时间戳
  • j5:可以自定义日期格式。
@ResponseBody
@RequestMapping("/user/j4")
public String json4() throws JsonProcessingException {
    // 创建ObjectMapper
    ObjectMapper mapper = new ObjectMapper();
    // 创建Date
    Date date = new Date();
    // 返回JSON
    return mapper.writeValueAsString(date);
}

@ResponseBody
@RequestMapping("/user/j5")
public String json5() throws JsonProcessingException {
    // 创建ObjectMapper
    ObjectMapper mapper = new ObjectMapper();
    // 自定义日期格式
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    mapper.setDateFormat(sdf);
    // 创建Date
    Date date = new Date();
    // 返回JSON
    return mapper.writeValueAsString(date);
}

j1-j5测试结果

img

工具类

Jackson 的使用步骤如下:

  1. 创建 ObjectMapper 对象;
  2. 创建对象
    • 对象类型:pojo、日期类、集合;
    • 如果是日期类,可以自定义日期格式;
  3. 将对象转换为 JSON 字符串。

将以上重复的代码抽取,封装成工具类。

package com.tian.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.text.SimpleDateFormat;
import java.util.Date;


public class JsonUtils {
    /**
     * ObjectMapper
     */
    private static final ObjectMapper MAPPER = new ObjectMapper();
    /**
     * 对象转换后的JSON字符串
     */
    private static String OBJ_JSON;

    /**
     * 获取pojo、集合类的JSON字符串
     */
    public static String getJson(Object obj) {
        // 转换为JSON字符串
        try {
            OBJ_JSON = MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return OBJ_JSON;
    }

    /**
     * 获取默认日期格式的JSON字符串
     */
    public static String getDateJson(Date date) {
        return getDateJson(date, "yyyy-MM-dd hh-mm-ss");
    }

    /**
     * 获取指定日期格式的JSON字符串
     */
    public static String getDateJson(Date date, String dateFormat) {
        // 自定义日期格式
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        MAPPER.setDateFormat(sdf);
        // 转换为JSON字符串
        try {
            OBJ_JSON = MAPPER.writeValueAsString(date);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return OBJ_JSON;
    }
}

11.2、fastjson

fastjson 是阿里开发的 Java 类库,可以方便地实现 Java 对象和 JSON 格式之间的转换。

环境搭建

  1. 新建 module,添加 web 框架支持:在项目结构中手动添加 lib 目录;

  2. 导入依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.78</version>
    </dependency>
    
  3. web.xml:注册 DispatcherServlet

    • 关联配置文件;
    • 启动优先级;
  4. springmvc-servlet.xml:添加注解支持、内部资源视图解析器。

  5. 实体类:User

    private int id;
    private String name;
    private int age;
    

JSON

JSON 类似 Jackson 中的 ObjectMapper,用于处理 JSON 数据。

不同的是,fastJson 无需 new 对象,提供静态方法,类似 Jackson 中的工具类。

Object obj = new Object();

// 转换为字符串
String objJson = JSON.toJSONString(obj);
// 转换为字节流
byte[] objBytes = JSON.toJSONBytes(obj);

// 读取字符串解析对象
Object obj1 = JSON.parse(objJson);
// 读取字节流解析对象
Object obj2 = JSON.parse(objBytes);

测试

Java类

  • j1:将 User 对象转换为 JSON 字符串
@ResponseBody
@RequestMapping("/uf/j1")
public String json1() {
    User user = new User(7, "路人甲", 10);

    return JSON.toJSONString(user);
}

集合

  • j2:将 List 集合转换为 JSON 字符串
  • j3:将 Map 集合转换为 JSON 字符串
@ResponseBody
@RequestMapping("/uf/j2")
public String json2() {
    // 创建List集合
    List<User> list = new ArrayList<>();

    User user1 = new User(3, "张三", 20);
    User user2 = new User(4, "李四", 17);
    User user3 = new User(5, "王五", 30);
    list.add(user1);
    list.add(user2);
    list.add(user3);

    return JSON.toJSONString(list);
}

@ResponseBody
@RequestMapping("/uf/j3")
public String json3() {
    // 创建List集合
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "tian");
    map.put("age", 17);

    return JSON.toJSONString(map);
}

日期

  • j4:将日期对象转换为时间戳
  • j5:可以自定义日期格式,fastjson 提供了默认日期格式。
@ResponseBody
@RequestMapping("/uf/j4")
public String json4() {

    Date date = new Date();
    return JSON.toJSONString(date);
}

@ResponseBody
@RequestMapping("/uf/j5")
public String json5() {

    Date date = new Date();
    return JSON.toJSONStringWithDateFormat(date, JSON.DEFFAULT_DATE_FORMAT);
}

j1-j5测试结果

img

如果一切正常,但是报 500。检查项目结构的 lib 目录中是否有相关 jar 包。

12、SSM整合SpringMVC

见SSM整合笔记

13、Ajax

13.1、概述

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML

  • AJAX 不是新的编程语言,而是一种使用现有标准的新方法

  • AJAX 是一种用于创建快速动态网页的技术

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

AJAX一个前后台配合的技术,它可以让javascript发送http请求,与后台通信,获取数据和信息。ajax技术的原理是实例化xmlhttp对象,使用此对象与后台通信。

Jquery将它封装成了一个函数**$.ajax()**,我们可以直接用这个函数来执行ajax请求。

13.2、$.ajax()

$.ajax()

重要:

  • url
  • data
  • success
$.ajax({          
       	 url:"发送请求(提交或读取数据)的地址", 
         dataType:"预期服务器返回数据的类型",  
         type:"请求方式", 
         async:"true/false",
         data:{发送到/读取后台服务器的数据},
         success:function(data){请求成功时执行},      
         error:function(){请求失败时执行}
});

$(function(){
    //请求参数
    var list = {};
    //ajax
    $.ajax({
        //请求方式
        type : "POST",
        //请求的媒体类型
        contentType: "application/json;charset=UTF-8",
        //请求地址
        url : "http://127.0.0.1/admin/list/",
        //数据,json字符串
        data : JSON.stringify(list),
        //请求成功
        success : function(result) {
            console.log(result);
        },
        //请求失败,包含具体的错误信息
        error : function(e){
            console.log(e.status);
            console.log(e.responseText);
        }
    });
});

实例

登陆页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
<%--    <script src="jquery-3.5.1.js"></script>--%>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script>
        $(function () {
            $("#button1").click(function (){
                $.get({
                    url: "${pageContext.request.contextPath}/a1",
                    data: {"name":$("#username").val()},
                    success: function (data) {
                        if ((data.toString() === 'ok')) {
                           $("#span1").css("color","green");
                        }
                        $("#span1").html(data);
                    }
                })
            })
        });
    </script>
</head>
<body>
    用户名:
    <input type="text" id="username">
    <button id="button1">校验</button>
    <span id="span1"></span>
</body>
</html>

Controller

@RestController
public class AjaxController {
   @RequestMapping("/a1")
   public String login(String name) {
      String msg = "";
      if ("admin".equals(name)) {
         msg = "ok";
      } else {
         msg = "用户名错误";
      }
      return msg;
   }
}

image-20210817221936206

14、拦截器

概述

在开发一个网站时可能有这样的需求:某些页面只希望几个特定的用户浏览。对于这样的访问权限控制,需要用到拦截器

SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理

过滤器、拦截器

过滤器是 servlet 规范中的一部分,任何 Java Web 工程都可以使用,就算在SpringMVC中也可以使用不受限制。在 web.xml 中使用 url-pattern 配置了拦截地址之后,会对所有的访问所有改地址的资源进行拦截。

拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。并且拦截器只会拦截访问的控制器方法,不会拦截其他资源,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。

使用拦截器

实现HandlerInterceptor 接口

/**
 * springmvc中的拦截器对象
 * 1.编写代码:实现拦截器接口 HandlerInterceptor
 * 2.配置拦截器
 */
public class Demo1Interceptor implements HandlerInterceptor {

    /**
     * preHandle方法,预处理
     * 作用:在请求进入指定Controller方法之前,执行的方法(对请求进行验证)
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 请求将要执行的Controller对象
     * @return 布尔类型:true表示请求放行,继续向后执行(有可能进入下一个拦截器,也有可能进入Controller)
     *                  false表示拦截请求,请求不能继续向后执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Demo1Interceptor拦截器中的preHandle方法执行了...");
        return true;
    }

    /**
     * postHandle 后处理
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 正在执行的Controller对象
     * @param modelAndView 模型和视图数据,Controller方法执行完成之后的返回值
     *
     * 注意:此方法必须在请求进入Controller之后才会执行,如果没有进入Controller是不会执行的
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Demo1Interceptor拦截器中的postHandle方法执行了...");
    }

    /**
     * afterCompletion 请求处理完成之后
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 已经执行过的Controller对象
     * @param ex Controller方法抛出的异常对象
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Demo1Interceptor拦截器中的afterCompletion方法执行了...");
    }
}

方法解析:

  • preHandle方法:在请求进入Controller方法之前调用此方法,起到拦截的作用

    • 如果preHandle方法返回值为true,表示放行(允许进入Controller方法)
    • 如果preHandle方法返回值为false,表示拦截(不允许进入Controller方法)
  • postHandle方法

    请求进入Controller方法之后,但未结束之前,调用此方法

    可在返回的模型数据进行处理加工,比如加入公用信息以便页面显示

  • afterCompletion方法

    • 请求离开Controller方法之后,调用此方法
    • 可获取异常信息,记录日志,资源清理等

拦截器配置

web.xml

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 一个拦截器 -->
    <mvc:interceptor>
        <!-- 配置此拦截器所拦截的路径
            拦截所有请求:/** 表示所有进入springmvc的请求
        -->
        <mvc:mapping path="/**"/>
        <!-- 注册拦截器对象 -->
        <bean class="com.newcapec.interceptor.Demo1Interceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

应用:用户登陆

  • controller
@Controller
@RequestMapping("/auth")
public class LoginController {

    @RequestMapping("/login")
    public String login(String username, String password, HttpSession session, Model model){
        //模拟查询:根据用户名和密码查询数据
        if("admin".equals(username) && "123".equals(password)) {
           //比对成功,用户登录
           //将用户信息放入session域对象
           session.setAttribute("loginUser", username);
           return "index";
        }
        model.addAttribute("message", "username or password is invalid");
        return "login";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.removeAttribute("loginUser");
        session.invalidate();
        return "login";
    }
}
  • 拦截器
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 身份认证:
     * 过滤器:
     * 1.从session域中获取用户的登录信息
     * 2.判断用户信息是否为空
     * 3.不为空(表示已登录)放行(doFilter方法)
     * 4.为空(表示未登录)
     * 5.判断当前请求是否为登录请求或无需登录可执行请求(身份认证白名单)
     * 6.如果为白名单请求,放行
     * 7.如果不是,拦截,跳转login,信息提示...
     *
     * 拦截器:
     * 1.从session域中获取用户的登录信息
     * 2.判断用户信息是否为空
     * 3.不为空(表示已登录)放行(返回true)
     * 4.为空(表示未登录),拦截,跳转login,信息提示...
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("身份认证拦截器执行.....");
        HttpSession session = request.getSession();
        Object userInfo = session.getAttribute("loginUser");
        if(userInfo == null){
            request.setAttribute("message", "you are not login.");
            request.getRequestDispatcher("/views/login.jsp").forward(request, response);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}
  • 配置
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <!-- 配置拦截器不拦截路径,可使用通配符 -->
        <mvc:exclude-mapping path="/auth/login"/>
        <!--<mvc:exclude-mapping path="/demo/**"/>-->
        <bean class="com.newcapec.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <base href="${pageContext.request.contextPath}/">
    <title>Title</title>
</head>
<body>
    <div style="text-align: center">
        <h1>用户登录</h1>
        <div style="color: red;">${message}</div>
        <form action="auth/login" method="post">
            <p>用户名:<input type="text" name="username"></p>
            <p>&emsp;码:<input type="text" name="password"></p>
            <p><button>登录</button></p>
        </form>
    </div>
</body>
</html>

15、文件上传和下载

参考内容

15.1、文件上传

在 Spring MVC 中MultipartResolver接口用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。

MultpartiResolver 接口有以下两个实现类:

  • StandardServletMultipartResolver:使用了 Servlet 3.0 标准的上传方式。
  • CommonsMultipartResolver:使用了 Apache 的 commons-fileupload 来完成具体的上传操作。

MultpartiResolver 接口具有以下方法

名称 作用
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContentType() 返回文件的内容类型
InputStream getInputStream() 返回一个InputStream,从中读取文件的内容
String getName() 返回请求参数的名称
String getOriginalFillename() 返回客户端提交的原始文件名称
long getSize() 返回文件的大小,单位为字节
boolean isEmpty() 判断被上传文件是否为空
void transferTo(File destination) 将上传文件保存到目标目录下

单文件上传

导入jar包

Maven 项目在 pom.xml 文件中添加以下依赖。

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2.2</version>
</dependency>

配置 MultipartResolver

使用 CommonsMultipartReslover 配置 MultipartResolver 解析器,在 springmvc-servlet.xml 中添加代码如下。

<!-- 配置MultipartResolver,用于上传文件,使用spring的CommonsMultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="5000000" />
    <property name="defaultEncoding" value="UTF-8" />
</bean>
  • defaultEncoding:请求的编码格式,默认为 ISO-8859-1,此处设置为 UTF-8(注:defaultEncoding 必须和 JSP 中的 pageEncoding 一致,以便正确读取表单的内容)。
  • maxUploadSize:上传文件大小上限,单位为字节。

编写文件上传表单页面

负责文件上传表单的编码类型必须是“multipart/form-data”类型。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/fileupload"
        method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="myfile"><br> 
        文件描述:<input type="text" name="description"><br> 
        <input type="submit" value="提交">
    </form>
</body>
</html>

表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。

  • application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
  • multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
  • text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。

编写控制器

@Controller
public class FileUploadController {
    // 得到一个用来记录日志的对象,这样在打印信息时能够标记打印的是哪个类的信息
    private static final Log logger = LogFactory.getLog(FileUploadController.class);
    @RequestMapping("getFileUpload")
    public String getFileUpload() {
        return "fileUpload";
    }
    /**
     * 单文件上传
     */
    @RequestMapping("/fileupload")
    public String oneFileUpload(@ModelAttribute FileDomain fileDomain, HttpServletRequest request) {
        /*
         * 文件上传到服务器的位置“/uploadfiles”,该位置是指 workspace\.metadata\.plugins\org.eclipse
         * .wst.server.core\tmp0\wtpwebapps, 发布后使用
         */
        String realpath = request.getServletContext().getRealPath("uploadfiles");
        String fileName = fileDomain.getMyfile().getOriginalFilename();
        File targetFile = new File(realpath, fileName);
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        // 上传
        try {
            fileDomain.getMyfile().transferTo(targetFile);
            logger.info("成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "showFile";
    }
}

多文件上传

前端

<body>
<h2>同一类别多个文件上传</h2>
<form name="onfile"  action="onfiles2" method="post" enctype="multipart/form-data">
    图片:
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="file" name="img"><br>
    <input type="submit" value="提交">
</form>
</body>

服务器

@PostMapping("onfiles2")
@ResponseBody
public String onfiles2(MultipartFile img[]) throws IOException {
   for(int i=0;i<img.length;i++)
   {
       if(!img[i].isEmpty())//文件不空
       {
           File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
           imgfile.createNewFile();
           img[i].transferTo(imgfile);
           logger.info(img[i].getOriginalFilename());
       }
   }
    return "sucucess";
}

15.2、文件下载

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Springmvc文件下载</title>
</head>
<body>
<h2>Springmvc文件下载</h2>
个人照片<a href="/download/个人照片.png">个人照片.png</a><br>
个人简历<a href="/download/个人简历.pdf">个人简历.pdf</a>
</body>
</html>

其中href是下载的超链接,download是下载的接口名,而链接最后面部分则是下载资源名称。

服务端

文件下载的原理就是服务端向客户端返回二进制流和信息,而Springmvc通过ResponseEntity完成。我们在controller中编写以下接口实现下载的功能:

@GetMapping("download/{filename}")
public ResponseEntity<byte[]>download(@PathVariable String filename) throws IOException {
    //下载文件的路径(这里绝对路径)
    String filepath= "F:/download/"+filename;
    File file =new File(filepath);
    //创建字节输入流,这里不实用Buffer类
    InputStream in = new FileInputStream(file);
    //available:获取输入流所读取的文件的最大字节数
    byte[] body = new byte[in.available()];
    //把字节读取到数组中
    in.read(body);
    //设置请求头
    MultiValueMap<String, String> headers = new HttpHeaders();
    headers.add("Content-Disposition", "attchement;filename=" + file.getName());
    //设置响应状态
    HttpStatus statusCode = HttpStatus.OK;
    in.close();
    ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);
    return entity;//返回
}

文件下载非常常见的问题:中文文件名错误显示。这个解决方案也很容易解决,只需将Content-Disposition内容后面的文件名进行url编码即可,具体代码为(替换上面对于部分):

headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

15.3、总结

前面所讲文件上传,前端就是form表单用<input type="file">表示客户端要上传文件,而服务端主要使用MultipartFile或者MultipartFile[]分别接收单个文件和多个文件。而在存储到本地也仅仅需要在本地磁盘创建对应文件然后MultipartFile调用transferTo()方法即可将上传的文件储存。

而文件下载的前端需要一个请求的url链接,服务端需要编写这个链接对应的接口。通过一些名称找到文件在本地真实的位置通过ResponseEntity即可将二进制文件返回给客户达到文件下载的功能。而ResponseEntity使用也很简单在创建时候只需要传入二进制主体、头和状态码即可成功返回,而这些Springmvc已进行了很好封装你可以直接使用。

而无论是文件上传、多文件上传还是文件下载,一个完整的案例大致都需要这样一个过程:

  • 构思需求和页面大体样式
  • 编写前端html页面
  • 编写服务端响应的请求
  • 启动程序运行测试

在其中过程如果有问题可以根据编译器的错误提示、运行时的错误日志找到根源进行修正,这样完整的案例就可以成功完成啦!