SpringMvc详细知识

学习:

1. 创建一个maven项目,在web.xml文件中用<servlet>标签配置一个中央调度器读取springmvc的配置文件。
    注意: <url-pattern>/</url-pattern>  这个是斜杠的话要在springmvc配置文件中加入如下两行:
    
      <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>
    
2. 加入spring-webmvc依赖,间接把spring的依赖加入到项目中,加入jsp,servlet依赖
3. 创建处理器类并在springmvc文件中加入包扫描,将处理器放入容器中
4. 用bean标签创建视图解析器InternalResourceViewResolver
5. 编写jsp页面

框架学习方法推荐:熟悉、研究官方文档,锻炼自学与做笔记的能力,锻炼自己的项目能力

SpringMvc

一、 回顾MVC

现在让我们来回想一下MVC的构架模式吧,当然基础扎实的你也可以直接看下面的内容

1.1、什么是MVC

  • MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
  • 是将业务逻辑、数据、显示分离的一种方法来组织代码。
  • MVC主要作用是降低了视图与业务逻辑间的双向偶合
  • MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

**Model(模型):**数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

**View(视图):**负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

**Controller(控制器):**接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC就是JSP + servlet + javabean的模式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0w72fOhu-1619613921826)(img/1619266542886.png)]

1.2、Model1时代

  • 在web早期的开发中,通常采用的都是Model1。
  • Model1中,主要分为两层,视图层和模型层。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23UdJYMX-1619613921832)(img/1619266567976.png)]

Model1优点:架构简单,比较适合小型项目开发;

Model1缺点:JSP职责不单一,职责过重,不便于维护;

1.3、Model2时代

Model2把一个项目分成三部分,包括视图、控制、模型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5l5auT5B-1619613921834)(img/1619266592556.png)]

  1. 用户发请求
  2. Servlet接收请求数据,并调用对应的业务逻辑方法
  3. 业务处理完毕,返回更新后的数据给servlet
  4. servlet转向到JSP,由JSP来渲染页面
  5. 响应给前端更新后的页面
职责分析:
Controller:控制器
    取得表单数据
    调用业务逻辑
    转向指定的页面
Model:模型
    业务逻辑
    保存数据的状态
View:视图
	显示页面

Model2这样不仅提高的代码的复用率与项目的扩展性,且大大降低了项目的维护成本。Model 1模式的实现比较简单,适用于快速开发小规模项目,Model1中JSP页面身兼View和Controller两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展性和维护的难度。Model2消除了Model1的缺点。

1.4、回顾Servlet

1、新建一个普通的Maven工程当做父工程(什么也别勾)!导入pom依赖

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</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>
    </dependencies>

2、 建立一个Moudle模块:springmvc-01-servlet , 添加Web app的支持!

右键项目添加项目web支持

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFowd4Ek-1619613921839)(img/1619267617740.png)]

导入模块的 servlet 和 jsp 的 jar 依赖

 <dependencies>
        <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>
    </dependencies>

3\编写一个Servlet类,用来处理用户的请求

//实现Servlet接口
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        //取得参数
        String method = req.getParameter("method");
        if (method.equals("add")){
            req.getSession().setAttribute("msg","执行了add方法");
        }
        if (method.equals("delete")){
            req.getSession().setAttribute("msg","执行了delete方法");
        }
        //业务逻辑
        //视图跳转
        req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

创建相应的jsp页面

/WEB-INF/jsp/hello.jsp

内容:
<body>
 取出服务器的数据: ${msg}
</body>

配置xml地址映射:

 <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.lsp.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
    <!--  配置  请求超时时间-->
    <session-config>
        <session-timeout>15</session-timeout>
    </session-config>
<!--    配置请求跳转首页
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
-->

4、给项目模块配置tomcat

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uG9w9jqW-1619613921841)(img/1619268727851.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPUR0K3a-1619613921845)(img/1619268882441.png)]

不干下面这两步启动照样出错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6o00mCwI-1619613921847)(img/1619271231339.png)]

现在就可以启动tomcat了

若果出现发行版本不支持错误,可参考如下链接:
https://blog.csdn.net/qq_40741513/article/details/112527128

测试:浏览器中输入以下地址

  • xxxxxxx/hello?method=add
  • xxxxxxx/hello?method=delete

所以mvc其实要做的事情基本上就是这些:

*将url映射到java类或java类的方法 .
*封装用户提交的数据 .
*处理请求--调用相关的业务处理--封装响应数据 .
*将响应的数据进行渲染 . jsp / html 等表示层数据 .

说明:

​ 常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另外一些模式如:MVP、MVVM 等等…

二、SpringMVC

(一)、概述

基于spring的一个框架,实际上就是spring的一个模块,专门是做web开发的,也可以理解为servlet的升级。基本上所有的web框架开发底层是servlet,框架是在servlet基础上面加入一些功能,让开发更方便。

我们为什么要学习SpringMVC呢?

 Spring MVC的特点:

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

Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。

DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;

正因为SpringMVC好 , 简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试 . 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等…所以我们要学习 .

最重要的一点还是用的人多 , 使用的公司多 .

spring和springmvc对比

spring:spring是一个容器,ioc能够管理其中的对象,
使用< bean>,@Component,@Repository,@Service,@Controller 等注解

springmvc:也能够创建对象,放入到springmvc容器中(springmvc是基于spring的),springmvc中放的是控制器对象。
 

(二)、springmvc开发的流程

基本思想:
我们要做的是使用 @Controller 注解创建控制器对象(负责业务请求处理) ,把对象放入到springmvc 容器中,把创建的对象作为控制器使用。这个控制器对象能够接收用户的请求,显示处理结果,可以当作是一个servlet使用(但不是servlet)。
因为使用 @Controller 注解创建的是一个普通的对象 ,不是servlet,springmvc赋予了控制器对象一些额外的功能,能像servlet一样处理请求罢了。

web开发底层是servlet,springmvc中有一个对象是继承了servlet:DispatcherServlet(中央调度器)
DispatcherServlet:负责接收用户的所有请求,用户把请求给了DispatcherServlet,之后DispatcherServlet把请求转发给我们的Controller对象,最后Controller对象处理请求。
 
 请求的处理过程:
1、index.jsp 页面发送请求给tomcat、
2、tomcat找到web.xml中的url-pattern映射规则将请求发送给DispatcherServlet—> 
3、DispatcherServlet(中央调度器,类似servlet)根据springmvc的配置文件找到映射地址对应的控制器类并将请求转发给控制器类的请求处理方法 
4、--然后springmvc框架执行请求方法,根据请求方法的内容决定是否将数据转发回页面 


2.1入眠案例:

(1)、新建一个空项目工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ms4OivAu-1619613921848)(img/1619319399726.png)]

(2)、添加一个maven模块

在这里插入图片描述

(2.1)选择webapp骨架,这个骨架是web开发的模板,包含了一些web开发的一些基本的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZxIhSph-1619613921849)(img/1619319555793.png)]

next

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjAOaily-1619613921850)(img/1619319751161.png)]

(2.2)配置模块的maven(已经配置过的可以跳过)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WoetEQn2-1619613921851)(img/1619319832833.png)]

