Spring MVC复习

Spring MVC复习

spring MVC===spring 的 web模块。用来实现前后端交互

MVC概述

img

  • View:就是一些JSP,jsp就是一个页面,负责展示数据
  • Controller:控制器处理请求
  • Model:模型 数据模型就是bean 实体; 业务模型就是业务逻辑

分层就是为了解耦,解耦是为了维护方便,分工方便

Spring MVC强大的原因:有一套非常强大的注解,让POJO(普通的java对象,plain old java object)
处理请求

Spring MVC 用了松散耦合组件结构

SpringMVC眼中的MVC

img_1
原来是MVC 是直接由控制器收到,现在是由前端控制器(Front Controller)接受。
前端控制器看谁能处理请求,然后派发请求。
前端控制器有点像到医院的智能导诊台

导包

spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
commons-logging-1.1.3.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar

配置spring mvc过程中的一些坑

  1. 导包时候提示class not found
    需要把包导入到/web/WEB-INF/lib/目录下,而不是原始的/lib/目录下(非web项目时就放在这),并添加到project structure

  2. 装配过程中 IDEA没办法自己写配置,需要自己把下面的配置弄到web.xml里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!--这段代码 IDEA编译器不能自动生成,必须手敲到web.xml中-->
    <servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <!--指定spring MVC配置文件位置-->
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--启动加载,在服务器启动的时候创建 值越小优先级越高-->
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <!--
    /*和/都是拦截所有请求
    /*会拦截.jsp的请求
    -->
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    这里不能写<param-value>springmvc.xml</param-value>应该写<param-value>classpath:springmvc.xml</param-value>

  3. spring mvc.xml的配置

    1
    <context:component-scan base-package="com.runsstudio.springmvc_review.*"/>

    基本上就是一个包扫描

  4. tomcat的配置-点项目 然后添加
    img_3

img_4
另外,index.jsp要放在web文件夹的根目录下,而不是放在/WEB-INF/html或者jsp文件夹下。

  1. helloController
    其实就是一个普通的类 不需要什么操作 只要告诉spring这个是一个controller 他就是一个controller

** Dispatch Servlet **这个就是前端控制器的代码,设置来拦截所有请求

  1. 如果不指定启动文件位置 怎么办?
    1
    2
    3
    <!--指定spring MVC配置文件位置-->
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    如果不指定,默认会找/WEB-INF/xxx-servlet.xml这里xxx是servlet-name,对于本项目也就是springDispatcherServlet-servlet.xml

    前缀后缀解析器的配置

    在controller里面 如果不配置视图解析器,则
    1
    2
    3
    4
    5
    6
    7
    8
    @RequestMapping("/hello")/*处理当前项目下的/hello请求,这里写hello也可以 但还是习惯加上*/
    public String helloServlet(){/*这里的函数名不需要特别指定 但是返回类型是string*/
    System.out.println("请求收到了,正在处理中");
    /*原先写法*/
    //return "/WEB-INF/jsp/success.jsp";
    /*添加前缀后缀解析器之后写法*/
    return "success";
    }
    原先需要写/WEB-INF/jsp/success.jsp 这样有些麻烦,我们可以在springmvc.xml里面配置前后缀页面解析器
    1
    2
    3
    4
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    </bean>
    这样就直接写return "success";就可以了

HTTP常见请求

GET POST HEAD PUT DELETE OPTIONS PATCH
如果规定了请求方式 然后又请求错误的方式 会返回405

ant风格字符替代

用在requestMapping上

  • ?能替代一个字符
  • *能替代多个字符和一层路径
  • **能替代多个路径

一些简单的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* http://localhost:8080/handle/handle03?username=11 可以
* http://localhost:8080/handle/handle03 不行
* http://localhost:8080/handle/handle03?username=11&gg=1 不行
* */
@RequestMapping(value = "/handle03" ,params = {"username","!gg"})
public String handle03(){
System.out.println("带参数请求 调用了");
return "success";
}
@RequestMapping(value = "/handle1?" )
public String handle11(){
System.out.println("问号匹配单个字符 调用了");
return "success";
}
/* {}占位符
* http://localhost:8080/handle/user/1 可以访问,且id为1*/
@RequestMapping(value = "/user/{id}")
public String handleUserPathVaribleTest(@PathVariable("id") String id){
System.out.println("用户调用了~,id="+id);
return "success";
}

REST风格

使用GET/PUT/POST请求区分查询 GET 是查询 PUT 是更新 POST是新增 delete是删除
img_5

问题:从页面上只能发起GET和POST请求,不能发起另外两种请求
需要拐弯抹角的发

img_6
img_7

spring mvc post请求的乱码问题及解决方案

