学习资料源:慕课网 - 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
- ** view - 视图层 **
为用户提供UI界面,重点关注数据的呈现
考虑如何给数据布局,将数据优美合理的展示给用户 - ** model - 模型层**
业务数据的信息表示,关注支撑业务的信息构成,通常是多个业务实体的组合
考虑需要给用户展示什么才能构成模型 - ** Controller - 控制层 **
调用业务逻辑产生合适的数据(model),传递数据给视图层用于呈现
考虑调用哪些业务逻辑使得可以给用户呈现正确的数据,让效率更高,性能更好
** MVC是一种架构模式,使得程序分层,分工合作;既相互独立,又协同工作 **
三、Spring MVC基本概念
1、DispatcherServlet
Spring MVC作为前端控制器的一种实现形式,DispatcherServlet就是它的前端控制器。浏览器端(前端)用户的请求就是通过DispatcherServlet进行了分发到达一个合适Controller来生产所需要的业务数据model,model再通过DispatcherServlet进行传递,传递给view来完成最终的业务呈现。
2、Controller
根据业务逻辑生成model。
3、HandlerAdapter
处理器适配器,是在DispatcherServlet内部使用的类,是controller的一种表现形式,最终调用的Controller是以HandlerAdapter的形式出现的(适配器模式)
4、HandlerInterceptor
处理器拦截器,是一个接口
具有三个方法:preHandle、postHandle和afterCompletion,会在调用Controller之前、调用Controller之后和完成页面呈现之后做一些工作。
5、HandlerMapping
前端控制器与Controller的映射关系,作用是告知DispatcherServlet,以哪一个Controller来响应特定的请求。
6 、HandlerExecutionChain
相关方法构成了执行链条:先执行HandlerInterceptor中的ProHandle,再执行相应Controller的方法,然后执行phstHandle、afterCompletion。
这个链条是如何实现的?看源码可以知道是通过反射机制实现的。
7 、ModelAndView
实现model,同样的还有Model、Map,最后都转换为ModelAndView,是Model的具体表现。
8 、ViewResolver
视图解析器,告知DispatcherServlet使用哪一个视图呈现页面(jstl、jsp...)
9 、Spring MVC工作模块划分(动态)
除了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的坐标,应该都能下载得到。
注意IDEA的提示:
** 定义和使用变量 **
<!--定义变量-->
<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访问的时候找不到页面:
控制台的提示:
难道是我配置文件写的有问题?改来改去不晓得问题在哪里,后来在这里找到了一定灵感:SpringMVC配置问题 - 开源中国社区
虽然他说的也不是很清楚,不过可以有一个定义问题的出路:** 我的com.qunar.controller没有被扫面,所以不会mapping **。
为什么没有被扫描呢?因为昨天刚记录了这个:IDEA项目不能新建package和class的解决,猜测可能是因为没有将这个目录** Make Directory As - Source Root **,即将目录设置为资源根目录导致的。
设一下,果然成了。
不得不吐槽下,坑啊,从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
记得在Deployment添加运行项目:
6、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
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页面由各种标签所代表的控件组成,在前(请求端)后(服务端)端进行数据传递时,可以按照“名称匹配”的原则将前端请求参数(表单数据)传到后端的对象模型中。
/**
* 通过绑定(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 **
获取文件流,写入文件