(2.3)修改xml文件的版本(版本太低)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZ5mcKwB-1619613921851)(img/1619321751573.png)]

(2.31)打开项目结构(右上角)

在这里插入图片描述

把原有默认的webModules删掉,然后重新添加新的

在这里插入图片描述

随便修改一下最后的xml名字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6JiRsoJV-1619613921855)(img/1619322745358.png)]

然后再把名字改回来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnqYUXvs-1619613921856)(img/1619322818954.png)]

(2.4)完善maven结构:

在src下添加一个Java与resources文件夹 在这里插入图片描述

将该Java文件夹指定为源代码文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTQpwXO2-1619613921858)(img/1619321389898.png)]

resources文件也是差不多的操作

(2.5)给项目配置tomcat

在这里插入图片描述 在这里插入图片描述
在这里插入图片描述

。。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4iKGBTr-1619613921864)(img/1619323946370.png)]

(3)、案例需求
需求:用户在页面发起一个请求,请求交给springmvc的控制器对象,并显示请求的处理结果

实现步骤:
1	新建web maven工程(上面已经完成)
2	pom.xml文件中加入依赖,spring-webmvc依赖,间接把spring的依赖加入到项目中,jsp,servlet依赖
加入如下内容:
<!--servlet-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

<!--spring mvc-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
 
然后刷新jar依赖

3	重点:在web.xml中注册springmvc框架的核心对象 DispatcherServlet
	1)DispatcherServlet叫做:中央调度器,是一个servlet,他的父类继承 HttpServlet
	2)DispatcherServlet也叫做:前端控制器(from controller)
	3)DispatcherServlet:负责接收用户提交的请求,调用其他控制器对象,并把请求的处理结果显示给用户
	4)DispatcherServlet是springmvc自带的
	5)在类路径(因为刚才已经指定了resources,所以放在resources即可)下创建一个名为springmvc.xml的配置文件 (名称跟web.xml中的一致)  
	
	6)在web.xml文件中添加如下内容“:
	
	<!--声明:注册springmvc的核心对象DispatcherServlet
        我们需要在tomcat服务器启动后,创建DispatcherServlet对象实例
        为什么要创建DispatcherServlet对象的实例呢?
        因为在DispatcherServlet创建过程中,会同时创建springmvc容器对象,
       去读取springmvc的配置文件,把这个配置文件中的对象都配置好,
        当用户发起请求时就可以直接使用对象了。

        因为servlet的初始化会执行init()方法,DispatcherServlet在init()中{
            //创建容器,读取配置文件
            webApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
            //把容器对象放入到ServletContext中
            getServletContext().setAttribute(key,ctx);
         }
    -->
    <servlet>
           <!--自定义springmvc配置文件名称-->
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--自定义springmvc读取文件的位置-->
        <init-param>
            <!--springmvc配置文件的位置属性-->
            <param-name>contextConfigLocation</param-name>
            <!--指定自定义文件的位置-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>

        <!--表示在tomcat启动后,创建servlet对象
            数字表示启动后创建对象的顺序,数值越小,tomcat创建对象越早,要求大于等于0的整数
        -->
        <load-on-startup>1</load-on-startup>

    </servlet>

        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <!--
                使用框架的时候,url-pattern可以使用两种值
                1.使用扩展名方式,语法 *.xxxx , xxxx时自定义扩展名。常用的方式 *.do, *.action, *.mvc等等
                    http://localhost:8080/myweb/some.do
                    http://localhost:8080?myweb/other.do
                2.使用斜杠"/"
            -->
            <url-pattern>*.do</url-pattern>
              <!--    表示.do为后缀的请求都交给这个servlet配置文件请求
        -->
        </servlet-mapping>
 
 
	
4	创建一个发起请求的页面 index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <p>第一个springmvc项目</p>
    <p><a href="some.do">发起some.do请求</a></p>
</body>
</html>
 
5	创建控制器类
	1)在类的上面加入@Controller注解,创建对象,并放到springmvc容器中
	2) 在类中的方法上面加入@RequestMapping注解
    3)代码:

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

/*
 * @Controller:创建处理器对象,对象放在springmvc容器中
 *
 * 能处理请求的都是控制器(处理器):MyController能处理请求,
 *                               叫做后端控制器(back controller)
 * */
@Controller
public class MyController {
    /*
     * 处理用户提交的请求,springmvc中是使用方法来处理的。
     * 方法是自定义的,可以有多种返回值,多种参数,方法名称自定义
     * */

    /*
     * 准备使用doSome方法处理some.do请求。
     * @RequestMapping:请求映射,作用是把一个请求地址和一个方法绑定在一起。
     *                  一个请求指定一个方法处理。
     *             属性:1. value是一个String类型,表示请求的uri地址(这里是:some.do)
     *                     value值是唯一的,不能重复
     *             说明:使用@RequestMapping修饰的方法叫做处理器方法或者控制器方法
     *                   可以处理请求,类似servlet中的doGet,doPost
     * */
    /*
     * 返回值 ModelAndView 表示本次请求的处理结果
     *   model:数据,请求处理完后,要显示给用户的数据
     *   view:视图,比如jsp等
     * */
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(){
        //处理some.do请求,相当于service调用处理完成了
        ModelAndView mv = new ModelAndView();
        //添加数据,框架在请求的最后把数据放入到request作用域
        //request.setAttribute("msg","欢迎使用....");
        mv.addObject("msg","欢迎使用springmvc做web开发");
        mv.addObject("fun","执行的是doSome方法");

        //指定视图,指定视图的完整路径
        //框架对视图执行的forward操作,request.getRequestDispather("/show.jsp").forward(...)
        mv.setViewName("/show.jsp");
        return mv;
    }


    //在springmvc.xml配置视图解析器后,修改 setViewName()
    @RequestMapping(value = {"/other.do","/second.do"})
    public ModelAndView doOther(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","---------欢迎使用springmvc做web开发----------");
        mv.addObject("fun","执行的是doOther方法");

        //当配置了视图解析器后,可以使用逻辑名称(文件名),指定视图
        //框架会使用视图解析器的 前缀 + 逻辑名称 +后缀 组成完整路径
        mv.setViewName("other");
        return mv;
    }
}    
    
6	创建一个作为结果的jsp,显示请求的处理结果。
在webapp目录下创建show.jsp
添加内容:

<h3>${msg}</h3>
<h3>${fun}</h3>

7	在springmvc的配置文件添加内容
	1)声明组件扫描器,指定@Controller注解所在的包名
	 <context:component-scan base-package="com.lsp"></context:component-scan>
	 
	[2)声明视图解析器,帮助处理视图](可选)
 
8   测试

(三)、视图解析器

在我们开发的时候可能会遇到这样的问题:浏览器用户可以直接在浏览器的地址栏输入地址访问我们的jsp页面,这样是不太好的(因为取不到服务器的数据内容)。

我们可以将我们的jsp页面放到WEB-INF目录下的子目录,这样在浏览器的地址栏就没有权限直接访问我们的jsp页面 [普通用户无权访问WEB-INF目录],但是这样我们开发人员也不能直接在浏览器发起请求了, 而需要先在浏览器将请求转发到服务器获取数据,再由服务器将页面跳转到WEB-INF目录下的jsp页面,而当我们把越来越多的jsp页面放在WEB-INF目录时,每次都要注明jsp页面的全路径(有些是一样的)就显得很烦,所以springmvc才会给我们弄了个视图解析器方便我们在服务器写路径jsp页面的转发路径