post请求到POJO对象时,如果传递中文参数,会出现乱码问题,例如:
Book{bookName='è??é??', bookPrice=123.0, bookAuthor='???é??'}
这个时候可以添加一个过滤器 来解决乱码问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--spring MVC 从jsp页面POST传递到Controller会出现中文乱码,此时配置这个来解决中文乱码-->
<!--这个filter必须配置在所有的前面-->
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

把这个filter配置在web.xml里面,并且放在其他过滤器前面 就可以解决中文乱码问题
a book:Book{bookName='null', bookPrice=22.0, bookAuthor='彩龙'}
顺便一说,spring mvc 自动装配POJO 是根据name

1
2
3
4
5
6
7
8
<form action="book" method="post">
<%--根据name来对应对象 如果name对应不上,则填充null--%>
<%--a book:Book{bookName='è??é??', bookPrice=123.0, bookAuthor='???é??'}--%>
<input id="bookNam" type="text" name="bookNam">
<input id="bookAuthor" type="text" name="bookAuthor">
<input id="bookPrice" type="number" name="bookPrice">
<input type="submit">
</form>

比如这个bookNam和book类里面的bookName属性对应不上的话 会装配null

原生api

1
2
3
4
5
6
7
8
9
10
11
/*往session、request里面传参数,对应jsp页面里,用
* 欢迎您,${requestScope.get("username")}!
当前时间:${sessionScope.get("time")}
* 获取传入的参数
* 需要导入servlet-api.jar包!*/
@RequestMapping("/httpRequest")
public String handleHttpServletRequest(HttpSession session , HttpServletRequest request){
session.setAttribute("time", System.currentTimeMillis());
request.setAttribute("username","菜龙");
return "successRequest";
}

需要导入servlet-api.jar包!

MVC里面 model、ModelMap、Map的关系:

使用MVC给页面传入数据的时候可以使用Map、Model、ModelMap,先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 使用map给jsp页面传参
* @param map
* @return
*/
@RequestMapping("/outputToPageByMap")
public String outputToPageByMap(Map<String,Object> map){
map.put("time",System.currentTimeMillis());
map.put("username","菜龙");
map.put("book",new Book("演员的自我修养",2333d,"周星驰"));
System.out.println("get map");
System.out.println(map.getClass());//class org.springframework.validation.support.BindingAwareModelMap
return "outputPage";
}

/**
* 使用model给jsp页面传参
* @param model
* @return
*/
@RequestMapping("/outputToPageByModel")
public String outputToPageByMap(Model model){
model.addAttribute("time",System.currentTimeMillis());
model.addAttribute("username","菜龙");
model.addAttribute("book",new Book("演员的自我修养",2333d,"周星驰"));
System.out.println("get model");
System.out.println(model.getClass());//class org.springframework.validation.support.BindingAwareModelMap
return "outputPage";
}
/*还可以使用ModelMap传参*/

输出Map、Model的类型,控制台都会打印class org.springframework.validation.support.BindingAwareModelMap,实际上这三个最后都是变成这个类
img_8
这也是为什么MVC给jsp页面的请求,都被放在request域里面的原因

@ModelAttribute注解 使用场景

img_9
使用方法
 在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了 @ModelAttribute 的方法。
 在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数绑定到对象中,再传入入参
 将方法入参对象添加到模型中
,具体解决参考: ModelAttributeTestController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 访问 http://localhost:8080//outputPageModelAttribute?id=1
* 输出
* id=1
* book created
* Book{bookName='演员的自我修养', bookPrice=30.0, bookAuthor='周星驰'}
* @param id 特别注意:是用@RequestParam 而不是用@PathVarible
*
* @return
*/
//1. 由 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 SpringMVC 调用!
@ModelAttribute("book")
public Book getBook(@RequestParam(value = "id",required = false) Integer id){
System.out.println("id="+id);
///↓这里应该是sql select * from book where id=?;
Book book=new Book("演员的自我修养",30d,"周星驰");
///
System.out.println("book created");
System.out.println(book);
return book;
}

@RequestMapping("/outputPageModelAttribute")
public ModelAndView outputPageModelAttribute(@ModelAttribute("book") Book book){
ModelAndView modelAndView=new ModelAndView("outputPage");
modelAndView.addObject("book",book);
modelAndView.addObject("username","菜龙");
modelAndView.addObject("time",System.currentTimeMillis());
return modelAndView;
}
  1. SpringMVC 确定目标方法 POJO 类型入参的过程
    ① 确定一个 key:
    1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
    2). 若使用了@ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值.
    ② 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
    1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 ① 确定的 key 一致, 则会获取到.
    ③ 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰,
    ④ 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常.
    ⑤ 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数
    ⑥ SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中.

简单来说就是先找隐含模型 再找实际的模型

页面转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 返回主页面 用相对路径。url变成http://localhost:8080/handle/return
* @return
*/
@RequestMapping("/return")
public String returnToMainPage(){
return "../../index";
}

