Spring MVC学习笔记

学习资料源:慕课网 - Spring MVC起步

内容概要

  • 一、前端控制器(Front Controller)
  • 二、MVC
  • 三、Spring MVC基本概念
  • 四、Spring MVC项目搭建
  • 五、配置文件
  • 六、Controller基础代码
  • 七、Binding 绑定
  • 八、FileUpload 单文件上传
  • 九、Spring MVC与json

一、前端控制器(Front Controller)

前端控制器

1、前端控制器通过HTTP等协议接收用户请求,并分发给能处理该请求的Controller;
2、Controller通过具体的业务逻辑处理请求,并返回model实体给前端控制器;
3、前端控制器再将生成的model传递给视图模板生成相应的用户视图,再次返回给前端控制器;
4、前端控制器将页面返回给前端控制器。

各部分的作用

  • Front Controller:分发调度
  • Controller:业务数据抽取
  • View Template:页面呈现

** MVC的核心思想**:业务数据的抽取和业务数据的呈现相分离(这是一种简化)

二、MVC

MVC
  • ** view - 视图层 **
    为用户提供UI界面,重点关注数据的呈现
    考虑如何给数据布局,将数据优美合理的展示给用户
  • ** model - 模型层**
    业务数据的信息表示,关注支撑业务的信息构成,通常是多个业务实体的组合
    考虑需要给用户展示什么才能构成模型
  • ** Controller - 控制层 **
    调用业务逻辑产生合适的数据(model),传递数据给视图层用于呈现
    考虑调用哪些业务逻辑使得可以给用户呈现正确的数据,让效率更高,性能更好

** MVC是一种架构模式,使得程序分层,分工合作;既相互独立,又协同工作 **

三、Spring MVC基本概念

1、DispatcherServlet

Spring MVC作为前端控制器的一种实现形式,DispatcherServlet就是它的前端控制器。浏览器端(前端)用户的请求就是通过DispatcherServlet进行了分发到达一个合适Controller来生产所需要的业务数据model,model再通过DispatcherServlet进行传递,传递给view来完成最终的业务呈现。

DispatcherServlet

2、Controller

根据业务逻辑生成model。

3、HandlerAdapter

处理器适配器,是在DispatcherServlet内部使用的类,是controller的一种表现形式,最终调用的Controller是以HandlerAdapter的形式出现的(适配器模式)

HandlerAdapter

4、HandlerInterceptor

处理器拦截器,是一个接口
具有三个方法:preHandle、postHandle和afterCompletion,会在调用Controller之前、调用Controller之后和完成页面呈现之后做一些工作。

5、HandlerMapping

前端控制器与Controller的映射关系,作用是告知DispatcherServlet,以哪一个Controller来响应特定的请求。

6 、HandlerExecutionChain

HandlerExecutionChain

相关方法构成了执行链条:先执行HandlerInterceptor中的ProHandle,再执行相应Controller的方法,然后执行phstHandle、afterCompletion。

这个链条是如何实现的?看源码可以知道是通过反射机制实现的。

7 、ModelAndView

实现model,同样的还有Model、Map,最后都转换为ModelAndView,是Model的具体表现。

8 、ViewResolver

视图解析器,告知DispatcherServlet使用哪一个视图呈现页面(jstl、jsp...)

9 、Spring MVC工作模块划分(动态)

Request流程

除了Controller要根据业务实现,其他部分大都是通过配置实现。

四、Spring MVC项目搭建

Github地址:CharacterCounter2

看视频、查资料,捣鼓了老大半天才把一个简单的测试Demo搭建好,实在是心力憔悴...以下几点体会:

  • Spring MVC极度依赖配置文件,如果配置文件有误,将导致项目出错;
  • 在集成了Maven的IDEA中搭建Spring MVC项目,需要掌握IDEA和Maven的使用技巧,尤其是在Maven的依赖管理方面、IDEA的特性方面要注意,否则掉进坑里爬不出来。
1、新建Maven Spring MVC项目