在配置文件中配置视图解析器:

  <!--声明 springmvc框架中的视图解析器,帮助开发人员设置视图文件的路径-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀:视图文件位置-->
        <property name="prefix" value="/WEB-INF/view/"/>
        <!--后缀:视图文件的扩展名-->
        <property name="suffix" value=".jsp"/>
    </bean>
    

添加了以上内容后以后在服务器将请求转发到   /WEB-INF/view/ 目录下的jsp页面时就可以省略/WEB-INF/view/前缀跟.jsp后缀了

(四)、springMVC注解开发的规则细节

1、@RequestMapping 注解: 定义请求规则

1.1、放在类上面使用
@Controller
@RequestMapping("/user")
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","欢迎使用springmvc做web开发");
        mv.addObject("fun","执行的是doSome方法");
        mv.setViewName("show");
        return mv;
    }
} 

放在类上表明所有要请求该该处理器类的方法的请求地址都要加上类上的地址
比如:要访问MyController类下的doSome()方法
它的请求地址就应该是: xxx/项目名称/user/some.do

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5fc0E6r-1619613921864)(img/1619337272889.png)]

1.2、@RequestMapping 注解的method 属性 :指定请求方式
@Controller
@RequestMapping("/test")
public class MyController {
    /*
    * @RequestMapping(value = "",method = )
    * 	属性:method 表示请求的方式,它的值是RequestMethod类枚举值
    * 	get请求方式, method = RequestMethod.GET
    * 	post请求方式,method = RequestMethod.POST
    * */
    //指定some.do用get请求获取
    @RequestMapping(value = "/some.do",method = RequestMethod.GET)
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","欢迎使用springmvc做web开发");
        mv.addObject("fun","执行的是doSome方法");
        mv.setViewName("show");
        return mv;
    }
    //指定other.do 用post请求获取
    @RequestMapping(value = "/other.do",method = RequestMethod.POST)
    public ModelAndView doOther(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","---------欢迎使用springmvc做web开发----------");
        mv.addObject("fun","执行的是doOther方法");
        mv.setViewName("other");
        return mv;
    }

对应的jsp页面发起请求的方式:

 <a href="test/some.do">发起some.do的get请求</a> 
    <br/>
    <form action="test/other.do" method="post">
        <input type="submit" value="post请求other.do">
    </form>
 

2、RestFull风格请求方式

概念
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

功能
    资源:互联网所有的事物都可以被抽象为资源
    资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
    分别对应 添加、 删除、修改、查询。
    
传统方式操作资源  :通过不同的参数来实现不同的效果!方法单一,post 和 get
	http://127.0.0.1/item/queryItem.action?id=1 查询,GET
	http://127.0.0.1/item/saveItem.action 新增,POST
	http://127.0.0.1/item/updateItem.action 更新,POST
	http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST
	
使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!
	http://127.0.0.1/item/1   查询,GET
	http://127.0.0.1/item     新增,POST
	http://127.0.0.1/item     更新,PUT
	http://127.0.0.1/item/1   删除,DELETE

拿个案例来实践一下:(springmvc的环境搭建前面有了)
(2.1)、建立一个Controller类
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class RestFullController {
    //映射访问路径
    @PostMapping("/commit")
    //在Spring MVC中可以使用  @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
    public String post(  int p1,   int p2, Model model){
        int result = p1+p2;
        System.out.println("post");
        //Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg", "结果:"+result+"       PostMapping");
        //返回视图位置
        return "test";
    }
    @GetMapping("/commit")
    //在Spring MVC中可以使用  @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
    public String get( Model model){
        int result = 0;
        System.out.println("GetMapping");
        //Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg", "结果:"+result+"       PostMapping");
        //返回视图位置
        return "test";
    }
    @DeleteMapping("/commit")
    //在Spring MVC中可以使用  @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
    public String delete(  int p1,   int p2, Model model){
        int result = p1+p2;
        System.out.println("DeleteMapping");
        //Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg", "结果:"+result+"       PostMapping");
        //返回视图位置
        return "test";
    }
    @PutMapping("/commit")
    public String put(  int p1,   int p2, Model model){
        int result = p1+p2;
        System.out.println("PutMapping");
        //Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg", "结果:"+result+"       PostMapping");
        //返回视图位置
        return "test";
    }
}

将如下内容加入到web.xml文件中:
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>  

index.jsp页面发送请求的书写方式:

<p><a href="${pageContext.request.contextPath}/commit.do">GET</a></p>
<form action="${pageContext.request.contextPath}/commit.do" method="post">
p1: <input type="text" name="p1">
b: <input type="text" name="p2">
    <input type="submit" value="post提交">
</form>
<form action="${pageContext.request.contextPath}/commit.do" method="post">
    <input name="_method" type="hidden" value="delete">
    a: <input type="text" name="p1">
    b: <input type="text" name="p2">
    <input type="submit" value="delete提交">
</form>
<form action="${pageContext.request.contextPath}/commit.do" method="post">
    <input name="_method" type="hidden"  value="put">
    a: <input type="text" name="p1">
    b: <input type="text" name="p2">
    <input type="submit" value="put提交">

使用restful风格路径变量的好处?
    使路径变得更加简洁;
    获得参数更加方便,框架会自动进行类型转换。
    通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是/commit/1/a,则路径与方法不匹配,而不会是参数转换失败。

3、处理器类中请求方法的参数

处理器方法可以(自己加在形参上)包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可以在方法中直接使用

1	HttpServletRequest		 请求
2	HttpServletResponse		 应答
3	HttpSession				会话
4	请求中所携带的请求参数	    (即get、post请求中的参数)

前面三个跟javaweb的时候差不多的用法

现在重点是第四个请求中所携带的请求参数 :即服务器中获取页面用户传来的参数的几种方式

3.1、第一种默认方式:逐个接收页面的请求参数
    *   要求:处理器类(控制器)的方法的形参名和请求方式中的参数名必须一致
    *         同名的请求参数赋值给同名的参数
        public String post(  int p1,   int p2, Model model){...}
    方法参数如p1会在系统调用时由系统自动找到页面传来的参数进行赋值

3.2、另一种方式:在形参前使用 @RequestParam(“要获取的参数名”)
/*      
方法中形参跟用户中传来的参数不一样的时候可以用这个方法
* @RequestParam:定义在处理器方法的形参前面
*   属性:1. value,请求参数名,可省略
*        2. required,是一个Boolean类型,默认为true,表示必须包含参数
* */
public String put( @RequestParam("p1") int x,  @RequestParam("p2") int y ){...}             这样就可以将页面传过来的参数p1赋值给x了
      但是用这种方法默认必须要从页面获取到相应的参数,要不然会出现错误
      
      


例子:

@Controller
public class HelloController {
    @RequestMapping("/hello1.do")
    public String  test1(String name,Integer age){
        System.out.println("页面传来的name="+name+"  age="+age);
        return "test";
    }
    @RequestMapping("/hello2.do")
    public String  test2(@RequestParam("name") String n, @RequestParam("age") Integer id){
        System.out.println("页面传来的name="+n+"  age="+id);
        return "test";
    }
}

