SpringMVC的初体验

MVC是一种软件架构思想,将软件按照M(Model)模型,V(View)视图和C(Control)控制器来划分,用于封装公共的Servlet。

分层后的作用

1. DispactherServlet:公共的Servlet,底层用的还是Servlet。

    • 封装了spring容器的创建和Bean初始化的代码。
    • 动态获取相关请求数据的代码。
    • 动态调用Controller代码
    • 动态响应处理结果的代码

2. Controller:控制层,用于前后端交互的代码

  • 请求分发:
    • 控制层接收来自前端的HTTP请求,并根据请求的URL和HTTP方法将请求分发到相应的处理方法。
  • 数据校验和转换:
    • 控制层负责校验请求参数的合法性,并将前端传递的数据转换为合适的Java对象。
  • 调用业务逻辑:
    • 控制层将请求转发到相应的业务逻辑层(Service层),并将业务层返回的数据传递给视图层(View层)进行展示。
  • 响应数据的处理:
    • 控制层根据业务逻辑层的执行结果,组装合适的响应数据,并返回给前端。

3. Service:业务逻辑层,负责实现应用程序的业务逻辑

  • Service 接口:
    • 这些接口定义了应用程序的各种业务操作,如查询、新增、修改和删除等。
    • 这些接口是业务逻辑的抽象,为上层的控制层提供服务。
  • Service 实现类:
    • 这些类实现了 Service 接口中定义的各种业务方法。
    • 在实现业务逻辑时,Service 层会调用 Model 层 (即 pojo 文件夹) 提供的数据访问方法。
    • Service 层负责处理业务规则、事务管理、权限控制等。
  • Service 辅助类:
    • 有时候,service文件夹还会包含一些辅助类,如工具类、常量类等。
    • 这些辅助类为 Service 层的实现提供支持。

4. Mapper:持久化层,项目和数据库交互层代码

  • 数据库操作:
    • 持久层中定义的Mapper接口和XML配置文件,包含了对数据库表进行增删改查等操作的SQL语句。
  • 数据映射:
    • 持久层负责将数据库查询结果映射为Java对象,并将Java对象转换为数据库所需的格式。
  • 事务控制:
    • 持久层通常会配合Service层实现事务管理,保证数据操作的完整性。

5. Domain:对象层,负责定义应用程序的数据结构和业务规则

  • 对象类:
    • 这些类对应数据库表中的各个字段,并提供了属性、getter 和 setter 方法。这些 POJO 类是 MyBatis 用来映射数据库表结构的基础。
  • 数据传输对象 (DTO) 类:
    • 有时候,我们需要在不同层之间传输数据,这时就需要定义 DTO 类。DTO 类可以是 POJO 类的子集,也可以是完全不同的类。它们用于封装要在不同层之间传输的数据。
  • 数据访问对象 (DAO) 接口:
    • 有时候,POJO文件夹也会包含 DAO 接口的定义。DAO 接口定义了与数据库交互的方法,如增、删、改、查等。

环境搭建