参考之前的文章:创建Maven项目 - 命令行 | IDEA - 使用本地项目模板

2、配置文件 和 建立目录结构

这里主要配置3个配置文件:
pom.xml web.xml mvc-dispatcher-servlet.xml

(1)配置pom.xml

如前面学习的Maven知识,pom.xml是Maven的配置文件,主要进行项目管理、依赖管理等。

感受最深的一点是,之前用非Maven开发Java项目,jar的引入是没有特别的管理方法的,但是Maven的依赖管理做得很好:所有的jar包都存放在Maven的本地资源库里(我的本地资源库在setting.xml中做了配置,改为了D:\Mobile DVE\maven\LocalWareHouse下),如果本地资源库中没有需要的jar文件,就会从中央存储库中下载(我这里默认从阿里云的托管库中下载了):

本地资源库

如果自动从中央存储库中下载故障,可以用浏览器到中央下载,再放到指定的本地存储库目录下。不过只要配置对了lib的坐标,应该都能下载得到。

配置引入org.springframework

注意IDEA的提示:

IDEA会自动完成jar的加载、引入

** 定义和使用变量 **

 <!--定义变量-->
<properties>
    <commons-lang.version>2.6</commons-lang.version>
    <slf4j.version>1.7.6</slf4j.version>
    <spring.version>4.1.3.RELEASE</spring.version>
</properties> 

这些变量可以被用来统一版本号,在后面的坐标配置中可以使用,比如使用<spring.version>来配置spring的版本:


引用变量:${变量名}
(2)配置web.xml

这个是Servlet的配置项,在这里配置使得spring的DispatcherServlet接手相关工作,指定DispatcherServlet的对应xml配置文件(也就是mvc-dispatcher-servlet.xml),并且指定DispatcherServlet能够拦截哪些请求等。

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--DispatcherServlet对应的上下文配置,默认为/WEB-INF/$servlet-name$-servlet.xml,我们做了改变-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <!--使mvc-dispatcher拦截所有请求-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

** <servlet-name> **:可自定义,不过要注意这个名称与DispatcherServlet的上下文配置文件有关,配置文件的构成规则:

  $servlet-name$-servlet.xml

** contextConfigLocation ** :DispatcherServlet的上下文配置文件路径
** <servlet-mapping> **:指定mvc-dispatcher将拦截的请求,“/”表示拦截所有请求

(3)配置mvc-dispatcher-servlet.xml

如(2)中所述,这个文件的名字是任意且有规则的。

<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  ">

<!--使DispatchServlet开启基于annotation的HandlerMapping-->
<mvc:annotation-driven/>

<!--激活@Required、@Autowired、JSR 250's、@PostConstruct等标注-->
<context:annotation-config/>

<!--DispatcherServlet上下文,把标记了@Controller注解的类转换为bean,而不搜索其他标注的类 -->
<context:component-scan base-package="com.qunar.controller">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀:/WEB-INF/jsps/文件夹下的jsp文件 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsps/"/>
    <property name="suffix" value=".jsp"/>
</bean>

</beans>

注释中记录得很清楚了,唯一要注意的就是按照配置文件中的描述建立相应的层级目录

层级目录

不过这里有一个巨坑!!!

被坑了2个多小时。

注意 <context:component-scan base-package="com.qunar.controller"> 这个地方,指定的是Controller的包路径,不过之前在执行的时候扫面不到我写的Controller,所以不会去做mapping,URL访问的时候找不到页面:


404

控制台的提示:

TomcatServer控制台警告

难道是我配置文件写的有问题?改来改去不晓得问题在哪里,后来在这里找到了一定灵感:SpringMVC配置问题 - 开源中国社区

虽然他说的也不是很清楚,不过可以有一个定义问题的出路:** 我的com.qunar.controller没有被扫面,所以不会mapping **。

为什么没有被扫描呢?因为昨天刚记录了这个:IDEA项目不能新建package和class的解决,猜测可能是因为没有将这个目录** Make Directory As - Source Root **,即将目录设置为资源根目录导致的。