jsp页面内容:

<p>提交参数给hello1</p>
<form action="${pageContext.request.contextPath}/hello1.do" method="post">
    姓名:<input type="text" name="name"><br/>
    年龄:<input type="text" name="age"><br/>
    <input type="submit" value="提交参数">
</form>
<p>提交参数给hello2</p>
<form action="${pageContext.request.contextPath}/hello2.do" method="post">
    姓名:<input type="text" name="name"><br/>
    年龄:<input type="text" name="age"><br/>
    <input type="submit" value="提交参数">
</form>

框架如何接收用户请求参数:

    * 1 仍然是使用request对象接收请求参数
    *   String strName = request.getParameter("name");
    *   String strAge = request.getParameter("age");
    * 2 springmvc框架通过dispatcherServlet 调用hellController的方法
    *   调用方法时,按名称对应,把接收的参数赋值给形参          test(strName,Integer.valueOf(strAge))但若是前端没有传这些参数中的任一个就会报错
    *   框架会提供类型转换的功能,把String转换为 int ,long ,float ,double等
注意,当方法的形参类型是String类时,若是这个字符串为空的话,仍然会报错,因为Integer.valueOf(strAge)方法转化错误,提示400客户端错误,表示提交参数过程中出现了问题
    * */

注: 在提交请求参数的时候,get请求方式中文没有乱码;post请求方式中文有乱码,需要使用过滤器处理乱码问题。

在web.xml中加入:

 <!--注册声明过滤器,解决post请求乱码的问题-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--设置项目中使用的字符编码-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <!--强制请求对象(HttpServletRequest)使用encoding编码的值-->
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--强制应答对象(HttpServletResponse)-->
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


3.3 第三种方式:按对象接收参数
* 处理器的方法的形参是要接收的java对象,这个对象的属性名和请求中参数名一样
* springmvc会创建形参的Java对象给属性赋值,假如用户请求的参数有一个pName,那么springmvc就会调用这个形参对象对应的参数的set方法来进行属性注入。而且默认是调用对象的无参构造器创建这个形参对象

(多个对象的话,将每个对象当成一个形参并列写在方法的形参上即可,而且用对象这种接收方法不能使用@RequestParam注解)

实例:

创建一个用于实验的bean

bean的要求:  类的属性名要跟用户从浏览器中传来的参数名一样,并且有相应的get\set方法

public class Person {
    private String pName;
    private Integer pId;
    ...//get\set方法
    }

处理方法:

 @RequestMapping("/hello3.do")
    public String  test3(Person person){
        System.out.println("页面传来的pName="+person.getpName()+"  id="+person.getpId());
        return "test";
    }

jsp页面:

<form action="${pageContext.request.contextPath}/hello3.do" method="post">
    姓名:<input type="text" name="pName"><br/>
    年龄:<input type="text" name="pId"><br/>
    <input type="submit" value="提交参数">
</form>

4、处理器类中请求方法的返回值

请求方法的返回值有四种:

4.1返回值 是 ModelAndView

若处理器方法处理完后,需要跳转到其他资源,且又要在跳转的资源间传递数据,此时使用处理器方法返回 值ModelAndView 比较好。当然,若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView对象。

@RequestMapping(value = "/object.do")
public ModelAndView doObject(Student student){
    System.out.println("doObject方法的,name="+student.getName()+", age="+student.getAge());

    ModelAndView mv = new ModelAndView();
    mv.addObject("myname",student.getName());
    mv.addObject("myage",student.getAge());
    mv.addObject(student);
    mv.setViewName("show");
    return mv;
} 

//这个类中的ModelAndView既可以存数据给浏览器又可以实现跳转页面

4.2返回值 是String ,表示逻辑视图名称或完整视图路径
//处理器方法返回 String,表示完整视图路径,此时没有配置视图解析器
    @RequestMapping(value = "/returnString-view2.do")
    public String doReturnView2(HttpServletRequest request,String name, Integer age) {
        System.out.println("---doReturnView2方法---,name=" + name + ", age=" + age);
        //手动添加数据到request作用域
        request.setAttribute("myname",name);
        request.setAttribute("myage",age);
        /*
         * 完整视图路径,项目中没有配置视图解析器
         * 框架对视图执行forward转发操作
         * */
        return "/WEB-INF/view/show.jsp"; //将页面跳转到show.jsp页面
    } 

4.3返回值 是void ,了解

void:不能表示数据,也不能表示视图。在处理ajax的时候,可以使用void返回值。

通过HttpServletResponse输出数据。响应ajax请求。ajax请求服务器端返回的就是数据,和视图无关。

实例:

1.引入js文件jquery-3.3.1.js

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zv3IEoF2-1619613921865)(img/1619360943056.png)]

2.pom.xml文件引入相关依赖
<!--    json-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
<!--json-->

3.在jsp页面引入js文件
jsp页面内容:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.3.1.js"></script>
    <script type="text/javascript">
        $(function () {

            $("#ajax_btn").click(function () {
                $.ajax({
                    url: "ajax_test.do",
                    data: {
                        pName: "zhansan",
                        pId: 18
                    },
                    type: "post",
                    dataType: "json",
                    success:function (resp) {
                        alert(resp.pName+""+resp.pId);
                    }
                });
            });
        });
    </script>
</head>
<body>
<button type="button" id="ajax_btn">ajax</button>
</body>
</html>

请求方法:

    //返回值是void响应Ajax
    @RequestMapping(value = "/ajax_test.do")
    public void  ajax(HttpServletResponse response,String pName,Integer pId) throws IOException {
        System.out.println("ajax...........pName="+pName+"......pId="+pId);
        Person person =new Person();
        person.setpName(pName);
        person.setpId(pId);
        //使用json处理Ajax数据
        String json="";
        if (person!=null){
            ObjectMapper objectMapper=new ObjectMapper();
            json=objectMapper.writeValueAsString(person);
            System.out.println("json.........."+json);
        }
//        /输出数据
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println(json); //传到Ajax的回调函数
        writer.flush();
        writer.close();
    }

4.4返回值 是object 对象
object:例如String,Integer,Map,List,Student 等等都是对象,
对象有属性,属性就是数据。所以返回 object 表示数据,和视图无关。
springmvc 处理器方法返回 object,可以转为 json输出到浏览器,响应 ajax 的内部原理
/*
* 区分返回值类型String是数据,还是视图,看看有没有@ResponseBody注解
* 如果有@ResponseBody注解,返回String就是数据,反之就是视图
同时注意,这种返回值是数据的String它不是json数据格式的而是文本的类型,所以在Ajax接收的时候不要表明是json数据格式
* */

springmvc开发中,返回值是object类型,如何转换为 json 并发送Ajax请求?实现原理及步骤

* 	<mvc:annotation-driven> 注解驱动
	注解驱动实现的功能是:完成java对象到 json,xml,test,二进制等数据格式的转换
	HttpMessageConverter接口:消息转换器
	功能:定义了java转json,xml等数据格式的方法。这个接口有很多实现类
		这些实现类完成了java对象到json,Java对象到xml,Java对象到二进制数据的转换

