SpringMVC

SpringMVC的介绍

问题:

在学习了Spring之后,基于MVC设计模式的项目,我们可以使用Mybatis将数据库替换,使用Spring将Controller层和Service层,以及Service层和数据库层之间进行解耦。但是基于MVC的模式中,在Controller层中的Servlet为请求的代码入口。tomcat服务器在接受到请求后,会根据请求地址自定调用对应的servlet的service方法完成请求处理,但是此流程存在如下问题:
① 每个功能都要声明对应的Servlet,麻烦。
② 在Servlet中获取请求数据比较麻烦。
③响应的方式的代码其实只想声明对应的响应数据。

解决:

项目只声明一个Servlet,该Servlet作为项目请求的公共入口。并且在该Servlet必须声明代码,此代码根据请求地址调用对应的逻辑代码处理请求。如果将逻辑方法全部声明在Servlet中造成代码的体系结构不清晰,将逻辑方法单独声明到逻辑类中(Controller类)。然后Servlet中根据请求动态的调用对应的逻辑类中的逻辑方法处理请求即可。
① 如何在Servlet中获取逻辑类对象呢?
使用Spring容器的子容器,在子容器中存储所有的Controller的实例化对象,然后Servlet一次性从子容器中获取所有的对象即可。在init方法中实现即可。
② 如何在Servlet中根据请求动态调用对象的逻辑方法呢 使用反射+注解。

原理图:
在这里插入图片描述
实现:

SpringMVC

本质:

就是将Servlet进行了封装,提供一个公共的Servlet。该Servlet可以根据请求动态的调用对应的逻辑方法完成请求处理。

优点:

提升开发效率。

使用:

① 导入SpringMVC的相关jar
② 在web.xml中配置Servlet
a. Servlet的访问路径
b. 配置SpringMVC容器对象的配置文件路径
③ 在src下创建并配置SpringMVC配置文件
a. 配置注解扫描路径
b. 配置SpringMVC的驱动解析器。

SpringMVC的基本实现

[1] 创建web项目并导入SpringMVC的jar

在这里插入图片描述

[2] 在src下创建MVC的包结构

在这里插入图片描述

[3] 配置web.xml文件,配置SpringMVC的Servlet

在这里插入图片描述

[4] 在src下创建并配置springmvc.xml文件

在这里插入图片描述
[5] 在Controller包下创建控制器类并声明单元方法
在这里插入图片描述

SpringMVC中单元方法获取请求数据

[1]单元方法中获取请求数据的介绍

问题:

在学习了SpringMVC的基本使用流程后,发现SpringMVC将Servlet进行了封装,在外部声明控制器类,并在其中声明单元方法。DispatcherServlet根据请求动态的调用对应的单元方法处理请求,所以我们需要在单元方法中声明请求处理的逻辑代码。而请求的处理需要获取本次请求的请求数据,那么在单元方法中如何获取请求数据呢?

解决:

请求被tomcat接受后会调用DispatcherServlet处理请求,Tomcat会将封装了此次请求数据的request对象作为实参传递给DispatcherServlet的service方法,而service方法中又会根据请求调用对应的单元方法处理请求,所以只需要在service方法中将请求数据作为实参传递给单元方法使用即可。注意,单元方法必须声明对应的形参接收数据。

实现:

方式一:紧耦方式

DispatcherServlet中的service方法直接将此次请求的request对象传递给调用的单元方法即可。同时在单元方法上声明形参HttpServletRequest来接收request实参即可。

方式二:解耦方式

DispatcherServlet在其service方法中将请求数据根据需求从request对象中获取出来后,将数据直接传递给对应的单元方法使用。同时在单元方法上直接声明对应的形参接收请求数据即可。

[2] 紧耦方式(request)在单元方法中获取请求数据

①在控制器类中声明请求处理单元方法,并在单元方法上声明形参,形参类型为HttpServletRequest,接收DispactherServlet传递的封装了此次请求的请求数据的request对象。
在这里插入图片描述
②在单元方法中使用request.getParameter(“键名”)获取请求数据
在这里插入图片描述
③在单元方法中声明请求处理的逻辑代码
在这里插入图片描述

[3] 解耦方式获取请求数据

  1. 形参属性名即为请求数据的键名