设一下,果然成了。

Mapped com.qunar.controller.TestController.testMVC()

不得不吐槽下,坑啊,从Eclipse迁移过来没多久,怎么会get到这个梗!

3、编写测试Controller
package com.qunar.controller;

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

@Controller                     // 将这个类注册为Controller
@RequestMapping("/test")        // 拦截相应路径:host/test
public class TestController {
    @RequestMapping("/hello")   // 拦截相应路径:host/test/hello
    public String testMVC(){
        System.out.println("拦截相应路径:host/test/hello");
        return "index";         // 返回ViewTemplate
    }
}
4、编写测试jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello String MVC</title>
</head>
<body>
    <h1>Successful!</h1>
</body>
</html>
5、配置Tomcat并运行

在IDEA中配置Tomcat


在IDEA中配置Tomcat

记得在Deployment添加运行项目:

添加部署
6、Run
run

五、配置文件


web.xml


1、使web.xml支持EL语言

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:web="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd "
        version="2.4">

2、层次化的ApplicationContext

Spring MVC的上下文层级

WebApplicationContext
Spring中的ApplicationContexts可以被限制在不同的作用域。在web框架中,每个DispatcherServlet有它自己的WebApplicationContext,它包含了DispatcherServlet配置所需要的bean。DispatcherServlet 使用的缺省BeanFactory是XmlBeanFactory,并且DispatcherServlet在初始化时会在你的web应用的WEB-INF目录下寻找[servlet-name]-servlet.xml文件。DispatcherServlet使用的缺省值可以使用servlet初始化参数修改。

** ContextLoaderListener **
ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。ContextLoaderListener启动的上下文为根上下文,DispatcherServlet所创建的上下文的的父上下文即为此根上下文,可在FrameworkServlet中的initWebApplicationContext中看出。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

** 多个DispatcherServlet使用同一个公共的上下文 **
为什么会出现多个DispatcherServlet?同一个网站会提供不同类型的服务,为了更加方便,使用不同的DispatcherServlet做不同的分发,为不同类型的请求提供不同的服务,这样更加方便快捷。

3、<servlet-mapping>

指定DispatcherServlet拦截的请求。


mvc-dispatcher-servlet.xml


这个配置文件供名为mvc-dispatcher的DispatcherServlet使用,提供其相关的Spring MVC配置。

1 、配置annotation

<context:annotation-config/>
激活@Required、@Autowired、JSR 250's、@PostConstruct等标注服务

2 、配置DispatcherServlet上下文

<context:component-scan base-package="com.qunar.controller">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
使得DispatcherServlet只管理@Controller类型的bean,忽略其他类型的bean,如@Service

3 、配置HandlerMapping

指导以何种方式mapping。Spring MVC默认启动DefaultAnnotationHandlerMapping。

4 、扩充注解驱动

<mvc:annotation-driven/>
扩充注解驱动,可以将请求参数绑定到Controller参数。
也就是说,URL中查询参数的变量可以直接映射到某个Controller参数中,这是一个很强大的功能。
可以非常方便的获取、传递参数。

5 、静态资源处理

<mvc:resources mapping="/resources/" location="/resources/"/>
指导静态资源(css、js、image)的位置

6 、配置ViewResolver

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsps/"/>
    <property name="suffix" value=".jsp"/>
</bean>
可以有多个ViewResolver,使用order属性排序,

applicationContext.xml


applicationContext.xml详解 - - 博客频道 - CSDN.NET

六、Controller基础代码

@RequestMapping(value="/hello",method = RequestMethod.GET)
public String testController(@RequestParam("id") Integer id,Model model){
    System.out.println("拦截相应路径:host/test/hello");
    // 逻辑
    TestModel testModel = new TestService().getTestModelById(id);
    // 传递model
    model.addAttribute(testModel);
    System.out.println("拦截相应路径:host/test/hello");
    // 返回ViewTemplate
    return "index";        
}

** @RequestMapping注解**
映射请求路径与Controller Class或Controller Method之间的关系