1、Web项目配置SpringMVC的坐标(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.aaan</groupId>
  <artifactId>Practice_0810</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <!--配置依赖-->
  <dependencies>
    <!--配置Spring相关依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.3.26</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.26</version>
    </dependency>
    <!--配置Mybatis相关依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.16</version>
    </dependency>
    <!--配置MyBatis和Spring的整合依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>
    <!--配置jdbc-->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.0.33</version>
    </dependency>
    <!-- 工具类 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <!--表示该jar只在代码的编程和编译阶段有效,在部署的时候自动剔除!-->
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <!--配置web相关依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/taglibs/standard -->
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!-- springmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.26</version>
        </dependency>
    </dependencies>
</project>

2.在Web.xml文件中配置DispatherServlet的拦截路径以及包路径

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!--配置spring的监听器以及全局的Spring配置文件的路径-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
  </context-param>
  <!--配置Spring相关监听器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--配置DispatcherServlet相关-->
  <servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--自定义配置SpringMVC的配置文件路径-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--配置DispatcherServlet服务器启动就完成初始化-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>/</url-pattern><!--斜杠表示拦截除Jsp以外的所有请求,/*表示拦截所有请求-->
  </servlet-mapping>  
</web-app>

3.创建SpringMVC.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--配置注解扫描:扫描bean对象,SpringMVc中的Controller的bean对象只能通过注解创建,不支持bean标签方式-->
    <context:component-scan base-package="com.aaan.controller"></context:component-scan>
    <!--配置注解解析器:初始化一些其他的相关bean自动化配置SpringMVc的核心组件的Bean-->
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>

4.创建Controller类

package com.wollo.controller;

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

/*
*一、SpringMVC学习
*  1. @Controller注解:
*     位置: 在Controller的类上
*     作用:结合SpringMVC容器的底层,配置该类为Bean对象
*  2. 单元方法:
*     说明:在controller类中定义的方法称之为”单元方法“,用来实现和前端的交互!
*     使用:
*         在单元方法上必须使用注解来声明单元方法的访问路径
*         @RequestMapping(value="单元方法的相对访问路径",其他属性);//注意里面的路径是相对路径
*         @GetMapping:请求方式必须是GET方式
*         @PostMapping:请求方式必须是POST方式
*         @PutMapping:请求方式必须是Put方式
*         @DeleteMapping:请求方式必须是Delete方式
* */
@Controller
public class MyController {
    //声明单元方法: @RequestMapping注解的method属性限定请求方式
    @RequestMapping(value = "/demo",method = RequestMethod.GET)
    public String  demo(){
        System.out.println("嘻嘻哈哈");
        return "aa";
    }

}

注意:

    • Dispatherservlet在初始化创建时底层会创建两个容器对象(Spring容器对象和SpringMVC容器对象)
    • SpringMVC容器对象(Map):管理Controller层以及DispatherServlet自己要用的其他对象
    • Spring容器对象(Map):管理Service层和Mapper层以及其他bean对象
  • 代码执行流程:
    • 传统:Request—>Servlet(自己写)—>Service—>Mapper
    • SpringMVC:Request—>Servlet(封装的)—>Controller(自己写)—>Service—>Mapper

注解

@Controller注解

  • 在Controller类上标识该类为一个Spring MVC控制器类,用于处理用户的请求。
  • 作用:结合SpringMVC容器底层,配置该类为Bean对象
  • 在该类中可以使用其他Spring MVC注解,比如@RequestMapping、@GetMapping、@PostMapping、@DeleteMapping、等,来定义处理不同URL请求的方法。
  • 控制器类中的方法可以返回视图名称、模型数据或者直接写入响应体等。
  • 控制器类会被Spring容器自动扫描和管理,无需手动注册。
  • 通常情况下,控制器类会注入Service层或者Repository层的实现类,用于处理业务逻辑。
  • 控制器类可以接受各种类型的请求参数,如请求参数、请求头、Cookie、Session等。

@RequestMapping注解

@RequestMapping("/demo") "/demo":相对路径

  • 可以将一个URL路径映射到一个控制器方法上。这样当客户端访问该URL时,Spring MVC框架就会调用对应的控制器方法来处理请求。
  • 请求方式限定:可以限定控制器方法只处理特定的HTTP请求方式,如GET、POST、PUT、DELETE等。
  • 参数绑定:可以将HTTP请求参数自动绑定到控制器方法的参数上。
  • 返回值处理:控制器方法的返回值会被Spring MVC框架处理,可以返回视图名称、模型数据或者直接写入响应体。

属性

value属性和path属性

path 属性和 value 属性使用方式是相同的,都是设置控制单元的映射路径。

//Value属性
@RequestMapping(path = "/demo")
public String demo(){
    return "hallo.jsp";
}
//Value属性
@RequestMapping("/demo1")
public String demo1(){
    return "hallo.jsp";
}
//Value属性
@RequestMapping({"/demo2","demo3"})
public String demo2(){
    return "hallo.jsp";
}
//Value属性
@RequestMapping(value = "/demo4")
public String demo3(){
    return "hallo.jsp";
}
//Value属性
@RequestMapping(value = {"/demo5","demo6"})
public String demo4(){
    return "hallo.jsp";
}
name属性

单元方法注解的name属性,可以理解为给单元方法加个注释

    //name属性
    @RequestMapping(value = "/demo7" ,name="demo7的方法")
    public String demo5(){
        return "hallo.jsp";
    }
method 属性

用来规定单元方法的请求方式,@RequestMapping请求方式只能使GET和POST类型

@RequestMapping(value = "/demo",method = RequestMethod.GET )
    public String  demo(){
        System.out.println("嘻嘻哈哈");
        return "hallo.jsp";
    }

简写:

  • @PostMapping("/test")等效于@PostMapping((value = "/test",method = RequestMethod.POST)
  • @GetMapping("/test")等效于@GetMapping((value = "/test",method = RequestMethod.POST)
  • @DeleteMapping("/test")等效于@DeleteMapping((value = "/test",method = RequestMethod.POST)
  • @PutMapping("/test")等效于@PutMapping((value = "/test",method = RequestMethod.POST)
  • @PatchMapping("/test")等效于@PatchMapping((value = "/test",method = RequestMethod.POST)
params属性

params 属性类型是 String[],表示请求中必须包含指定名称的请求参数。

//表示从服务器中接收的请求必须含有name参数,如果没有包含name参数,响应会出现 400 状态码
    @RequestMapping(value = "/test",params = {"name"})
    public String test(){
        return "hallo";
    }
headers属性

类型是 String[], 表示请求头中必须包含指定的请求头参数。

@RequestMapping(value = "/test1",headers = "aaan")
    public String test1(){
        return "hallo.jsp";
    }
consumers属性

表示处理请求内容(Content-Type)的类型,平时多不设置,由 Spring MVC 自动

判断。

produces属性

类型是 String[],作用是设置@ResponseBody 注解的响应内容类型。且仅当请求头 中 Accept 中包含的值才生效。 produces 只有在使用@ResonseBody 注解中才生效。

@RequestMapping(value = "/test2",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String test2(){
        return "hallo";
    }

Result请求

格式:

http://ip:端口号/单元方法路径/请求数据/请求数据/...(该格式在前端页面中声明)

后端

@XXXMapping("/访问路径/{占位符}/{占位符}/...")

public 返回值类型 单元方法名称(@PathVariable("占位符的名称")形参类型 形参名,形参类型 形参名.....){

//方法体

}

注意:如果单元方法前没有用@PathVariable注解,则按照请求数据为键值对的方式来获取请求数据

如果占位符的名称和形参名一致就不需要@PathVariable注解

不同的请求方式用不同的注解

  • 新增:@PostMapping
  • 新增:@PostMapping
  • 修改:@PutMapping
  • 删除:@DeleteMapping

//声明单元方法:删除用户信息
@DeleteMapping("/user/{a}")
public String delUser(@PathVariable("a") Integer uid){
    System.out.println("要删除的用户id为 = " + uid);
    return "add";
}

@ResetController注解

  • 使用@RestController注解的控制器中的单元方法默认全部都是直接响应,相当于每个单元方法上都添加了@ResponseBody注解。
  • 一旦使用该注解,所有控制单元返回的都是XML或JSON数据,而无法实现页面跳转的功能。
@Controller
public class MyController05 {
    //声明单元方法:直接响应
    @GetMapping(value = "/demoStr",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String demoStr(){
        return "那咋了";
    }

    //声明单元方法:jackson字符串
    @GetMapping(value = "/demoStr1")
    @ResponseBody
    public User demoStr1(Integer id){
        User user=new User(1,"张三","123","123@qq.com");
        return user;//DispatcherServlet底层接收到单元方法返回值后将返回值转换为Json格式后直接响应
    }
}
@RestController
public class MyController05 {
    //声明单元方法:直接响应
    @GetMapping(value = "/demoStr",produces = "text/html;charset=utf-8")
    public String demoStr(){
        return "那咋了";
    }

    //声明单元方法:jackson字符串
    @GetMapping(value = "/demoStr1")
    public User demoStr1(Integer id){
        User user=new User(1,"张三","123","123@qq.com");
        return user;//DispatcherServlet底层接收到单元方法返回值后将返回值转换为Json格式后直接响应
    }
}

这两段代码作用相同。

@ResponseBody注解

服务器端—>Json格式的请求数据—>客户端

设置服务器端响应给客户端的方式为直接响应,并且底层调用jackSon的jar包将数据转换为json格式给客户端

单元方法上使用@ResponseBody注解表示将单元方法的返回值作为响应数据直接响应给前端,响应数据的格式不能乱写,必须和前端配套!

Java对象的属性名默认为转换的Json的键名,可以在属性上使用@JsonProperty("键名")的方式来自定义对象转换Json的键名。

  • 主流格式为Json格式:
    • "{name:'zhangsan',fav:'唱歌'}",键名默认为实体类扥属性名
  • List的Json格式:
    • 泛型为基本数据类型或String:"[1,2,3,4]"
    • 泛型为实体类类型:"[{name:'张三',fav:'唱歌'},{},{}...]"
  • MapdeJson格式:
    • "{name:'张三',favor:'唱歌'}"

直接响应

//声明单元方法:直接响应
    @GetMapping(value = "/demoStr",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String demoStr(){
        return "那咋了";
    }

Jackson格式响应对象

导入依赖

        <!--自动转换为Jackson字符串-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.10.8</version>
            </dependency>
//声明单元方法:jackson字符串
    @GetMapping(value = "/demoStr1")
    @ResponseBody
    public User demoStr1(Integer id){
        User user=new User(1,"张三","123","123@qq.com");
        return user;//DispatcherServlet底层接收到单元方法返回值后将返回值转换为Json格式后直接响应
    }

响应Json数据--数据为Map集合类型

    //声明单元方法:map集合
    @GetMapping(value = "/demoMap")
    @ResponseBody
    public Map<String,String> demoMap(){
        HashMap<String, String> map = new HashMap<>();
        map.put("id","2");
        map.put("name","张三");
        map.put("password","123456");
        map.put("email","123@qq.com");
        return map;
    }

响应Json数据--数据为List集合类型

//声明单元方法:List集合
    @GetMapping(value = "/demoList")
    @ResponseBody
    public List<String> demoList(){
        List<String> list = new ArrayList<>();
        list.add("2");
        list.add("张三");
        list.add("123456");
        list.add("123@qq.com");
        return list;
    }

响应Json数据--数据为List<对象类型>集合类型

//声明单元方法:List<对象>集合
    @GetMapping(value = "/demoList2")
    @ResponseBody
    public List<User> demoList2(){
        List<User> list = new ArrayList<>();
        list.add(new User(2,"马龙","123","123@qq.com"));
        list.add(new User(1,"张继科","123","123@qq.com"));
        return list;
    }

转换位XML文件

导入依赖(和Jackson依赖不兼容),其他范媛方法不变

<!--自动转换为Html字符串-->
        <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.9</version>
        </dependency>
//声明单元方法:List<对象>集合
    @GetMapping(value = "/demoList2")
    @ResponseBody
    public List<User> demoList2(){
        List<User> list = new ArrayList<>();
        list.add(new User(2,"马龙","123","123@qq.com"));
        list.add(new User(1,"张继科","123","123@qq.com"));
        return list;
    }

@RequestBody注解

客服端—>Json格式的请求数据—>服务器端

采用的是键值对的方式,请求中必须将请求数据作为请求地址的一部分携带

请求中只能有一个请求体数据,在单元方法上只能声明实体类对象或者Map集合类型的形参来接收请求体数据

请求方式必须是post方式

必须依赖JackSon的Jar包

<!--自动转换为Jackson字符串-->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.10.8</version>
</dependency>
    @RequestMapping("/demoReq0")
    @ResponseBody
    public User demoReq0(@RequestBody User user){
        System.out.println("user = " + user);
        return user;
    }

    @RequestMapping("/demoReq0")
    @ResponseBody
    public User demoReq0(@RequestBody User user,String fav){
        System.out.println("user = " + user + ", fav = " + fav);
        return user;
    }

@PutMapping 接受put请求,整体更新

@PatchMapping 对@PutMapping 的补充,整体更新

@DeleteMapping 接受delete请求

@PostMapping 接受post请求

@GetMapping 接受get请求

@CrossOrigin允许跨域,在响应头添加 AcceSs-Control-Allow-Origin 属性

@ExceptionHandler 异常处理

@RequestParam 处理请求参数

@RequestHeader 接收请求头

@PathVariable 获取restful请求参数

@SessionAttribute 获取 Session 作用域的值.name:作用域值的名称。如果没有设置name 按照参数名称获取。required:默认为true,表示作用域中必须有这个属性,如果没有报异常。设置为false,如果没有返回 null

映射

映射路径指的就是@RequestMapper("/xx")中的"/xx"也就是浏览器访问单元方法的路径,例如:http://localhost:8081/aaan/testt 中 的URl 是/aaan/test,其中 /aaan 表示当前项目的名称,/test 表示映射路径。Spring MVC 发现映射路径是/test 时就会执行上面的控制单元。

多级路径问题

//单元方法的访问路径:locaLhost:8080/demo/test
// 请求转发的资源路径:LocaLhost:808θ/demo/test.jsp
//要解决这个问题就必须在返回值中加“/”
@RequestMapping("/demo/test")
public String test3(){
    return "/my.jsp";//以"/“开头的路径为绝对路径,从webapp下查找对应的资源,和当前的单元方法访问路径无关
}

Ant 风格的映射路径

Ant风格的映射路径就是支持三种特殊的符号

  • ?—>匹配任意单字符
  • * —>匹配0或者任意数量的字符
  • ** —>匹配0或者更多数量的目录

使用Ant的特殊符号时,表示模糊匹配。可能出现客户端发送过来的URL能匹配上多个映射路径,这时的优先级为:固定值(wollo1)>?形式(wollo?)>一个号(/*)>**形式

//优先级最高
    @RequestMapping("/test4")
    public String test4(){
        return "/hallo.jsp";
    }
    //优先级最低于test5
    @RequestMapping("/test?")
    public String test5(){
        return "/hallo.jsp";
    }
    //优先级最低于test?,一层目录
    @RequestMapping("/test*")
    public String test6(){
        return "/hallo.jsp";
    }
    //优先级最低于test*,任意层层目录
    @RequestMapping("/test**")
    public String test7(){
        return "/hallo.jsp";
    }

直接访问,转发和重定向

直接访问

这是最简单的一种方式,在控制器的方法中返回一个逻辑视图名称,Spring MVC 会根据视图解析器的配置找到对应的物理视图页面并渲染返回给客户端。

优点:

  • 实现简单,代码量少
  • 可以在视图中访问模型数据

缺点:

  • 视图和控制器紧耦合,不利于代码的维护和扩展
  • 如果需要重定向或转发,则需要另行实现
    @RequestMapping("/demo")
    public String demo(){
        //返回hallo.jsp或者hallo.html视图
        return "hallo";
    }

请求转发

一次请求,请求转发是在服务器端内部进行的页面跳转,客户端感知不到跳转过程。使用 forward: 前缀指定转发的目标路径。发生在服务端程序内部,当服务器端收到一个客户端的请求之后,会先将请求,转发给目标地址,再将目标地址返回的结果转发给客户端。

优点:

  • 可以在转发过程中传递数据,如 request 属性
  • 客户端感知不到跳转过程,URL 地址不变

缺点:

  • 只能在服务器端内部跳转,不能跳转到外部资源
  • 如果目标页面抛出异常,异常信息会被传递到客户端
    @RequestMapping("/demo1")
    public String demo1(){
        //转发到hallo控制器映射的方法
        return "forward:/hallo";
        //return的值为要请求转发的资源路径,请求转发的时候forward关键字可以省略不写
    }

重定向

两次请求,重定向是服务器端返回一个 HTTP 重定向响应,客户端收到后会自动跳转到新的 URL。使用 redirect: 前缀指定重定向的目标路径。请求重定向指的是服务器端接收到客户端的请求之后,会给客户端返回了一个临时响应头,这个临时响应头中记录了,客户端需要再次发送请求(重定向)的 URL 地址,客户端再收到了地址之后,会将请求发送到新的地址上,这就是请求重定向。

优点:

  • 可以跳转到任何 URL,包括外部资源
  • 客户端 URL 地址会发生变化
  • 安全性更高,防止重复提交表单

缺点:

  • 无法在重定向过程中传递数据,需要借助 RedirectAttributes 或 Session
  • 多次重定向可能会降低性能
    @RequestMapping("/demo2")
    public String demo2(){
        //重定向到hallo控制器映射的方法
        return "redirect:/hallo";
    }

业务场景:

  • 查询请求:请求转发,Servlet负责请求数据的处理,转发到JSP页面进行拼接,然后相应
  • 增删改qingq:重定向,避免表单重复提交

自定义视图解析器

WEB-INF目录下的页面资源

Web项目会编译WEB-INF文件下的隐藏目录classes文件夹—>启动Tomcat的时候会将webapp目录下的所有资源拷贝到Tomcat的项目目录下,真正的运行资源一直在webapp目录下的资源

WEN-INF目录的特点:

  • 该项目下的所有资源对浏览器不可见
  • 浏览器无法直接访问
  • 必须使用请求转发

视图解析器

不配置视图解析器,返回代码臃肿

    @RequestMapping("/demo3")
    public String demo3(){
        return "/WEB-INF/Page/add.jsp";
    }

配置视图解析器

<!--配置视图解析器-->
    <bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/page"/>
        <property name="suffix" value=".jsp"/>
    </bean>
@RequestMapping("/demo4")
public String demo4(){
    return "add";
}

问题:自已在单元方法中直接使用return请求转发的资源路径的方式来完成WEB-INF下资源的转发比较麻烦!并且我们的页面技术也不仅仅有JSP,怎么办?

解决:封装一个接口,该接口中定义抽象方法,该抽象方法的作用是对单元方法的返回值进行二次的字符串拼接拼接要转发的资源路径!并且官方已经创建了该接口并提供了该接口的实现类!

使用:InternalResourceViewResolver:

实现了ViewResolver接口!

在springmvc.xml文件中配置该类的bean对象,并设置其前缀和后缀的参数!

注意:如果单元方法的返回值没有使用forward关键字,则会触发视图解析的二次拼接功能如果单元方法的返回值使用了forward关键字,则单元方法的返回值就是要请求转发的资源路径!

静态资源放行

因为Dispatherservlet的拦截范围是"/"表示拦截除jsp页面外的所有请求,导致css、js、images等静态资源的访问也会被拦截,拦截不是去查找静态资源而是作为单元方法的请求进行处理。

解决:在springmvc.xml文件中配置静态资源放行

    <!--配置静态资源放行-->
    <mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
    <!--<mvc:resources mapping="/css/**" location="/static/css/"></mvc:resources>-->
@RequestMapping("/css/my.css")
public String demo5(){
    return "add";
}

控制单元参数

SpringMVC获取单元请求的方式:

  • 紧耦合
    • 获取原生 Servlet API,通过原生 Servlet API 获取请求参数、设置响应内容、 设置作用域的值,自己写从request取请求的方式
  • 解耦合
    • 使用 Spring MVC 提供的方式获取请求参数、设置响应内容、设置作用域的值。

紧耦合

    //声明单元方法:紧耦合的方式获取请求数据
    @RequestMapping("/demoRequest")
    public String demoRequest(HttpServletRequest request){
        //获取请求数据
        String name = request.getParameter("name");
        int age = Integer.parseInt(request.getParameter("age"));
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        //响应结果
        return "add";
        //请求转发
    }

解耦合

解耦方式是 Spring MVC 独有方式。是 Spring MVC 给开发者提供的:

//声明单元方法:解耦合方式--单元方法的形参名为请求数据的键名,参数必须和请求的参数名一致
    @RequestMapping("/demoParam")
    public String demoParam(String name,int age){
        System.out.println("name = " + name + ", age = " + age);
        return "add";
    }

解耦合—单元方法的形参名和请求数据的键名不一致

使用@RequestParam()注解,属性

value:别名

defaultValue:默认值

required:如果为true,请求必须含有改名的参数值(和默认值不能一起用)默认为true

/	/声明单元方法:解耦合方式--单元方法的形参名为请求数据的键名不一致
    @RequestMapping("/demoParam1")
    public String demoParam1(@RequestParam(value = "username",defaultValue = "hahaha",required = false) String name, int age){
        System.out.println("name = " + name + ", age = " + age);
        return "add";
    }

解耦合方式—使用JavaBean作为参数

创建JavaBean对象,在domain中创建User对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int id;
}
    //声明单元方法:解耦合方式--使用JavaBean作为参数
    @RequestMapping("/demoParam2")
    public String demoParam2(User user){
        System.out.println(user);
        return "add";
    }

解耦合方式--混合方式获取请求数据

//声明单元方法:解耦合方式--混合方式获取请求数据
    @RequestMapping("/demoParam3")
    public String demoParam3(String name,int age,User user){
        System.out.println("name = " + name + ", age = " + age + ", user = " + user);
        return "add";
    }

解耦合方式--接收同名表单数据

//声明单元方法:解耦合方式--接收同名表单数据,使用数组来接收同名的表单数据
    @RequestMapping("/demoParam4")
    public String demoParam4(String[] fav){
        System.out.println("fav = " + Arrays.toString(fav));
        return "add";
    }
//声明单元方法:解耦合方式--接收同名表单数据,使用list类型来接收同名的表单数据,必须使用@RequestParam注解来声明请求数据的键名
    @RequestMapping("/demoParam5")
    public String demoParam5(@RequestParam("fav")List fav){
        System.out.println("fav = " + fav);
        return "add";
    }

解耦合方式--接收日期数据

使用Date对象时必须使用@DateTimeFormat(pattern = "yyyy-MM-dd")标明日期格式

//声明单元方法:解耦合方式--接收日期数据
    @RequestMapping("/demoParam6")
    public String demoParam6(@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){
        System.out.println("birthday = " + birthday);
        return "add";
    }

解耦合方式--获取请求头数据

必须使用注解@RequestHeader("xx")获取,并且形参名必须是请求头中数据的键名

//声明单元方法:解耦合方式--获取请求头数据
    @RequestMapping("/demoParam7")
    public String demoParam7(@RequestHeader String accept){
        System.out.println("accept = " + accept);
        return "add";
    }

SpringMVC的作用域对象

传统作用域对象:

  • request:一次请求内,请求转发的时候用
  • session:一次会话,用户不同请求之间的数据共享
  • application:不同用户的数据共享

SpringMVC的作用域对象:

  • Model对象:如果使用JSP页面技术,底层仍是request对象!
@RequestMapping("/demoReq")
    public String demoReq(HttpServletRequest request, Model model, HttpSession session){
        request.setAttribute("name","hahahaha");
        model.addAttribute("fav","xxxxxxxxxxxxxxx");
        return "add";
    }
${requestScope.name}--${requestScope.fav}

SpringMVC实现文件的上传和下载

上传

  • 目的:将客户端的文件上传到服务器存储
a. 客户端
    • 请求必须是POST格式,请求数据必须是二进制格式
    • 表单方式:表单中的请求数据=上传文件的二进制+普通标单向的二进制
<%--enctype="multipart/form-data"该属性表示表单收集的数据会以二进制的形式发送给服务器端--%>
<form action="/addRoom" method="post"enctype="multipart/form-data">
    <tr>
         <td>酒店图片</td>
          <td>
              <%--从客户端的硬盘中选择要上传的文件,选择好后浏览器会自动读取该文件的二进制数据--%>
              <input type="file" name="roomPhotos">
          </td>
      </tr>
</form>
b. 服务器端
  • 带有上传功能的请求数据必须为二进制数据,Dispatherservlet接收后可以完成解析,但需要调用相关的Bean需要在springmvc.xml文件中配置上传解析的Bean。
    • 注意:需要在啊pom.xml文件中配置commons-fileupload的依赖,上传解析Bean的底层要使用。
  • 在单元方法生命对应的形参接受解析的结果,普通表单数据仍是以前形参名是键名的格式来声明上传文件的文件数据形参类型必须是MultipartFile,参数名必须是上传表单的name属性值。
  • 奖MultipartFile对象中存储的上传文件存储到指定位置
    • 存储到项目运行的主机的硬盘中:存储资源过多,影响项目的运营
    • 存储到服务器中:专门搭建服务器用来存储上传的资源,需要额外的技术,微服务阶段学习。
  • 存储上传记录在数据库中
<dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="1024000000000"></property>
  <!--限制文件大小-->
    </bean>
//配置文件保存路径
private static final String UPLOAD_PATH="S:/BeiJing/Code/images/";
//声明单元方法:新增加房间
@RequestMapping("/addRoom")
public String addRoom(MultipartFile roomPhotos, Room room) throws IOException {
    //判断有没有上传文件
    if (roomPhotos.getSize()>0) {
        //获取文件上传路径
        String oldName = roomPhotos.getOriginalFilename();
        //获取文件名后缀
        String suffix = oldName.substring(oldName.lastIndexOf("."));
        //判断文件类型(限制文件类型)
        if (!(".jpg".equals(suffix)||".png".equals(suffix)||".webp".equals(suffix)||".jpeg".equals((suffix)))){
                return "NullPointEX";
        }
        //创建新的文件名称
        String newName = "" + UUID.randomUUID() + System.currentTimeMillis() + suffix;
        //存储文件上传到本地硬盘
        roomPhotos.transferTo(new File(UPLOAD_PATH + newName));
        //将新的文件名存储到room对象中
        room.setImages(newName);
    }
    roomService.insertRoom(room);
    return "redirect:/roomInfo";
}

下载

  • 将文件从服务器传给客户端的过程
  • 客户端:使用restful格式发起下载请求,携带要下载文件的名称
  • 服务器端:单元方法的返回值类型必须是void
    • 使用IO流读取服务器
    • 使用response对象的输出流将读取的数据输出给浏览器
    • 设置响应头告诉浏览器将文件下载后存储到客户端硬盘中,而不是直接打开
      • response.setHeader("Content-Disposition","attachment;filename="+fileName);
<%--"/roomImages/${room.images}"等同于"http://localhost:8080/roomImages/${room.images}"--%>
<a href="/roomImages/${room.images}" style="text-decoration: none; color: #ff0260">下载图片</a>
//声明单元方法:下载图片
@RequestMapping("/roomImages/{fileName}")
public void roomImage(@PathVariable String fileName, HttpServletResponse response) throws IOException {
    //fileName的值就是客户端看到下载文件的名称
    response.setHeader("Content-Disposition","attachment;filename="+fileName);
    //创建读取流对象
    FileInputStream fileInputStream = new FileInputStream(new File(UPLOAD_PATH + fileName));
    //获取网络输入流对象
    ServletOutputStream outputStream = response.getOutputStream();
    //输出图片资源
    IOUtils.copy(fileInputStream,outputStream);

}

拦截器

<!--配置拦截器-->
    <mvc:interceptors>
        <!--配置全局拦截: 拦截所有的单元方法-->
        <bean id="my" class="com.aaan.interceptor.MyInterceptor"></bean>
    </mvc:interceptors>

preHandle方法

创建MyInterceptor类实现HandlerInterceptor接口,重写preHandle方法

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("我是MyInterceptor.preHandle--------------------------------");
    HandlerMethod hm=(HandlerMethod) handler;
    Method method = hm.getMethod();
    System.out.println("我是preHandle方法:"+handler.getClass()+"*********"+method.getName());
    return true;
}
/*
    *作用:
    *   拦截单元方法的请求
    * 执行时机:
    * 在DispatcherServLet之后,单元方法之前
    * param  request 封装了此次请求的请求数据的request对象
    * @param  response 此次请求的响应对象
    * @param  handler HandlerMethod,HandlerMethod对象中行结北的中元方达前Method对家
    * @return 返回true表示放行继续执行单元方法,返回false表示拦截
    * */
//声明单元方法
    @RequestMapping(value = "/demo",produces ="text/html;charset=utf-8")
    public String demo(){
        System.out.println("我是单元方法demo");
        return "今天天气不错,适合学习1";
    }

postHandle方法

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerMethod hm=(HandlerMethod) handler;
        Method method = hm.getMethod();
        System.out.println("我是postHandle方法:"+handler.getClass()+"*********"+method.getName());
    }
/*
     *作用:
     *  对作用域中的数据进行一些操作,比如:敏感词汇的脱敏
     * 执行时机:
     *  在单元方法之后,转发页面资源之前
     * param  request 封装了此次请求的请求数据的request对象
     * @param  response 此次请求的响应对象
     * @param  modelAndView  Model对象和View对象,ModeL对象存储了此次转发的作用域数据,View存储了转发资源路径
     * @param
     * @return 返回true表示放行继续执行单元方法,返回false表示拦截
     * */

afterCompletion

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex!=null){
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("服务器繁忙,请稍后再试");
        }
        System.out.println("MyInterceptor.afterCompletion");
    }
    /*
     *作用:
     *  记录请求处理的日志信息或者对请求处理过程中的异常进行统一的处理
     * 执行时机:
     *  在preHandle放行的情况下,不管中间执行流程是否出现异常,afterCompletion方法都会被调用
     * param  request 封装了此次请求的请求数据的request对象
     * @param  response 此次请求的响应对象
     * @param  HandlerMethod  HandlerMethod对象中存储了此次请求的单元方法的Method对象
     * @param   ex  异常处理
     * */

拦截器和过滤器的区别(面试题)

  • 来源不同
    • 拦截器是 SpringMVC 中的技术,过滤器是 Java EE 中的技术
  • 生效位置不同
    • 拦截器是进入 DispatcherServlet 后才能执行,过滤器是进入到 DispatcherServlet 容器前就可
      以触发。
  • 目标不同
    • 拦截器拦截的目标是 HandlerMethod(控制单元,控制器方法),过滤器可以过滤所有的 URL。
  • 运行机制不同
    • 拦截器是在 HandlerMethod 之前前后和视图处理完成后执行,分为三部分。过滤器只能在目
      标资源前后执行。
  • 接口中方法类型不同
    • 拦截器中的方法都是 default 方法,可以重写也可以不重写。过滤器中的方法都是 abstract 方
      法,如果当前类不是抽象类,必须重写。
  • 上下文不同
    • 拦截器可以获取到 Spring 容器中内容,但是 Filter 因为是被 Tomcat 管理,所以无法获取
      Spring 容器内容。

异常处理

基于注解的配置

    • 局部配置:处理当前类中单元方法的公共异常
@RequestMapping("/testEx")
    @ResponseBody
    public String testEx(){
        Object o=null;
        System.out.println("MyController06.testEx"+o.toString());
        return "今天天气不错,适合学习";
    }
    //方法上使用注解@ExceptionHandler(value=要处理的异常类型的cLass对象)
    @ExceptionHandler(value = NullPointerException.class)
    public String myExcep(){
        return "excep";
    }
    • 全局配置:处理当前项目中所有的异常(先找局部异常配置,再找全局异常配置)
      • 创建异常类MyException.class
      • 在类上加注解@ControllerAdvice
      • 在方法上使用注解@ExceptionHandler(value=要处理的异常类型的cLass对象)
<!--配置注解扫描:扫描bean对象,SpringMVc中的Controller的bean对象只能通过注解创建,不支持bean标签方式-->
<context:component-scan base-package="com.aaan.controller,com.aaan.exception"></context:component-scan>
@ControllerAdvice
public class MyException {
    @ExceptionHandler(value = NullPointerException.class)
    @ResponseBody
    public String myExcep1(HttpServletRequest request, Exception e){
        System.out.println("我是全局异常处理方法");
        return "我是全局异常处理方法";
    }
}

基于配置文件的配置

程序员自已实现HandlerExceptionResolver接口,并在springmvc.xml将其配置为bean

想要在异常出现时跳转到指定页面,只需要在 springmvc.xml 文件中添加异常解析器即可。

(配置文件优先级低于注解的方式)

<!--下面的配置表示当出现空指针异常时跳转到 /error1.jsp,其他异常都跳转到 /error2.jsp-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  <property name="exceptionMappings">
    <props>
      <prop key="java.lang.NullPointerException">/error1.jsp</prop>
      <prop key="java.lang.Exception">/error2.jsp</prop>
    </props>
  </property>
</bean>

上面的方式只有在 Spring MVC 中出现异常时才会触发,也可以使用 Java EE 中的配置方式。

在 web.xml 中配置 error-page 即可。

<error-page>
  <exception-type>java.lang.NullPointerException</exception-type>
  <location>/error3.jsp</location>
</error-page>

基于状态码跳转到指定页面

在 web.xml 中配置,使得根据状态码跳转到指定页面

<error-page>
        <error-code>404</error-code>
        <location>/add.jsp</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/hallo.jsp</location>
    </error-page>

运行原理

1. 客户端向服务端发起请求,Spring MVC 总体入口 DispatcherServlet 进行请求分发。

2.DispatcherServlet 把 URL 交给映射处理器 HandlerMapping 进行解析 URL。并寻找是否具

有对应的控制单元。

3. 如果存在控制单元,执行拦截器 Interceptor 的预处理方法 preHandle 进行处理。如果不存

在对应控制单元,拦截器不执行。4. 如果拦截器允许放行,返回给 Servlet 分发器 DispatcherServlet,调用后续组件。

5.Servlet 分发器 DispatcherServlet 使用适配处理器 HandlerAdapter,调用具体的控制单元

方法。

6. 执行单元方法 HandlerMethod。

7. 控制单元 HandlerMethod 执行完成后产生模型和视图组件 ModelAndView。

8. 返回模型和视图组件给 Servlet 分发器 DispatcherServlet。如果有拦截器 Interceptor,执

行 postHandle

9.Servlet 分发器 DispatcherServlet 调用视图解析器 ViewResolver 解析视图

10. 产生视图 View 对象,执行了视图对应的资源。

11. 把结果返回给 Servlet 分发器 DispatcherServlet。如果有拦截器 Interceptor,执行拦截器

的 afterCompletion

12. Servlet 分发器把最终视图执行结果响应给客户端浏览器。

中文乱码

<!--配置编码过滤器-->
<filter>
  <filter-name>myFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <!--设置编码格式-->
  <init-param>
    <param-name>encodeing</param-name>
    <param-value>utf-8</param-value>
  </init-param>
  <!--设置编码格式-->
  <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>myFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

要是乱码还未解决,就得在单元方法中单独设置编码格式

Tomcat临时设置乱码:-Dfile.encoding=utf-8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值