*	HttpMessageConverter接口的两个方法是:控制器类把结果输出给浏览器时使用的
		boolean canWrite(Class<?> var1, @Nullable MediaType var2);
		void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3)
	
    	1) canWrite:检查处理器方法返回值,能不能转为var2的数据格式
    	2) write:把处理器方法的返回值对象,调用jackson中的ObjectMapper转为json字符串

*	ResponseBody 注解
    放在处理器方法的上面,通过HttpServletResponse输出结果,响应ajax请求


步骤“

1	加入处理json的工具库的依赖,springmvc默认使用的jackson

	pom.xml 加入jackson依赖

	<!--jackson依赖-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>

2 在springmvc配置文件之间加入 mvc:annotation-driven 注解驱动

    主配置文件 springmvc.xml 加入:
    <!--@ResponseBody注解驱动-->
    <mvc:annotation-driven/>

3 在处理器方法的上面加上 @ResponseBody

  控制器方法 MyController.java
   //处理器方法返回一个Person,通过springmvc框架转为json,响应ajax请求
//@ResponseBody 作用:把处理器方法返回对象转为json后,通过HttpServletResponse输出给浏览器
   @ResponseBody
   @RequestMapping("/helloAjax.do")
   public Person  helloAjax(String pName,Integer pId){
        Person  person =new Person();
        person.setpId(pId);
        person.setpName(pName);
       System.out.println(person);
       return person;
   }

/*当返回值是多个参数(用的比较普遍),返回类型改为List集合,其他不变*/


index.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.3.1.js"></script>
    <script type="text/javascript">
        $(function () {

            $("#ajax_btn").click(function () {
                $.ajax({
                    url: "helloAjax.do",
                    data: {
                        pName: "zhansan",
                        pId: 18
                    },
                    type: "post",
                    dataType: "json",
                    success:function (resp) {
                        alert(resp.pName+""+resp.pId);
                    }
                });
            });
        });
    </script>
</head>
<body>
<button type="button" id="ajax_btn">ajax</button>
</body>
</html>

(五)、页面回显服务器传回的数据

(六)、中央调度器

5.1、< url-pattern/ >

在web.xml文件配置springmvc的配置文件(调度器)时

(部分截取)
<servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

这部分中的*.do表示从页面发送过来的以 .do 为后缀的请求都交由现在配置的中央调度器处理;而且 在没有特殊要求的情况下,springmvc 的中央调度器 DispatcherServlet 的< url-pattern/ >常使用后缀匹配方式,如写为 *.do 或者 *.action , *.mvc 等

思考一下:浏览器发起的请求是由哪些服务器程序处理的?  
xxxxx/项目名/index.jsp		    jsp为后缀的请求由tomcat处理
xxxxx/项目名/images/1.jpg	    jpg为后缀的请求由tomcat处理
xxxxx/项目名/html/test.html	html为后缀的请求由tomcat处理
xxxxxx/项目名/some.do	        springmvc框架的(DispatcherServlet中央调度器)

这些说明并不是配置了springmvc中央调度器之后所有的请求都是由springmvc中央调度器处理的,tomcat本身也能处理静态资源的访问,像html,图片,js文件等都是静态资源

tomcat怎么处理的?

tomcat的conf目录下的web.xml文件有一个名称为 default的servlet,在tomcat服务器启动时就会创建。
这个名叫 default 的 servlet 作用是:
        1 处理静态资源
        2 处理未映射到其它servlet的请求(就是程序员没有手动设置的映射地址)
定义内容如下:
<servlet>
    <servlet-name>default</servlet-name> 
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>  // "/" 表示静态资源和未映射的请求都给这个default处理
</servlet-mapping>

既然tomcat服务器中有着这么一个servlet ,且它的 / 是斜杠”/“

那么我们自己的项目中把springmvc中的*.do的 *.do也换成斜杠”/“

会如何呢?

当你的项目的<url-pattern>*.do</url-pattern>使用了 / ,它会替换 tomcat 中的 default这个servlet.  导致所有的静态资源都给DispatcherServlet处理,默认情况下DispatcherServlet没有处理静态资源能力的,因为控制器对象不能处理静态资源的访问,所以静态资源(如html,js,图片,css)在向服务器发起请求时都是报404的错误

那为什么动态资源some.do可以访问(同样也是用了斜杠)
      因为斜杠”/“表示中央调度器能接收所有请求(但是没有处理静态资源能力),所以在浏览器发起.do后缀的请求时提交给中央调度器,这个调度器会根据springmvc的配置文件找到相对应的控制器对象中的方法上的@RequestMapping("/some.do"),所以能处理some.do请求


那么用了这个斜杠后该怎么样去做才能即使有了斜杠也能处理静态资源呢?

有两种方式:

1、 在springmvc配置文件加入:
   <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>
       
  原理是:
 加入这个标签后,springmvc框架会创建控制器对象(类似自己创建的MyController控制器对象),这个对象可以把接收到的请求转发给tomcat的名字叫default的servlet (虽然tomcat的default这个servlet被代替了但是我们可以手动呼叫它)
   但是注意:
   这个default-servlet-handler会和@RequestMapping有冲突,需要同时在springmvc配置文件加入
   <mvc:annotation-driven/>
   来解决问题

2 、使用 < mvc:resources /> (需要掌握)
在 spring3.0 版本后,spring定义了专门用来处理静态资源访问请求的处理器 ResourceHttpRequestHandler。并且添加了< mvc:resources />标签,专门用于解决资源无法访问问题。需要在springmvc配置文件中添加如下形式的配置:
  //springmvc.xml
	<!--第二种处理静态资源的方式
        mvc:resources加入后框架会创建 ResourceHttpRequestHandler这个处理器对象
        让这个对象处理静态资源的访问,不依赖tomcat服务器。
        mapping:访问静态资源的uri地址,使用通配符 **
        location:静态资源在你的项目中的目录位置
	-->
    <mvc:resources mapping="/images/**" location="/images/" />
    <mvc:resources mapping="/html/**" location="/html/" />
    <mvc:resources mapping="/js/**" location="/js/" />
 //这个也会和@RequestMapping有冲突,需要同时在springmvc配置文件加入
 //  <mvc:annotation-driven/>
//   来解决问题

为了方便,一般把静态资源统一放在 static 文件夹下

所以一个标签语句就可以指定多个静态资源的访问路径(重点):
    <mvc:resources mapping="/static/**" location="/static/" />

在这里插入图片描述

5.2、路径问题

地址分类
1 、绝对地址:带有协议名称的,例如:http://www/baidu.com

2 、相对地址,没有协议开头的,例如:user/some.do,/user/some.do
             相对地址不能独立使用,必须有一个参考地址。通过参考地址+相对地址本身才能指定资源路径

3 、参考地址

​ 在你的页面中,访问地址不加 " / " 时,参考的是当前页面的地址

在jsp页面中发起请求:
<a href="user/some.do">跳转</a>

在jsp页面发起 user/some.do请求,它访问地址会变为http: //localhost:8080/06_path/test/some.do
在你的地址没有斜杠开头时,例如上面的:user/some.do,当你点击链接时,访问地址就是当前页面的地址加上后面链接上的地址,如index页面的地址是根路径地址http: //localhost:8080/06_path/,点击链接后这个some.do的请求地址就变成了这样:

http: //localhost:8080/06_path/user/some.do

在你的页面中,访问地址加了 " / " 时,参考的是 参考地址是你的服务器地址,也就是http: //localhost:8080

要请求访问地址在:http: //localhost:8080/项目名/some.do
 
在jsp页面中发起请求:
<a href="/user/some.do">跳转</a>
点击发起请求后访问地址变为:http: //localhost:8080/user/some.do
也就是说它参考地址是你的服务器地址,即http: //localhost:8080
所以说若是访问不到相应的资源也不必奇怪,路径都不对;加上:${pageContext.request.contextPath}就好了,
<a href="${pageContext.request.contextPath}/user/some.do">跳转</a>


(七)、转发与请求重定向

​ 请求转发与重定向是当处理器对请求处理完毕后,向其它资源进行跳转时的两种跳转方式。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。

在这里插入图片描述

请求转发是一次请求:用户在浏览器端发起请求进入到资源一,然后在服务器端中从资源1到资源2,然后从资源2响应回用户,因为请求转发是在服务器端内部实现的,所以可以访问WEB-INF目录中的资源

重定向是两次请求:浏览器用户首先向资源1发起请求,然后资源1告诉浏览器用户你要访问的东西在资源2,然后浏览器用户才向资源2发起请求,因为两次请求都是用户发起的,所以重定向不能访问WEB-INF下的目录

SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了方法封装。所以现在可以使用简
单的方式实现转发和重定向。

原来的方式:

forward:表示转发,实现 request.getRequestDispatcher("xx.jsp").forward()
redirect:表示重定向,实现 response.sendRedirect("xxx.jsp")

forward与redirect都是关键字,他们都不和视图解析器一起工作

5.1、 请求转发

* 处理器方法返回 ModelAndView 时,需在 setViewName()指定的视图前添加 forward:,
且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。
视图页面必须写出相对于项目根的路径。forward 操作不需要视图解析器。

* 处理器方法返回 String,在视图路径前面加入 forward: 视图完整、路径。

案例:

jsp页面准备

请求页面:
index.jsp

<form method="post" action="${pageContext.request.contextPath}/dosomething" >
    <input type="text" name="pName" >
    <input name="pId" type="text">
    <input type="submit" value="提交">
</form>
 
跳转页面:
tets.jsp

<body>
</body>

Controller类方法准备

 请求转发:
 @RequestMapping("/dosomething.do")
    public String  doSomething(String pName,Integer pId){
        Person  person =new Person();
        person.setpId(pId);
        person.setpName(pName);
        System.out.println("来到doSomething。。。。");
        System.out.println(person.getpName());
        ModelAndView modelAndView =new ModelAndView();
        modelAndView.addObject("msg",person);
        //  modelAndView.setViewName("forward:/WEB-INF/view/test.jsp");
        return "forward:WEB-INF/view/show.jsp";
    }
  请求重定向:
     @RequestMapping("/donothing.do")
    public ModelAndView  donothing(String pName,Integer pId){
        Person  person =new Person();
        person.setpId(pId);
        person.setpName(pName);
        System.out.println("donothing。。。。");
        System.out.println(person.getpName());
        ModelAndView modelAndView =new ModelAndView();
        modelAndView.addObject("msg",person);
        modelAndView.setViewName("redirect:data.jsp");
     //  modelAndView.setViewName("redirect:WEB-INF/view/show.jsp");// //会发现若果重定//向到WEB-INF目录下虽然能进入请求方法但是跳转不成功
         return modelAndView;
    }

视图解析器准备:

  <!--声明 springmvc框架中的视图解析器,帮助开发人员设置视图文件的路径-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

5.2、 使用请求重定向时参数的传递

   @RequestMapping("/donothing.do")
    public String  donothing(String pName,Integer pId){
        Person  person =new Person();
        person.setpId(pId);
        person.setpName(pName);
        System.out.println("donothing。。。。");
        System.out.println(person.getpName());
        ModelAndView modelAndView =new ModelAndView();
        modelAndView.addObject("msg",person);
       // return "redirect:WEB-INF/view/show.jsp";
         return "redirect:index1.jsp";
    }
     
 因为重定向是二次请求:第一次请求是来到   @RequestMapping("/donothing.do"),这是请求1,当代码执行到 ModelAndView modelAndView =new ModelAndView();
        modelAndView.addObject("msg",person);
 这两句时, modelAndView  将person添加到请求1 的请求域中,然后代码执行 return "redirect:index1.jsp"; 这句,这时这个是请求2,存放在请求1 的数据不能在请求2的请求域中取出,所以浏览器用户直接在jsp页面中以${msg}取数据就接收不到数据。
  

那如何在使用重定向的时候也能向用户传递参数数据呢?

解决方法是:在取数据的时候直接取页面的请求域数据
具体做法是:把  ${msg}  换成  ${param.msg}   就可以取到数据了
这样子就相当于  <%=request.getParameter("msg")%>

(八)、异常处理

异常发生时处理的逻辑:
  1. 需要把异常记录下来,记录到数据库、日志文件等
  记录日志发生的时间,是哪个方法产生的,异常错误的内容
  2. 发送通知,把异常的信息通过右键,短信微信发给相关工作人员
  3. 给用户一个友好的提示信息。

通常我们在方法中处理异常的时候都是使用try\catch的方式去弄,但是有时候代码中每个方法都有可能存在异常,一个程序中的try\catch就变得非常多,代码就变得非常臃肿,后期维护的时候成本就变高了

所以springmvc框架提供一了一种集中处理异常的方法:统一全局异常处理;基本思想是将Controller中的所有异常处理都集中到一个地方,类似于aop的思想,将异常处理于业务处理的代码分开,解耦合。

实现的方式涉及到两个注解:@ExceptionHandler 注解、@ControllerAdvice

7.1、@ExceptionHandler 注解

使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可
选属性 value,为一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹
配的异常。
而被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法
参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。
系统会自动为这些方法参数赋值。
对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。

示例:

1. 搭建环境(跟上面一样)
2. 新建一个自定义异常类如MyUserException , 再定义它的子类NameException,AgeException
3. 在Controller中抛出NameException,AgeException  
4. 创建一个普通类作为异常处理类
   1)在类的上面加入@ControllerAdvice注解
   2)在类中定义方法,方法上面加入@ExceptionHandler
5. 创建处理异常的视图页面(提示用户出现上面错误了) 
6. 创建springmvc的配置文件
   1)配置组件扫描器,扫描@Controller注解
   2)配置组件扫描器,扫描@ControllerAdvice注解
   3)声明注解驱动

自定义异常类 :

public class MyUserException  extends  Exception{
    public MyUserException() {
        super();
    }
    public MyUserException(String message) {
        super(message);
    }
}

/***********************/

//姓名有异常时抛出
public class NameException  extends MyUserException{
    public NameException() {
        super();
    }
    public NameException(String message) {
        super(message);
    }
}
/************************/
public class AgeException  extends MyUserException{
    public AgeException() {
        super();
    }
    public AgeException(String message) {
        super(message);
    }
}