问题:
让DispatcherServlet将请求数据获取后传递给单元方法,但是请求数据的获取需要数据的键名,而DispatcherServlet不是我们自己声明的无法修改其底层代码,怎么将请求数据的键名告诉给DispatcherServlet呢?
解决:
在单元方法上声明形参来接收请求数据时,形参名必须和请求数据的键名一致,DispatcherServlet会将调用单元方法的形参名作为请求数据的键名获取请求数据,然后传递给单元方法。
实现:
在这里插入图片描述

  1. 形参名和请求数据的键名不一致

问题:
如果单元方法的形参名和请求数据的键名一致,DispatcherServlet底层会自动
将请求数据获取后传递给单元方法使用,我们在单元方法中直接使用即可,美滋滋
但是在实际生产环境中,前台请求数据的页面是由张三编写,而后台对应的请求处 理的单元方法是李四声明的。而张三和李四因为沟通不及时,造成张三前台请求
数据的键名和李四后台单元方法的形参名不一致,怎么办?
解决:
① 让张三修改其前台页面请求数据的键名,改成和李四的单元方法的形参名
一致。这样虽然可以但是不现实,因为我们请求数据的键名,在前台有
可能被Js或者CSS在使用,一旦修改造成大量代码失效。
② 让李四修改其单元方法的形参名,改成和请求数据的键名一致。这样虽然
可以但是不现实,因为在单元方法的方法体中我们已经使用形参来进行逻
辑处理了,形参名修改,造成方法体代码也必须修改,工作量比较大。
√ ③ 在请求数据的键名和单元方法的键名之间声明一个过渡的变量,来完成
请求数据的获取。

实现:

在单元方法上的形参声明中使用注解@RequestParam来实现。
在这里插入图片描述

  1. 使用实体类对象获取请求数据

问题:
在学习了使用SpringMVC后,我们可以在单元方法上声明形参直接获取请求数据
只要形参名和请求数据的键名一致即可。但是如果我们的请求数据过多,总不能咱
们声明N个形参来接收请求数据吧?而且按照我们以往的开发经验,请求数据过多
我们会将请求封装到实体类对象中进行使用,保证数据的完整性。那么,在Spring
MVC中一旦请求数据过多,如何在单元方法上获取请求数据呢?
解决:
我们希望在单元方法中直接获取一个封装好请求数据的实体类对象使用,美滋滋。
也就说我们希望DispatcherServlet可以将请求数据封装到实体类对象中,然后将
实体类对象作为实参传递给单元方法使用。在单元方法上声明对应的实体类的形参
类型,来接收DispatcherServlet传递的封装了请求数据的实体类对象,以及告诉
DispatcherServlet使用哪个实体类来封装请求数据。而且,要求实体类的属性名
必须和请求数据的键名一致,DispatcherServlet会按照该方式将请求数据赋值给
实体类的属性。

实现:

/**
 *解耦方式使用实体类对象接收请求
 * 要求:
 *  实体类的属性和请求数据的键名一致,必须提供get/set方法。
 * 注意:
 *  实体类的属性类型使用包装类,避免请求中没有对应的数据时出现类型转换异常。
 */
@RequestMapping("argObject")
public String demoArgObject(User user){
    //处理请求
    System.out.println("MyController.demoArgObject:请求数据使用实体类对象接收:"+user);
    //响应结果
    return "aa";
}
  1. 获取同键不同值的请求数据

问题:
目前我们在单元方法上可以使用形参或者实体类来接收请求数据,美滋滋。但是
有某些请求中,请求数据是同键不同值的。比如,在页面中的多项选择的请求数据,
爱好,fav=1&fav=2&fav3.像这样的请求数据,如何获取呢?
解决:
我们自己使用Request对象获取同键不同值的数据,使用 req.ParameterValues(“键名”),返回值是String[]数组。在单元方法上声明形参,类型为String[]数组类型,要求形参名和请求数据的键名一致即可。

实现:

/**
 * 解耦合方式获取同键不同值的数据
 *  要求:
 *      使用String类型的数组来接收,形参名为请求数据的键名
 */
@RequestMapping("argKeyNotValue")
public String demoArgKeyNotValue(String uname,Integer age,String[] fav){
    //处理请求数据
    System.out.println("MyController.demoArgKeyNotValue:获取同键不同值的请求数据:"+uname+":"+age+":"+fav[0]);
    //响应结果
    return "aa";
}
  1. 混合使用紧耦和解耦方式获取请求数据

