QA 由浅入深 Spring Framework 5.0(九)- MVC

Section 01 - MVC思想与Spring MVC

MVC是一种软件架构模式

  • M:Model,即数据模型,主要是封装和映射数据,对应的是Java Bean实体类
  • V:View,即视图,显示数据的页面,html,jsp或者ftl文件
  • C:Controller,即控制器,控制数据处理逻辑及页面跳转

MVC的流程大概是

image.png

Spring MVC

Spring为了展现层提供的基于MVC模式设计的Web框架,是主流的MVC框架之一,Spring MVC通过注解让Java Bean成为处理请求的控制器,无需实现任何接口,并且支持Rest风格的URL形式,采用松散耦合可插拔的组件结构,比其他MVC框架更具有扩展性和灵活性

重要组件:

  • DispatcherServlet:负责接收请求和转发请求
  • HandlerMapping:处理器映射器,根据请求查找执行类Handler,并返回Handler给到DispatcherServlet
  • HandlerAdapter:处理器适配器,执行DispatcherServlet发来的Handler,并返回视图ModelAndView给DispatcherServlet。
  • Handler:执行Handler方法,返回ModelAndView给HandlerAdapter,HandlerAdapter在返回给DispatcherServlet
  • ViewResolver:视图解析器,解析DispatcherServlet发来的ModelAndView,返回视图
  • View:视图,渲染DispatcherServlet发来的视图,并返回给DispatcherServlet,并由DispatcherServlet返回给客户端展示

Section 02 - QuickStart

创建Spring MVC项目

首先创建一个maven项目spring-web-mvc,添加项目依赖

<properties>
    <spring-version>5.3.13</spring-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring-version}</version>
    </dependency>
</dependencies>
复制代码

添加 Framework Support,添加Web框架支持,勾选创建web.xml

add web support.gif

打开Project Structure,选择Artifacts,首先在WEB-INF下创建一个lib文件夹,然后将右侧的jar包导入进lib文件夹中

put into lib.gif