/**
* 返回主页面 用forward。url变成http://localhost:8080/handle/returnByForward
* @return
*/
@RequestMapping("/returnByForward")
public String returnToMainPageByForward(){
return "forward:/index.jsp";
}

/**
* 返回主页面 用重定向。url变成http://localhost:8080/index.jsp
* @return
*/
@RequestMapping("/returnByRedirect")
public String returnToMainPageByRedirect(){
return "redirect:/index.jsp";
}

有前缀(forward、redirect)的和视图解析器就没啥关系了,必须加上.jsp,视图解析器不会进行拼串

异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* http://localhost:8080/testException
* @return
*/
@RequestMapping("/testException")
public String testExceptionPage(){
int i=1/0;
return "success";
}

/**
* 异常处理器,输出:出现异常了,异常信息是: java.lang.ArithmeticException: / by zero
* @param e
* @return
*/
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView exceptionHandler(Exception e){
System.out.println("出现异常了,异常信息是: "+e);
ModelAndView modelAndView=new ModelAndView("errorPage");
modelAndView.addObject("exception",e);
return modelAndView;
}

参考 src/com/runsstudio/springmvc_review/controller/ExceptionTestController.java
需要注意的1点:

1
2
3
4
5
public String exceptionHandler(Exception e, ModelMap modelMap){
System.out.println("出现异常了,异常信息是: "+e);
modelMap.addAttribute("exception",e);
return "errorPage";
}

这个写法是不行的,会报错

1
2
3
4
5
6
7
8
9
10
java.lang.IllegalStateException: Unsupported argument [org.springframework.ui.ModelMap] for @ExceptionHandler method: public java.lang.String com.runsstudio.springmvc_review.controller.ExceptionTestController.exceptionHandler(java.lang.Exception,org.springframework.ui.ModelMap)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver.resolveHandlerArguments(AnnotationMethodHandlerExceptionResolver.java:273)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver.doResolveException(AnnotationMethodHandlerExceptionResolver.java:144)
at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:135)
at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1222)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1034)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:984)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)

如果有多个ExceptionHandler可以处理异常,那么精确的优先
如果想写一个全局处理异常的类,用@ControllerAdvice注解标注在类上

Spring工作流程描述

1) 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;

2) DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI):

判断请求URI对应的映射
①    不存在:
a)    再判断是否配置了mvc:default-servlet-handler:
b)    如果没配置,则控制台报映射查找不到,客户端展示404错误
c)    如果有配置,则执行目标资源(一般为静态资源,如:JSP,HTML)
②    存在:
a)    执行下面流程

3) 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

4) DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。

5) 如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】
6) 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

①    HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
②    数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
③    数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
④    数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

7) Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
8) 此时将开始执行拦截器的postHandle(…)方法【逆向】

9) 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图

  1. 在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】

  2. 将渲染结果返回给客户端

img_10

spring 和spring mvc整合

  • spring和spring mvc 整合的目的:分工明确
  • 有和控制器相关的放在spring mvc 的配置里面
  • 有和业务逻辑相关的放在spring配置里面

怎么整合?只让spring mvc扫描 Controller
只让spring 扫描非Controller就可以了
具体来说,就是三步走:

第一步:在web.xml中配置监听器,配置在最前面

1
2
3
4
5
6
7
8
<!-- 配置启动 Spring IOC 容器的 Listener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

第二步:
设置Spring 的容器扫描,让他扫描非controller

1
2
3
4
5
6
7
<context:component-scan base-package="com.atguigu.springmvc">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 配置数据源, 整合其他框架, 事务等. -->

(这里参考conf/springCombinedWithSpringMVC.xml)

第三步,配置spring mvc容器扫描,让他只扫描controller

1
2
3
4
5
6
7

<context:component-scan base-package="com.atguigu.springmvc" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>

然后照常使用@Autowired注入参考src/com/runsstudio/springmvc_review/controller/BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class BookController {

@Autowired //← 这里的是由spring容器进行注入的! 第一次使用IDEA运行的时候可能提示没有装配成功
private BookService bookService;
//但是只要从配置的spring的xml那里可以扫到bookService这个bean的话就没有问题


@RequestMapping("/book")
public String bookSaveTest(Book book){
System.out.println("a book:"+book);
return "success";
}
}

** SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean. **

反过来呢 ? 反之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean

  • 在 Spring MVC 配置文件中引用业务层的 Bean
  • 多个 Spring IOC 容器之间可以设置为父子关系,以实现良好的解耦。
  • Spring MVC WEB 层容器可作为 “业务层” Spring 容器的子容器:
    即 WEB 层容器(含controller)可以引用业务层(service、dao)容器的 Bean,而业务层容器却访问不到 WEB 层容器的 Bean
-------------文章已结束~感谢您的阅读-------------
穷且益坚,不堕青云之志。