控制器方法:

import com.exception.AgeException;
import com.exception.MyUserException;
import com.exception.NameException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age) throws MyUserException {
        //处理some.do请求了。 相当于service调用处理完成了。
        ModelAndView mv  = new ModelAndView();
        //try {
        //根据请求参数抛出异常
        if (!"zs".equals(name)) {
            throw new NameException("姓名不正确!!!");
        }
        if (age == null || age > 80) {
            throw new AgeException("年龄比较大!!!");
        }
        //}catch(Exception e){
        //   e.printStackTrace();
        //}
        mv.addObject("myname",name);
        mv.addObject("myage",age);
        mv.setViewName("show");
        return mv;
    }
}

定义一个普通类作为异常处理类:

import com.exception.AgeException;
import com.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * @ControllerAdvice : 控制器增强(也就是说给控制器类增加功能--异常处理功能)
 *           位置:在类的上面添加这个注解。
 *  特点:必须让框架知道这个注解所在的包名,需要在springmvc配置文件声明组件扫描器。
 *  指定@ControllerAdvice所在的包名
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    //定义方法,处理发生的异常
    /*
        处理异常的方法和控制器方法的定义一样, 可以有多个参数,可以有ModelAndView,
        String, void,对象类型的返回值
        形参:Exception,表示Controller中抛出的异常对象。
        通过形参可以获取发生的异常信息。
        @ExceptionHandler(异常的class):表示异常的类型,当发生此类型异常时,
        由当前方法处理
     */
    @ExceptionHandler(value = NameException.class)
    public ModelAndView doNameException(Exception exception){
        //处理NameException的异常。
        /*
           异常发生处理逻辑:
           1.需要把异常记录下来, 记录到数据库,日志文件。
             记录日志发生的时间,哪个方法发生的,异常错误内容。
           2.发送通知,把异常的信息通过邮件,短信,微信发送给相关人员。
           3.给用户友好的提示。
         */
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","姓名必须是zs,其它用户不能访问");
        mv.addObject("ex",exception);
        mv.setViewName("nameError");
        return mv;
    }
    //处理AgeException
    @ExceptionHandler(value = AgeException.class)
    public ModelAndView doAgeException(Exception exception){
        //处理AgeException的异常。
        /*
           异常发生处理逻辑:
           1.需要把异常记录下来, 记录到数据库,日志文件。
             记录日志发生的时间,哪个方法发生的,异常错误内容。
           2.发送通知,把异常的信息通过邮件,短信,微信发送给相关人员。
           3.给用户友好的提示。
         */
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","你的年龄不能大于80");
        mv.addObject("ex",exception);
        mv.setViewName("ageError");
        return mv;
    }
    //处理其它异常, NameException, AgeException以外,不知类型的异常
    @ExceptionHandler
    public ModelAndView doOtherException(Exception exception){
        //处理其它异常
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","你的年龄不能大于80");
        mv.addObject("ex",exception);
        mv.setViewName("defaultError");
        return mv;
    }
}

在springmvc的配置文件中添加:(将刚才的控制器加入mvc容器中) 在这里插入图片描述

 <context:component-scan base-package="com.controller"/>
    <!--@ResponseBody注解驱动、异常-->
    <mvc:annotation-driven/>

编写jsp页面:

index页面:

<form method="post" action="${pageContext.request.contextPath}/something.do" >
    <input type="text" name="name" >
    <input name="age" type="text">
    <input type="submit" value="提交测试异常">
</form>

/WEB-INF/view/目录下创建错误提示页面

ageError.jsp:
<body>
ageError.jsp<br>
提示信息:${msg}
异常信息:${ex.message}
</body>

defaultError.jsp:
<body>
defaultError.jsp
提示信息:${msg}
异常信息:${ex.message}
</body>

nameError.jsp:
<body>
nameError.jsp
提示信息:${msg}
异常信息:${ex.message}
</body>


(九)、拦截器

SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,类似于Servlet开发中的过滤器Filter,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。
当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

9.1、过滤器Filter与SpringMVC 拦截器的区别

过滤器:
* 是servlet规范的一部分,任何javaweb工程都可以使用
* 主要功能是用来过滤请求参数,设置编码字符集等工作
* 使用时在url-pattern中配置了  /*  之后可以对所有想要访问的资源进行过滤

拦截器:
* 是springmvc框架自己的东西,可以看做是多个Controller中公用的功能,集中到拦截器统一处理。使用的aop的思想

* 拦截器拦截用户的请求,做请求判断处理。
* 拦截器是全局的,可以对多个Controller做拦截。 
   一个项目中可以有0个或多个拦截器, 他们在一起拦截用户的请求。
	拦截器常用在:用户登录处理,权限检查, 记录日志。
* 拦截器只会拦截能被访问的控制器请求方法,若是访问的是jsp\html等静态资源是不能进行拦截的

/***********************************************************/
/***********************************************************/
1.过滤器是servlet中的对象,  拦截器是框架中的对象
2.过滤器实现Filter接口的对象, 拦截器是实现HandlerInterceptor
3.过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的。
  拦截器是用来验证请求的,能截断请求。
4.过滤器是在拦截器之前先执行的。
5.过滤器是tomcat服务器创建的对象
  拦截器是springmvc容器中创建的对象
6.过滤器是一个执行时间点。
  拦截器有三个执行时间点
7.过滤器可以处理jsp,js,html等等
  拦截器是侧重拦截对Controller的对象。 如果你的请求不能被DispatcherServlet接收, 这个请求不会执行拦截器内容
8.拦截器拦截普通类方法执行,过滤器过滤servlet请求响应


9.2、 如何实现拦截器

截器的使用步骤:
 1.定义类实现HandlerInterceptor接口
 2.在springmvc配置文件中,声明拦截器, 让框架知道拦截器的存在。


拦截器的执行时间:
  1)在请求处理之前, 也就是controller类中的方法执行之前先被拦截。
  2)在控制器方法执行之后也会执行拦截器。
  3)在请求处理完成后也会执行拦截器。


案例:

1、 创建一个普通类,作为拦截器使用

​ 1)实现HandlerInterceptor接口
​ 2)实现接口中的三个方法:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {…}

/*
     * preHandle叫做预处理方法。
     *   重要:是整个项目的入口,门户。 当preHandle返回true 请求可以被处理。
     *        preHandle返回false,请求到此方法就截止。
     *
     * 参数:
     *  Object handler : 被拦截的控制器对象
     * 返回值boolean
     *   true:请求是通过了拦截器的验证,可以执行处理器方法。
         *   拦截器的MyInterceptor的preHandle()
             =====执行MyController中的doSome方法=====
             拦截器的MyInterceptor的postHandle()
             拦截器的MyInterceptor的afterCompletion()
         *
     *   false:请求没有通过拦截器的验证,请求到达拦截器就截止了。 请求没有被处理
     *      拦截器的MyInterceptor的preHandle()
     *
     *
     *  特点:
     *   1.方法在控制器方法(MyController的doSome)之前先执行的。
     *     用户的请求首先到达此方法
     *
     *   2.在这个 方法中可以获取请求的信息, 验证请求是否符合要求。
     *     可以验证用户是否登录, 验证用户是否有权限访问某个连接地址(url)。
     *      如果验证失败,可以截断请求,请求不能被处理。
     *      如果验证成功,可以放行请求,此时控制器方法才能执行。
     */