配置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">
    
    <!--DispatchServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <!--
            DispatcherServlet是Spring MVC最核心的对象
            DispatcherServlet用于拦截Http请求,
            并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理
        -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--applicationContext.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <!--
            在Web应用启动时自动创建Spring IOC容器,
            并初始化DispatcherServlet
        -->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--"/" 代表拦截所有请求,/*拦截所有请求包括jsp页面这些请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>
复制代码

在resources目录下新建applicationContext.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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.citi">
    </context:component-scan>

    <!--配置试图解析器,自动拼接页面地址,自动在jsp页面前增加/WEB-INF/pages/-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
复制代码

配置Tomcat

image.png

新建controller包,创建HelloController控制类

@Controller
public class HelloController {

    // 处理转发请求
    @RequestMapping("/hello")
    public String hello(){
        System.out.println("处理中....");
        // 返回页面
        //return "/WEB-INF/pages/success.jsp";
        // 添加视图解析器后,自动拼接页面地址
        return "success";
    }
}
复制代码

@RequestMapping:告诉Spring MVC这方法处理什么请求,其中"/"可以省略,习惯加上会比较好

在WEB-INF文件夹下新建pages文件夹,用来存放jsp文件,新建success.jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Success</title>
</head>
<body>
    success
</body>
</html>
复制代码

启动Tomcat,浏览器输入 http://localhost:8080/hello 页面显示success

image.png

HelloController运行流程:
1).客户端(浏览器)点击链接发送localhost:8080/hello请求
2).Tomcat收到请求
3).SpringMVC的dispatchServlet前端控制器收到所有请求
4).dispatchServlet查看请求地址和@RequestMapping标注的哪个Controller类的方法相匹配
5).前端控制器知道目标类和方法,利用反射执行目标方法
6).方法执行完成之后会有一个返回值,SpringMVC认为这个返回值就是要去的页面
7).拿到返回值,使用视图解析器拼接得到完整的页面地址
8).前端控制器根据地址转发到具体页面

applicationContext.xml配置文件

在web.xml配置文件中的param-value标签下指定了配置文件位置,Spring MVC有默认的配置文件。

注销配置文件,启动Tomcat

image.png

报错"Could not open ServletContext resource [/WEB-INF/springmvc-servlet.xml]" 默认会找这个配置文件springmvc-servlet.xml
修改Servlet名字为dispatcherServlet,再次启动Tomcat,可以看出默认配置文件名称为DispatcherServlet Bean的名字-servlet.xml

image.png

如果想不指定配置文件,就需要在WEB-INF目录下配置一个名字为[Servlet名字-servlet.xml]的配置文件,Spring MVC会在Tomcat容器启动时自动查找这个文件
在WEB-INF下创建一个dispatchServlet-servlet.xml文件,文件内容就是原来resources目录下applicationContext.xml的内容,并将applicationContext.xml删除,再次启动Tomcat

image.png 不再报错,页面可以正常访问

"/" 与 "/*" 的区别

web.xml配置文件中url-parttern标签的 "/" 代表拦截所有请求(不包括JSP页面),"/*"拦截所有请求包括jsp页面这些请求,将配置中的“/”改为“/*”,重新启动容器,并访问/hello,出现404报错

image.png 控制台报错如下

image.png 说明 "/" 拦截的请求不包括jsp页面,"/*" 拦截所有的请求,包括jsp页面

在web目录下增加index.html,将 “/*” 改为 “/”,重启容器,访问index.html

image.png

页面无法访问,控制台报错,这是为什么?

image.png 首先tomcat文件中conf文件夹下本身就有一个web.xml文件,项目中的web.xml就是继承Tomcat conf文件夹下的web.xml

Tomcat中web.xml相当于是父类,其中配置了DefaultServlet,专门用来处理静态资源的,项目中web.xml是子类,都配置了 "/",相当于子类重写了父类的方法,那么Tomcat中的web.xml中的defualtServlet配置的 "/" 也就失效了,也就无法处理html静态资源了。 image.png 其中default配置类 "/" image.png

项目中配置 “/” 不拦截jsp请求是为了放行jsp,将jsp交由tomcat处理,tomcat的web.xml中有一个JspServlet,专门处理*.jsp文件的

image.png

image.png

“/*” 就是直接拦截所有请求,"/" 是为了迎合Rest风格的URL地址

@RequestMapping注解

Spring MVC 使用@RequestMapping注解标注xxController或者方法可以处理哪些URL请求

  • @RequestMapping定义在类上表明提供上层URL地址,这是针对方法上@RequestMapping的URL地址来说的
  • @RequestMapping定义在方法上,标注了方法能够处理的具体请求

在controller包下创建一个MappingController,测试@RequestMapping注解

plus:不能两个方法处理同一个请求即不能有两个方法的RequestMapping中value是一样的 严格遵循一个方法处理一个请求

@RequestMapping标注在类上,为当前所有方法所处理的请求前增加前缀

@Controller
@RequestMapping("/mapping")
public class MappingController {

    @RequestMapping("/handle")
    public String handle(){
        String method = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println(this.getClass().getSimpleName() + "类的" + method + "方法正在执行");
        return "success";
    }
}    
复制代码

浏览器输入http://localhost:8080/mapping/handle

image.png

@RequestMapping其他属性

image.png

method:限定请求方式,默认为空,也就是说任何请求方法都可以处理

在MappingController中增加方法

@RequestMapping(value = "/handle_post_req", method = RequestMethod.POST)
public String handlePostReq(){
    String method = Thread.currentThread().getStackTrace()[1].getMethodName();
    System.out.println(this.getClass().getSimpleName() + "类的" + method + "方法正在执行");
    return "success";
}
复制代码

表单形式发送POST请求,在index.jsp页面的body标签中增加form表达代码

<p>发送POST请求</p>
<form action="/mapping/handle_post_req" method="post">
  <button type="submit">发送POST请求</button>
</form>
复制代码

重新启动Tomcat,点击按钮发送POST请求,成功跳转至success.jsp页面

image.png

控制台输出 image.png

而在浏览器中输入http://localhost:8080/mapping/handle_post_req, 则会报错,说明请求不支持GET方式

image.png

params:指定请求参数

  • params是一个数组
  • params支持简单的表达式,指出 “!”, “!=”
  • params指定请求中必须包含指定名的请求参数

在MappingController类中新增代码

@RequestMapping(value = "/handle_params", params = {"username"})
public String handleParams(){
    String method = Thread.currentThread().getStackTrace()[1].getMethodName();
    System.out.println(this.getClass().getSimpleName() + "类的" + method + "方法正在执行");
    return "success";
}
复制代码

请求不携带params指定的参数,报错404 image.png 增加username参数,成功跳转至success页面

image.png

!params表示请求中必须不带params参数,修改代码

@RequestMapping(value = "/handle_params", params = {"!username"})
public String handleParams(){
    return "success";
}
复制代码

重启tomcat,请求http://localhost:8080/mapping/handle_params?username=stark

image.png

规定参数值,params={"username=peter"},修改代码,重新启动Tomcat,再次输入http://localhost:8080/mapping/handle_params?username=stark

image.png 输入 http://localhost:8080/mapping/handle_params?username=peter

image.png 也可以指定非指定值,如params={"username!=peter"},指定多个限制规则,必须同时满足,多个参数之间使用 "," 连接,URL地址多个参数之间使用 "&" 链接,如params={"username="stark",password,!gender}

image.png

headers,规定请求头表达式与params一样,也是一个数组

HTTP请求头中User-Agent表示发送请求的浏览器,可以使用headers属性指定User-Agent的值
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36

增加代码

@RequestMapping(value = "/handle_headers",headers = {"User-Agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0"})
public String handleHeaders(){
    return "success";
}
复制代码

重启Tomcat,谷歌浏览器中访问

image.png

火狐浏览器中访问 image.png

@RequestMapping的另外两个属性

  • consumes:指定接收内容的类型
  • produces:指定浏览器返回的内容类型

Section 03 - @RequestMapping的模糊匹配

  • ? : 支持匹配一个字符
  • * : 支持匹配任意字符
  • ** : 支持匹配多层路径

新建一个MatchController,测试@RequestMapping的模糊匹配,以及通配符的使用

@Controller
public class MatchController {

    @RequestMapping("/matc?")
    public String matchOne(){
        return "success";
    }
}
复制代码

“?”匹配一个字符,重启tomcat,输入http://localhost:8080/match

image.png

image.png

image.png

“?”可以表示任意一个字符,但是匹配超过1个字符会报错

image.png

“*” 可以匹配多个字符

@RequestMapping("/matc*")
public String matchMore(){
    return "success";
}
复制代码

匹配0个或多个字符 image.png

也可以匹配一层路径

@RequestMapping("/matc*/layer")
public String matchOneLayer(){
    return "success";
}
复制代码

