0 前言
JavaEE体系结构包括四层,从上到下分别是应用层、Web层、业务层、持久层。Struts和SpringMVC是Web层的框架,Spring是业务层的框架,Hibernate和MyBatis是持久层的框架。
1.Spring MVC概述:
Spring MVC是Spring提供的一个强大而灵活的web框架。借助于注解,Spring MVC提供了几乎是POJO的开发模式,使得控制器的开发和测试更加简单。这些控制器一般不直接处理请求,而是将其委托给Spring上下文中的其他bean,通过Spring的依赖注入功能,这些bean被注入到控制器中。
Spring MVC主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。他的两个核心是两个核心:
处理器映射:选择使用哪个控制器来处理请求
视图解析器:选择结果应该如何渲染
通过以上两点,SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet(前端控制器),它是一个Servlet,顶层是实现的Servlet接口。Spring MVC保证了如何选择控制处理请求和如何选择视图展现输出之间的松耦合。
2 为什么要使用SpringMVC?
很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖于同一个业务对象时是没有灵活性的。
SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。
3 SpringMVC的架构
SpringMVC是Spring推出的MVC设计框架,所以他的架构还是效仿Spring架构《Spring框架详解》
3 SpringMVC的工作流程
SpringMVC接口解释
- 核心接口——DispatcherServlet: 是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项: (1)截获符合特定格式的URL请求。
(2)初始化DispatcherServlet上下文对应WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。
(3)初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。- HandlerMapping接口: 能够完成客户请求到Controller映射。
- Controller接口: 需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。 Controller将处理用户请求,这和Struts
Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。
从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。- ViewResolver接口: Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。
具体流程:
(1)首先浏览器发送请求——>DispatcherServlet前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
(2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;
(3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
(4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
(5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;
(6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;
(7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
4 SpringMVC程序示例
4.1 xml配置版
1、创建动态Web项目
2、导入spring的jar包,导入common-logging的jar包
3、为web项目引入SpringMVC框架,即在web.xml中配置SpringMVC的DispatcherServlet
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- SpringMVC的配置文件所在的位置和名称 -->
<init-param>
<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>
<!-- SpringMVC拦截所有请求,再进行转发 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
4、在src目录下创建SpringMVC的配置文件springmvc.xml,并加入如下代码
<!--配置方式必须写的两个,每个请求对应一个Controller,不用此种方式-->
<!-- 标明所有实现了org.springframework.web.servlet.mvc.Controller接口的Bean可以作为Spring Web MVC中的控制器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 表示将请求的URL和Bean名字映射 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 一个具体的映射,表示将hello.action地址映射到了HelloController的方法上 -->
<bean name="/hello.action" class="com.controller.HelloController"/>
5、编写接受请求的控制器
//实现Controller接口,配置方式需要实现Controller类,相当于servlet继承HttpServlet
public class HelloController implements Controller{
//重写Controller接口的handleRequest方法
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response) throws Exception {
//新建一个视图对象
ModelAndView mav = new ModelAndView();
//给视图对象里添加键值对形式的存储结构,相当于返回的键值对
mav.addObject("message", "hello springmvc");
//指定视图的jsp文件
mav.setViewName("hello.jsp");
return mav;
}
}
6、编写hello.jsp文件
<body>
获取值${message}
</body>
4.2 注解版
1、同上
2、同上
3、同上
4、在src目录下创建SpringMVC的配置文件springmvc.xml,并加入如下代码
<!-- 配置扫描包,告诉SpringMVC被注解的class都在哪个包下 -->
<context:component-scan base-package="com.controller"></context:component-scan>
<!-- 配置视图解析器,告诉SpringMVC在网站的哪个目录下能找到jsp文件 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
5、编写接受请求的控制器
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloWorld {
/**
* 1. 使用RequestMapping注解来映射请求的URL,写在方法上面,一个请求对应一个方法
* 2. 返回值会通过视图解析器解析为实际的物理视图, 会做如下解析
* 通过prefix+returnVal+suffix 这样的方式得到实际的物理视图,然后会转发操作
* "/WEB-INF/views/success.jsp"
*/
@RequestMapping("/helloworld")
public String hello(){
System.out.println("hello world");
return "success";
}
}
6、同上
4.3 SpringMVC映射规则
二级映射
在类上和方法上同时注解@RequestMapping,相当于地址栏里有两级的地址
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("one")
public class TestController {
@RequestMapping("two")
public String test(){
return "index";
}
}
//如上注解后,映射地址为:http://localhost:8080/xx/one/two
Method类型选择接受
@RequestMapping注解括号里,有method属性作为对提价类型的选择接受
@RequestMapping(value="/getName",method={RequestMethod.GET, RequestMethod.POST})
//如果没有指定method,则默认为所有请求类型都接受
参数规则匹配
在响应请求的时候,对提交的参数规则进行验证,如果不符合设置的规则,则不接受请求
- param1=value1 - 请求中必须包含该参数和指定值
- param2 - 请求中必须包含该参数,值任意 !
- param3 -请求中必须不包含该参数\
@RequestMapping(value="/getName",method=RequestMethod.GET,params={"id=123","name","!age")
//上述规则定义了,只能响应get请求,并且请求的参数必须包含id=123,必须包含name,不能包含age
//根据上述规则此地址合法:http://localhost:8080/xx?id=123&name=abc
参数绑定的含义
- 所谓的参数绑定,就是怎么样获取到前台页面传过来的值,通常是跟据参数名(key)来获取值;
- 绑定页面传值的key值到映射方法的参数上,如下程序所示
//页面端提交请求的程序
$.post("../hc.v",
{
name : "shoji",
price : "8888"
},
function(d) {
alert(d);
}
)
//后台响应上面ajax的post请求的代码
//通过两段代码里“name”和“price”的相同,把“shouji”和“8888”传到hc方法里
//问号传值的方式同样适用 ?name=shoji&price=8888
...
@RequestMapping("hc")
public String hc(String name,String price){
return "test";
}
...
- 用标准PO属性来进行绑定
- 页面端提交请求的js代码同上
//新建一个标准的PO,同时当前类的属性名应该跟前台代码里的KEY对应上
public class PO{
private String name;//和key值一样
private Stirng price;//和key值一样
//省略各自的set get
}
//后台响应上面ajax的post请求的代码
//通过PO里的“name”和“price”属性名和前台js代码里的key相同,把“shouji”和“8888”传到hc方法里
//问号传值的方式同样适用 ?name=shoji&price=8888
...
@RequestMapping("hc")
public String hc(PO po){
//po.getName() 取出shoujie
//po.getPrice() 取出8888
return "test";
}
...
3.用注解@RequestParam来绑定
- 页面端提交请求的js代码同上
//后台响应上面ajax的post请求的代码
//通过注解里的“name”和“price”参数名和前台js代码里的key相同,把“shouji”和“8888”传到hc方法里
//问号传值的方式同样适用 ?name=shoji&price=8888
...
@RequestMapping("hc")
public String hc(@RequestParam("name") String p1,@RequestParam("price") String p2){
//p1 取出shoujie
//p2 取出8888
return "test";
}
...
4.通过注解@PathVariable
- 本注解把要传给后台程序的值,绑定在地址里
- 页面端提交请求的地址变为:dianhua/name/shoji/price/8888
/
/后台响应上面ajax的post请求的代码
//@RequestMapping注解的映射改变,地址里值的部分用{}括起来
//在方法的参数上使用注解@PathVariable
//通过PathVariable注解后面的“idvalue”和“pricevalue”和RequestMapping注解里{}部分相同,把“shouji”和“8888”传到hc方法里
@RequestMapping("dianhua/name/{idvalue}/price/{pricevalue}")
public String hc(@PathVariable String idvalue,@PathVariable String pricevalue){
//idvalue 取出shoujie
//pricevalue 取出8888
return "test";
}
...
映射方法的返回值类型
- 返回ModelAndView
<!-- springmvc配置文件代码片段 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
@RequestMapping("/szw/jintiancailai")
public ModelAndView ccc(String namee) {
List list = new ArrayList();
list.add("zhiweizhi");
list.add("luchang");
System.out.println("hello name=" + namee);
ModelAndView mav = new ModelAndView();
mav.addObject("myList", list);
mav.addObject("mySize", "2");
mav.setViewName("first");
//first是一个页面文件的名字
//在springmvc的配置文件指定页面文件所在的路径是/WEB-INF/views/
//在springmvc的配置文件指定页面文件的类型是.jsp
//我们ModelAndView所展示的页面会做如下解析 prefix+setViewName+suffix
//即 /WEB-INF/views/first.jsp
return mav;
}
ModelAndView是springmvc框架提供一个包装界面jsp文件的类,常用的方法addObject和setViewName
addObject有两个参数,类型都是Object类型,第一个参数是key,第二个参数是值;主要用途,把java后的里的数据带到前台jsp页面上
setViewName有一个参数,类型是String;参数是jsp文件的名字;用途是告诉ModelAndView所展示的页面是哪个jsp文件
- 返回值是String
- 返回值里只有一个字符串
public String ddd() {
return "first";
}
//这样返回同上面程序注释的过程
- redirect和forward在返回值字符串前,并且redirect和forward和字串之间有一个冒号
public String eee() {
return "redirect:../first.jsp";
}
public String fff() {
return "forward:../first.jsp";
}
//这样返回,我们转去的地址不在被springmvc管理,需要转去的地址能直接在地址栏访问
- @ResponseBody注解
- 当返回值是String,并且方法上方有此注解时,返回的字符串将不在被解析为springmvc的视图(即jsp文件),会被直接以字符串展示在浏览器里
@RequestMapping("/test")
@ResponseBody
public String hhh() {
return "first";
}
//first将被输出到浏览器里
- 返回时void
- 需要人为在方法的参数列表里,加入request和response这两个参数,所有的操作依靠这两个参数来完成,非常类似标准的Servlet
public void ggg(HttpServletRequest req,HttpServletResponse rep) {
//标准servlet里怎么写,这段代码就怎么写
}
文件上传
Tomcat虚拟目录的配置
在tomcat的server.xml中,找到节点,在开始和结束标签中写如下代码
<!--
docBase - 网站跟目录的绝对路径
path - 在浏览器访问这个网站的网址
-->
<Context docBase="/网站文件夹在硬盘的绝对路径" path="/浏览器地址栏的路径" reloadable="当网站文件被改变时是否自动重启">
如:
<Context docBase="D:\apps\My12306" path="/my12306" reloadable="true">
springMVC文件上传
1.导入jar包
commons-fileupload-1.2.1.jar commons-io-1.3.2.jar
2.在springmvc配置文件中配置上传解析器
<!--
id属性是bean的实例名,可以指定
class属性固定不变
property name="maxUploadSize",property name="defaultEncoding",名字固定,值可以指定
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 上传文件的最大值 -->
<property name="maxUploadSize">
<value>20971520</value>
</property>
<!-- 上传流的编码 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
3.编写Controller代码
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
/*
采用三种方式来接收上传文件,分别用三个方法试验
每个方法里,有程序执行时间记录
实际运用时,三者选其一
第一种最慢,第三种最快,一般选用第二种
*/
@Controller
public class FileController {
/*
* 通过流的方式上传文件
*
* @RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
*/
@RequestMapping("fileUpload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException {
// 用来检测程序运行时间
long startTime = System.currentTimeMillis();
try {
// 获取输出流
OutputStream os = new FileOutputStream("E:/" + new Date().getTime() + file.getOriginalFilename());
// 获取输入流 CommonsMultipartFile 中可以直接得到文件的流
InputStream is = file.getInputStream();
int temp;
// 一个一个字节的读取并写入
while ((temp = is.read()) != (-1)) {
os.write(temp);
}
os.flush();
os.close();
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("方法一的运行时间:" + String.valueOf(endTime - startTime) + "ms");
return "/success";
}
/*
* 采用file.Transto 来保存上传的文件
*/
@RequestMapping("fileUpload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file) throws IOException {
long startTime = System.currentTimeMillis();
String path = "E:/" + new Date().getTime() + file.getOriginalFilename();
File newFile = new File(path);
// 通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(newFile);
long endTime = System.currentTimeMillis();
System.out.println("方法二的运行时间:" + String.valueOf(endTime - startTime) + "ms");
return "/success";
}
/*
* 采用spring提供的上传文件的方法
*/
@RequestMapping("springUpload")
public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException {
long startTime = System.currentTimeMillis();
// 将当前上下文初始化给 CommonsMutipartResolver (多部分解析器)
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
// 检查form中是否有enctype="multipart/form-data"
if (multipartResolver.isMultipart(request)) {
// 将request变成多部分request
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
// 获取multiRequest 中所有的文件名
Iterator iter = multiRequest.getFileNames();
while (iter.hasNext()) {
// 一次遍历所有文件
MultipartFile file = multiRequest.getFile(iter.next().toString());
if (file != null) {
String path = "E:/springUpload" + file.getOriginalFilename();
// 上传
file.transferTo(new File(path));
}
}
}
long endTime = System.currentTimeMillis();
System.out.println("方法三的运行时间:" + String.valueOf(endTime - startTime) + "ms");
return "/success";
}
}
4.编写jsp页面代码
<!-- 三个form分别对应controller里三个上传方法 -->
<form name="Form1" action="fileUpload.v" method="post" enctype="multipart/form-data">
<h1>采用流的方式上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload" />
</form>
<form name="Form2" action="fileUpload2.v" method="post" enctype="multipart/form-data">
<h1>采用multipart提供的file.transfer方法上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload" />
</form>
<form name="Form3" action="springUpload.v" method="post" enctype="multipart/form-data">
<h1>使用spring mvc提供的类的方法上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload" />
</form>
Ajax和Controller的交互
1.导入jar包
jackson-annotations-2.9.2.jar jackson-core-2.9.2.jar jackson-databind-2.9.2.jar
2.在springmvc的配置文件中,加入json转换器
<!-- 加入到beans标签的标签体里 -->
<mvc:annotation-driven/>
传入key-value键值对,输出json
1.在ajax的提交请求里,传给后台的数据格式是kv对
//$.ajax $.post $.get 都是jquery的请求,需要引入jquery.js
//$.post和$.get都是$.ajax的简写方式
$.ajax({
type : "post",
url : "../tm.v",
contentType:"application/json; charset=utf-8",//请求成功后,后台返回的数据格式,即success : function(r)里“r”的格式
data : "name=shoji&price=8888",//此处就是标题中提到的传入key-value键值对
success : function(r) {
//r 直接就是jsonObject
alert(r.name);
}
})
2.在controller的方法返回值里,传给前台的数据格式是json
@RequestMapping(“tm”)
@ResponseBody
public BYQ tm(BYQ byq2){
System.out.println(byq2.getName());
System.out.println(byq2.getPrice());
return byq2;
}
传入json,输出json
1.在ajax的提交请求里,传给后台的数据格式是json串
$.ajax({
type : "post",
url : "../tm.v",
contentType:"application/json; charset=utf-8",
data:'{"name":"shouji","price":"8888"}',//此处就是标题中提到的传入的json格式串
success : function(r) {
//r 直接就是jsonObject
//如果返回的r是字符串,在调用r.price之前需要把字符串转为json对象,var jsonObject = JSON.parse(r);
alert(r.price);
}
})
2.在controller的方法返回值里,传给前台的数据格式是json,并且在方法的参数里接收的也是json串
//@ResponseBody注解和方法的返回值被置为BYQ,是为了返回值是json格式
//@RequestBody注解和方法的参数是BYQ类型,是为了接收前台提交过了的json格式
@RequestMapping("tm")
@ResponseBody
public BYQ tm(@RequestBody BYQ byq2){
System.out.println(byq2.getName());
System.out.println(byq2.getPrice());
return byq2;
}
restful
-
简介
体现在浏览器地址;每一个地址代表一种后台的资源(实例),地址里不能出现动词,具体的功能操作用提交method的类型来区分 -
映射模板
传统方式:http://localhost:8080/ssm/updateuser?id=123
restful: http://localhost:8080/ssm/user/123 通过这个地址看不出是增删改,具体操作下面来对应
提交方法类型对应(method的值)
get - 查询
post - 修改
put - 添加
delete - 删除
springmvc里对restful的支持
通过注解@PathVariable来支持rest风格的地址,详见
springmvc4以上的版本,提供注解@RestController来支持
当SpringMVC映射为/时,不需要被映射的地址处理方式
映射为/,即在web.xml中引入springmvc时,url-pattern标签的值为/,即网站下所有地址都被mvc拦截
...
<servlet>
<servlet-name>springmvcservlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mymvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvcservlet</servlet-name>
<url-pattern>/</url-pattern><!-- 被标识为/ -->
</servlet-mapping>
<!-- 地址被全部拦截后,出现的问题是,.js、.css、图片文件等等静态资源文件都被拦截了,不能直接访问 -->
...
解决静态资源被拦截的方法之一,在SpringMVC的配置文件中加入下面代码
<!-- 地址里含有js和img的,都不被springmvc拦截 -->
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:resources location="/img/" mapping="/img/**"/>