问题:
目前我们可以在单元方法中使用形参,实体类,request对象方式获取请求数据,
但是如果请求中的数据,一部分要放入到对应的实体类中,一部分要使用形参直接
获取怎么办?
解决:
我们可以在单元方法上根据自己的需求来声明形参获取请求数据, DispatcherServlet会想尽一切办法给我们声明的单元方法的形参赋值。那么如果
没有赋值,则表明形参声明有问题。我们的获取请求数据的方式可以混合使用。

实现:

/**
 * 单元方法请求数据获取方式混合使用
 */
@RequestMapping("argAll")
public String demoArgAll(User user,String uname,String[] fav,HttpServletRequest request){
    //处理请求
    System.out.println("MyController.demoArgAll:获取请求数据方式混合使用:"+user
                        +":"+uname+":"+fav[0]+":"+
                        request.getParameter("uname"));
    //响应结果
    return "aa";
}

SpringMVC对restful请求的支持

[1] restful格式请求的介绍

问题:

目前我们浏览器发起请求给服务器的时候,一般我们常用的请求方式有两个,get 和post方式。不管get还是post方式,请求数据的格式都是键值对格式,get 方式请求数据是在请求地址上以问号隔开的形式,拼接在请求地址后,post请求 呢是有专门的请求实体的,例如:
get方式请求:
localhost:8080/project/aa?uname=zhangsan&age=18
post方式请求:
地址:localhost:8080/project/aa
请求数据:
uname=zhangsan
age=18
要求我们后台获取请求数据的代码,必须按照指定的键名来获取请求数据。键名
就是请求数据的键名。这样造成,一旦请求数据的键名发生变更,造成后台的逻辑
代码也需要进行变更。前台的请求数据的键名和后台的逻辑代码之间的耦合性过高,造成前台和后台的开发过程中相互依赖性过高。怎么办?

解决:

让前台和后台代码之间进行解耦。也就说不再让请求请求数据的键名造成前后台
代码之间的依赖。前台请求数据的键名发生变更,不影响后台逻辑代码的正常执行
实现:
请求数据不再以键值对的形式发送给后台使用。直接发送数据本身给后台即可。
既然请求数据不再使用键值对,请求数据必须按照指定的格式来进行发送。使用
restful格式。
restful格式:
传统的get方式请求格式:
localhost:8080/project/aa?uname=zhangsan&age18
restful请求格式:
localhost:8080/project/aa/zhangsan/18

注意:

restful格式要求请求数据作为请求地址的一部分发送给后台使用。

[2] restful格式请求后台获取请求数据

问题:

按照以前键值对格式的请求数据,我们在后台直接使用req.getParameter方法
根据键名获取请求数据即可。但是在restful请求格式中,请求数据是作为请求
地址的一部分来进行发送的,那么在后台我们如何获取restful格式的请求数据呢?

解决:

从服务器的角度,服务器在接收到请求后,会根据请求地址调用对应的Servlet
来处理请求。
①传统请求格式的流程:
localhost:8080/project/aa?uname=zhangsan&age=18
服务器在接收到此请求后,会调用一个url-pattern为aa的Servlet来处理 请求
②restful格式请求的流程:
localhost:8080/project/aa/zhangsan/18
服务器在接收到此请求后,会调用一个url-pattern为aa/zhangsan/18的 Servlet来处理请求。
③通过原有方式和restful格式的请求流程处理的比较,在后台处理请求的Servlet 就不能针对的声明某个url-pattern值为确定值的Servlet来处理请求。创建一个 公共Servlet来处理请求,而公共的Servlet的url-pattern的值为”/”即可。除 jsp请求以外的所有的请求都会被该Servlet拦截处理,无需关心请求数据是单独 携带还是在请求地址中携带了。
④封装的公共的Servlet既可以处理键值对类型的请求,又可以处理restful格式 的请求。

实现:

必须在后台封装一个公共Servlet,该Servlet可以处理键值对请求和restful
请求,并且能够按照执行的格式获取restful中的请求数据。

[3] SpringMVC对restful格式的支持

解释:

DispactherServlet拦截除jsp请求以外的所有请求,浏览器发起了一个
restful格式的请求,会被DispatcherServlet拦截处理。而DispatcherServlet
根据请求调用对应的单元方法处理请求。比如:
locahost:8080/project/aa/zhangsan/18
DispatcherServlet会调用一个@RequestMapping的值为”aa/zhangsan/18”
的单元方法来处理此次请求。但是restful格式请求的请求数据每次都不同,也就
说每次请求地址都不相同,我们无法声明对应的单元方法来处理请求。所以 SpringMVC在单员方法中使用指定的格式来进行模糊匹配,具体实现参照示例代 码。