public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception {…}

/*     (了解)
    postHandle:后处理方法。   
参数:    Object handler:被拦截的处理器对象MyController                      ModelAndView mv:处理器方法的返回值    
特点:     1.在处理器方法之后执行的(MyController.doSome())    
          2.能够获取到处理器方法的返回值ModelAndView,可以修改ModelAndView中的数据和视图,可以影响到最后的执行结果。    
          3.主要是对原来的执行结果做二次修正,
          ModelAndView mv = MyController.doSome();              postHandle(request,response,handler,mv); */

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {…}

 /*   (了解)
      afterCompletion:最后执行的方法
      参数
        Object handler:被拦截器的处理器对象
        Exception ex:程序中发生的异常
      特点:
       1.在请求处理完成后执行的。框架中规定是当你的视图处理完成后,对视图执行了forward。就认为请求处理完成。
       2.一般做资源回收工作的, 程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收。
     */

拦截器类代码:

package com.controller.handle;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
//拦截器类:拦截用户的请求。
public class MyInterceptor implements HandlerInterceptor {
    private long btime = 0;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        btime = System.currentTimeMillis();
        System.out.println("拦截器的MyInterceptor的preHandle()");
        //计算的业务逻辑,根据计算结果,返回true或者false
        //给浏览器一个返回结果 //request.getRequestDispatcher("/tips.jsp").forward(request,response);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("拦截器的MyInterceptor的postHandle()");
        //对原来的doSome执行结果,需要调整。
        if( mv != null){
            //修改数据
            mv.addObject("mydate",new Date());
            //修改视图
            mv.setViewName("other");
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("拦截器的MyInterceptor的afterCompletion()");
        long etime = System.currentTimeMillis();
        System.out.println("计算从preHandle到请求处理完成的时间:"+(etime - btime ));
    }
}

2、 在springmvc配置文件中声明拦截器
  <!--声明拦截器: 拦截器可以有0或多个-->
    <mvc:interceptors>
        <!--声明第一个拦截器-->
        <mvc:interceptor>
            <!--指定拦截的请求uri地址
                path:就是uri地址,可以使用通配符 **
                      ** : 表示任意的字符,文件或者多级目录和目录中的文件
                http://localhost:8080/myweb/user/listUser.do
                http://localhost:8080/myweb/student/addStudent.do
            -->
            <mvc:mapping path="/**"/>
            <!--声明拦截器对象-->
            <bean class="com.controller.handle.MyInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>


3、编写jsp页面
<form method="post" action="${pageContext.request.contextPath}/test.do" >
    <input type="text" name="name" >
    <input name="age" type="text">
    <input type="submit" value="提交测试拦截器">
</form>

9.3 多个拦截器的执行顺序

原则:先声明的先执行,后声明的后执行
   因为多个拦截器在springmvc中存在时其实是一个ArrayList集合,按照声明的顺序放进该集合内。

顺序如下:(aop)

第一个拦截器preHandle=true , 第二个拦截器preHandle=true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1EvpOYs-1619613921868)(img/1619530283904.png)]

=================================================================
多个拦截器:
第一个拦截器preHandle=true , 第二个拦截器preHandle=true 

111111-拦截器的MyInterceptor的preHandle()
22222-拦截器的MyInterceptor的preHandle()
=====执行MyController中的doSome方法=====
22222-拦截器的MyInterceptor的postHandle()
111111-拦截器的MyInterceptor的postHandle()
22222-拦截器的MyInterceptor的afterCompletion()
111111-拦截器的MyInterceptor的afterCompletion()

---------------------------------------------------
第一个拦截器preHandle=true , 第二个拦截器preHandle=false

111111-拦截器的MyInterceptor的preHandle()
22222-拦截器的MyInterceptor的preHandle()
111111-拦截器的MyInterceptor的afterCompletion()

----------------------------------------------------------
第一个拦截器preHandle=false , 第二个拦截器preHandle=true|false

111111-拦截器的MyInterceptor的preHandle()


9.4、一个登录验证的拦截器

案例需求:

只有经过登录的用户方可访问处理器,否则,将返回“无权访问”提示。
本例的登录,由一个 JSP 页面完成。即在该页面里将用户信息放入 session 中。也就是说,只要访问过该页面,就说明登录了。没访问过,则为未登录用户。

1.index.jsp页面

<form action="/some.do" method="post">
姓名:<input type="text" name="name" ><br>
年龄: <input type="text" name="age"><br>
     <input type="submit" value="提交">
</form>


2.拦截器方法

 @RequestMapping("/some.do")
    public ModelAndView some(String name,Integer age){
        System.out.println("11111请求的some.....");
        ModelAndView mv=new ModelAndView();
        mv.addObject("myName",name);
        mv.addObject("myAge",age);
        mv.setViewName("show");
        return mv;
    }

3 . 创建login.jsp 模拟登陆(将用户的信息放在session);

​ 创建一个 loginout.jsp模拟退出系统(删除session数据)

login.jsp:
<body>
模拟登录了
<%
    session.setAttribute("name","zs");
%>
</body>

loginout.jsp:
<body>
模拟退出了
<%
    session.removeAttribute("name");
%>
</body>

4.创建一个拦截器,从session中获取用户登录数据,验证能否访问系统

public class MyInterutor implements HandlerInterceptor {
    private long btime = 0;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        btime = System.currentTimeMillis();
        System.out.println("拦截器111111的MyInterceptor的preHandle()");
        //计算的业务逻辑,根据计算结果,返回true或者false
        //给浏览器一个返回结果 //request.getRequestDispatcher("/tips.jsp").forward(request,response);
        String loginName="";
        Object name = request.getSession().getAttribute("name");
        if (name!=null){
            loginName= (String) name;
        }
//        判断登录的用户是否符合要求
        if (!"zs".equals(loginName)){
            request.getRequestDispatcher("/tips.jsp").forward(request,response);
            System.out.println("dsd");
            return false;
        }
        return true;
    }
}

5.在配置文件中配置拦截器(扫描器)

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

    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>


<mvc:interceptors>
        <!--声明第一个拦截器-->
        <mvc:interceptor>
            <!--指定拦截的请求uri地址
                path:就是uri地址,可以使用通配符 **
                      ** : 表示任意的字符,文件或者多级目录和目录中的文件
                http://localhost:8080/myweb/user/listUser.do
                http://localhost:8080/myweb/student/addStudent.do
            -->
            <mvc:mapping path="/**"/>
            <!--声明拦截器对象-->
            <bean class="com.controller.handler.MyInterutor" />
        </mvc:interceptor>
    </mvc:interceptors>


6.测试,运行项目

先访问index.jsp页面输入zs为用户名,是被拦截的;
再访问login.jsp页面,然后进入index.jsp页面重新输入一个用户,访问成功
再访问loginout.jsp页面,然后进入index.jsp页面重新输入一个用户,访问不成功

(十)、映射器,适配器,视图解析器

后续更新…

猜你喜欢

转载自blog.csdn.net/glass__sky/article/details/116242913