image.png

”**“ 匹配0或者多层

@RequestMapping("/match/**/layers")
public String matchMoreLayers(){
    return "success";
}
复制代码

image.png

Section 04 - @PathVariable注解

通过@PathVariable注解可以将URL中占位符参数绑定到控制器处理方法的入参

新建一个PathVarController

@Controller
public class PathVarController {

    // 路径上可以有站位符,*也是占位符,但是不能获取位置上变量的值,{}可以获取变量的值
    // 方法参数中定义变量接收路径中的变量的值,并用@PathVariable指定路径变量的名称,默认参数中的变量名一致
    @RequestMapping("/user/{id}")
    public String userInfo(@PathVariable("id") String id){
        System.out.println("路径参数id的值为:" + id);
        return "success";
    }
}
复制代码

image.png 控制它打印出路径中变量的值 image.png 只能占一层路径,如果想要获取两层路径就要定义两个占位符

@RequestMapping("/user/{id}/{orderId}")
public String userInfoDetial(@PathVariable("id") String id, @PathVariable("orderId") String orderId){
    System.out.println("路径参数id的值为:" + id);
    System.out.println("路径参数orderId的值为:" + orderId);
    return "success";
}
复制代码

image.png

image.png

REST风格URL

REST:即Representational State Transfer,表现层状态转化,是一种软件架构, REST结构清晰,符合标准,易于理解,扩展方便

  • 资源(Resources):网络上的资源,如文本图片信息等,可以用一个URI(统一资源定位符)来指向,每种资源对应一个特定的URI,可以通过访问URI或者资源
  • 表现层(Representation):把资源具体呈现出来的形式,即表现层,如JSON格式,XML格式,TXT格式
  • 状态转化(State Transfer):每发出一个请求,就代表客户端和服务端的一次交互。HTTP协议是一种无状态的协议,所有的状态都保存在服务器端,客户端想要操作服务器,必须通过某种手段,让服务器端状态发生变化,这种转化建立在表现层之上。HTTP协议里有四种操作方式,PUT表示更新资源,GET表示获取资源,POST表示新增资源,DELETE表示删除资源

猜你喜欢

转载自juejin.im/post/7053685152425181192
QA