示例代码:

/***
 * SpringMVC处理restful格式请求
 *
 * 1.
 *      在 @RequestMapping注解中可以使用{字母}来声明单元方法的拦截范围
 *      比如:
 *       @RequestMapping("aa/{uname}/{age}")
 *      表示请求地址要请求的单元方法以aa开头,后面为两位任意。
 * 2.
 *      我们可以在单元方法的形参声明上使用注解@PathVariable
 *      来告诉DispatcherServlet将请求地址中的数据截取后作为
 *      实参传递给单元方法使用,默认按照地址占位中的参数名和
 *      形参名一致的规则赋值,如果不一致可以在@PathVariable中
 *      指明赋值@PathVariable("un")
 *
 * 3.
 *  注意:
 *      说白了SpringMVC的restful格式的支持就是单元方法模糊拦截+注解声明来实现。
 *
 */
@RequestMapping("aa/{un}/{age}")
public String demoResful(@PathVariable("un")  String uname,@PathVariable Integer age){
    //处理请求
    System.out.println("MyController.demoResful:SpringMVC处理restful格式请求:"+uname+":"+age);
    //响应结果
    return "aa";
}

SpringMVC的编码过滤器配置

[1] 浏览器服务器交互的编码格式介绍

问题:

在浏览器和服务器的数据交互流程中,处理英文数据以外还有中文数据。
浏览器发起的请求数据中包含中文,服务器响应的数据包含中文。而浏览器
默认的编码格式为iso-8859-1,也就说浏览器默认发送给服务器的请求数据的
编码格式为iso-8859-1。同理,服务器响应给浏览器的数据,浏览器在接受到后
会按照iso-8859-1的编码格式来进行解码获取数据。但是tomcat服务器使用的
编码格式为utf-8,这样造成,浏览器发起的请求数据在服务器端使用utf-8的格式
接收出现乱码,服务器响应的utf-8类型的数据被浏览器解析时出现乱码。一旦出 现乱码问题,一定是编码格式和解码格式不一致造成的,怎么办?

在这里插入图片描述
解决:

1.请求编码格式:
因为浏览器是客户端的,在发起请求的时候,普通用户是不懂技术的,所以
请求数据编码格式的设置只能在服务器端来解决。既然浏览器发起的请求默认
使用的是iso-8859-1的方式进行的编码,而服务器端使用的是utf-8来进行 解码,而我们在Servlet中获取的封装了此次请求的请求数据的request对象
已经是tomcat服务器将请求数据按照utf-8的格式解析后封装的request对 象。所以我们req.getParameter获取的中文数据已经是乱码了,所以将请求 数据进行反编码,变为没有被tomcat服务器解析的状态,然后使用正确的编 码格式进行解码来获取数据。
String uname = request.getParameter(“uname”);
String uname2=new String(uname.getBytes(“iso-8859-1”),“utf-8”);

2.响应编码格式:
响应数据是服务器响应给浏览器,让浏览器显示给用户的。但是用户不懂技术的,用户自己是不会主动设置浏览器的编码格式来正确的显示响应数据的。需要我们在服务器端响应数据的数据,告诉浏览器使用指定的编码格式来解析响应数据
并显示给用户。
resp.setCharacterEncoding(“utf-8”)
resp.setContentType(“text/html;charset=utf-8”)
请求编码格式的公共解决:
虽然我们使用反编码可以将乱码的中文请求数据变为正常数据,但是需要每个数据
独立进行反编码,一旦请求数据过多,造成代码编写麻烦。能不能在某个地方我们
配置一次,以后就无需配置了呢?
get请求:
i.request.setCharacterEncoding(”utf-8“);
ii.在tomcat的server.xml文件中进行编码格式的配置
在这里插入图片描述
post请求:
i. request.setCharacterEncoding(”utf-8“);

使用过滤器配置项目的编码格式:
因为每次都在Servlet中设置编码格式,过于麻烦,所以在过滤器中进行统一的编 码格式设置。

[2]SpringMVC的编码过滤器配置

在项目的web.xml文件中配置SpringMVC官方提供的编码过滤器即可。

<!--配置编码过滤器-->
<filter>
    <filter-name>code</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>
    <!--设置编码格式的生效范围-->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>code</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

SpringMVC的静态资源放行

[1] SpringMVC的Dispatcher的拦截范围问题

问题:

按照SpringMVC的使用流程,需要在web.xml文件中配置DispatcherServlet
的拦截范围,而我们配置的拦截范围为”/”,表示拦截除jsp请求以外的所有请求。
这样造成,请求是js,css,图片等静态资源的请求,也会被DispatcherServlet拦截
,调用对应的单元方法来处理请求。但是,我们呢是一个静态资源的请求,不应该
按照普通单元方法请求的流程来处理,而是将对应的静态资源响应给浏览器使用。
怎么办?

前台代码示例:
在这里插入图片描述

后台代码示例:
在这里插入图片描述

运行结果:
在这里插入图片描述
在这里插入图片描述

解决:

① 将DispatcherServlet的底层逻辑进行变更,静态资源的请求就不要作为
单元方法请求处理,而是查找对应的资源响应给浏览器。
② 在SpringMVC的配置文件中配置静态资源的放行,告诉DispatcherServlet
哪些资源静态资源需要放行,将静态资源响应给了浏览器。

[2] SpringMVC的静态资源放行配置

配置示例:
在这里插入图片描述

注意:

浏览器发起静态资源请求,DispatcherServlet会先按照正常的单元方法逻辑
进行处理,如果找不到对应的单元方法,则根据SpringMVC的配置文件的静
态资源,判定此次请求是否为静态资源请求,如果是则将资源响应给浏览器,如
果不是,则响应404.也就说,不要设置某个单元方法的路径和静态资源的路径
是一致,这样就算配置了静态资源放行,也会导致静态资源无法访问的问题。

SpringMVC的响应

[1] SpringMVC的响应介绍

问题:

在学习了SpringMVC的配置流程以及单元方法请求数据的获取后,我们可以
使用SpringMVC搭建一个项目,并声明单元方法来处理请求,在单元方法中
使用SpringMVC提供的方式来获取请求信息,然后根据功能需求,声明请求
处理的逻辑代码,进行请求的处理。当请求处理完成后,我们需要将此次请求的
处理结果响应给浏览器,以前我们是自己在Servlet中使用response对象来完成
响应的,那么在SpringMVC中如何响应请求的处理结果呢?

解决:

因为在使用了SpringMVC后,Servlet只有一个了,也就是DispatcherServlet
而DispatcherServlet又不是我们自己声明的,所以我们在外部声明单元方法,让
DispatcherServlet来根据请求调用,处理请求。也就说我们需要将请求处理的
相关代码声明在对应的单元方法中。既然这样,单元方法是由DispatcherServlet
来调用,需要在单元方法执行完毕后,告诉DispatcherServlet如何响应此次请求
的处理结果。也就说单元方法需要通过返回值来告诉DispatcherServlet,如何进行请求的响应。

实现:

紧耦方式使用原生的response对象完成
使用SpringMVC的方式

[2] 使用Response对象完成响应

/***
 * 使用response对象完成响应
 *  1.单元方法的返回值类型设置void
 *      因为使用response对象在单元方法中直接对此次请求进行了响应,不再通过
 *      DispatcherServlet了,既然已经响应了,就不需要再给DispatcherServlet返回值了。
 *  2. 在单元方法上声明HttpServletResponse形参,来接收
 *      此次请求的response对象。
 *  3.在单元方法中直接使用response对象完成响应
 *      直接响应
 *      请求转发
 *      重定向
 */
@RequestMapping("resp")
public void demoResp(String uname,Integer age,HttpServletRequest req,HttpServletResponse response) throws IOException, ServletException {
        //处理请求
            System.out.println("MyControllerResp.demoResp:使用原生的response对象完成响应:"+uname+":"+age);
        //响应结果
            response.setContentType("text/html;charset=utf-8");
            //直接响应
                //response.getWriter().write("直接响应");
            //请求转发
                //req.getRequestDispatcher("/index.jsp").forward(req,response);
            //重定向
                response.sendRedirect(req.getContextPath()+"/redirect.jsp");
}

[3] 使用forward关键字完成响应

作用:实现请求转发
使用:通过单元方法的返回值来告诉DispatcherServlet请求转发指定的资源。
注意:如果是请求转发,forward关键字可以省略不写的。

代码示例:

1.请求转发jsp资源
在这里插入图片描述
2.请求转发其他的单元方法
在这里插入图片描述

[4] 使用redirect关键字完成响应

作用:完成资源的重定向
使用:通过单元方法的返回值来告诉DispatcherServlet重定向指定的资源。

代码示例:

重定向到jsp资源
在这里插入图片描述
重定向到单元方法
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AloneDrifters

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值