** @RequestParam**
将请求参数绑定到Controller参数

** Model**
Controller和View(Jsp File)之间传递的模型实体:

// Controller
model.addAttribute(Object obj);

// Jsp
<h3>${testModel.name}</h3>

使用HttpServletRequest对象的方法:传入HttpServletRequest对象:

@RequestMapping("hello2")
public String testController(HttpServletRequest request) {
    Integer id = Integer.valueOf(request.getParameter("id"));
    TestModel m = new TestModel("使用HttpServletRequest对象",id);
    request.setAttribute("testModel",m);
    return "index";
}

七、Binding 绑定

** Binding **:将请求中的参数字段按照名字原则(名称匹配)填入对象模型。

web页面由各种标签所代表的控件组成,在前(请求端)后(服务端)端进行数据传递时,可以按照“名称匹配”的原则将前端请求参数(表单数据)传到后端的对象模型中。

Model与表单关系示意
/**
 * 通过绑定(binding)获取到表单数据并直接得到对应的model
 * 注意是按名称对应
 * @param tm
 * @return
 */
@RequestMapping(value = "/save",method = RequestMethod.POST)
public String doSave(@ModelAttribute TestModel tm) {
    System.out.println(tm.toString());
    return "redirect:hello?id=" + tm.getId();
}

** @ModelAttribute 绑定 **
参数中是一个模型实体,它有两个属性域:id和name;页面中的form表单也有同样名称的两个控件,这样就通过名称匹配的方式将前端的请求参数同后端的模型绑定了,很方便。

** 重定向 **
在代码中可以看到Spring中的重定向方式:

return "redirect:hello?id=" + tm.getId();

** 请求转发 **

return "forward:hello";

八、FileUpload 单文件上传

Spring MVC提供了文件上传的内置支持,将其作为一个公共的服务,我们只需要通过简单的配置就可以使用接口完成文件上传工作。

1、配置文件

配置mvc-dispatcher-servlet.xml,增加bean:CommonsMultipartResolver

<!--配置文件上传-->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver ">
    <!--限制文件上传最大限制:200*1024*1024=200M-->
    <property name="maxUploadSize" value="209715200" />
    <property name="defaultEncoding" value="UTF-8" />
    <!-- 启用resolveLazily属性是为了推迟文件解析,以便捕获文件大小异常 -->
    <property name="resolveLazily" value="true" />
</bean>

2、引入包

<dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
</dependency>

3、前端页面

** (1)form**

<form method="post" action="/courses/doUpload" enctype="multipart/form-data">
action:相应方法
enctype:属性为multipart/form-data,表示上传文件

**(2)input **

<input type="file" name="file"/>

4、编写处理方法

@RequestMapping(value="/doUpload", method=RequestMethod.POST)
public String doUploadFile(@RequestParam("file") MultipartFile file)  throws IOException {
    if(!file.isEmpty()){
        FileUtils.copyInputStreamToFile(file.getInputStream(), new File("D:\\", System.currentTimeMillis()+ file.getOriginalFilename()));
    }
    return "success";
}

** MultipartFile对象 **

// source
public interface MultipartFile {
    java.lang.String getName();获取表单中文件组件的名字
    java.lang.String getOriginalFilename();获取上传文件的原名
    java.lang.String getContentType();获取文件MIME类型
    boolean isEmpty();文件是否为空
    long getSize();获取文件的字节大小,单位byte
    byte[] getBytes() throws java.io.IOException;获取文件bytes
    java.io.InputStream getInputStream() throws java.io.IOException;获取文件流
    void transferTo(java.io.File file) throws java.io.IOException, java.lang.IllegalStateException;保存到一个目标文件中
}

** @RequestParam("file") MultipartFile file **
绑定参数,<input>的name和Controller的参数名保持一致

** FileUtils.copyInputStreamToFile **
获取文件流,写入文件

九、Spring MVC与json

4-7 JSON(上)(03:41)
4-8 JSON(中)(05:23)
4-9 JSON(下)(04:46)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容