SpringMVC学习总结
1 . MVC架构
MVC是一种架构模型,本身没有什么功能,只是让我们的项目结构更加合理,流程控制更加清晰,一般包含三个组件:
一、流程图
二、流程总结
- Model(模型):数据模型,用于提供要展示的数据。一般包含数据和行为(也就是业务),在JavaWEB中,数据和业务往往是分离开的
- View(视图):负责对模型数据进行展示,例如我们看到的网页。概念比较广泛,可以是:html、JSP、excel、Word、PDF、json、XML等
- Controller(控制器):接收用户请求,委托给模型做处理,处理完毕返回数据,再交给视图做渲染,相当于调度员工作
2 . SpringMVC架构
一、流程图
二、流程总结
DisPatcherServlet:前端控制器
HandlerMapping:处理器映射
Handler:处理器
HandlerInterceptor:处理器拦截器
HandlerAdapter:处理器适配器
ModelAndView:模型和视图
Model:模型数据
View:视图名称,不是真正的视图对象
ViewResolver:视图解析器
- 用户发送请求到DisPatcherServlet
- DispatcherServlet通过HandlerMapping 寻找用户要请求的 Handler
- HandlerMapping返回执行链,包含两部分内容:Handler和HandlerInterceptor
- DisPatcherServlet通过HandlerAdapter对Handler进行适配包装
- 调用包装后的Handler中的方法处理业务
- 处理业务完成,返回ModelAndView对象,包含两部分内容:Model和View
- DispatcherServlet获取处理得到的ModelAndView对象
- DispatcherServlet将视图名称交给ViewResolver查找视图
- ViewResolver返回真正的视图对象给DispatcherServlet
- DispatcherServlet把Model交给视图对象进行渲染
- 返回渲染后的视图
- 将最终的视图返回给用户,产生响应
3 . SpringMVC入门案例
第一步:在war工程的pom.xml文件中导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itcast.springmvc</groupId>
<artifactId>springmvc-day03</artifactId>
<packaging>war</packaging>
<!-- 1. 导入父模块 -->
<parent>
<groupId>cn.itcast.parent</groupId>
<artifactId>itcast-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<!-- 2. 导入相关依赖 -->
<dependencies>
<!-- 2.1 SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- 2.2 JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
</dependency>
<!-- 2.3 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
</dependencies>
<!-- 3. 配置插件 -->
<build>
<plugins>
<!-- 3.1 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 端口信息 -->
<port>8080</port>
<!-- 项目路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:在WEB-INF目录下编写项目配置文件web.xml
<?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/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="MyWebApp" version="2.5">
<!-- 配置欢迎页面 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 描述servlet的名称信息 -->
<display-name>springMVC</display-name>
<servlet>
<!-- 在web.xml(web程序的入口) 配置了DispatcherServlet,
并且指定所有以.do结尾的请求全部交给DispatcherServlet来处理-->
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 配置/*会拦截所有请求,包括静态页面 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
第三步:在WEB-INF目录下创建springmvc-servlet.xml
springmvc-servlet.xml是SpringMVC项目的核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 配置HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- 配置Handler,一般以Controller后缀,指向自定义的那个Controller-->
<bean name="/hello.do" class="cn.itcast.controller.HelloController"/>
<!-- 视图解析器,这里是用内部资源视图解析器,其实就是指向项目中的jsp文件
Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" -> "/WEB-INF/jsp/test.jsp" -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
第四步:编写Handler
Handler,具体的处理器,在SpringMVC中,一般是叫做Controller(控制器),我们可以自定义类,实现Controller接口,就称为控制器,代码如下所示:
package cn.itcast.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class HelloController implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("hello");
mv.addObject("msg","这是我的第一个springmvc程序");
return mv;
}
}
第五步:定义视图
创建一个hello.jsp文件,符合上面的视图规则,所以要放到WEB-INF/views/下,代码如下
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>
第六步:测试
将该工程的父模块工程使用 maven install 命令,打成jar包到仓库中,然后使用tomcat7:run命令执行该模块工程
测试结果OK!
第七步:优化配置
- 我们尝试省略HandlerMapping和HandlerAdapter的配置
重启Tomcat重试:发现依然没有问题
原因:在spring-webmvc-4.1.3.RELEASE.jar –> org.springframework.web.servlet –> DispatcherServlet.properties文件中定义了很多默认配置,因此,一般我们只需要配置 Handler和视图解析器就够了
4 . SpringMVC注解案例
入门案例中,我们自定义控制器类的方式并不好,存在以下问题:
- 类必须实现Controller接口,不友好
- 每个类中只能有一个默认处理方法,只能处理一个用户请求
- 每个类需要在配置文件中进行配置,很麻烦
解决方案:在Spring2.5以后,就引入了注解方式的控制器定义方式,并且在Spring3.0以后引入了更多非常强大的注解帮我们进行参数注入
第一步:创建注解控制器
package cn.itcast.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
// 通过@Controller注解表名当前类是一个控制器类
@Controller
public class AnnotationController {
// 通过@RequestMapping注解定义当前方法的映射路径,在浏览器中通过这个路径访问这个方法
@RequestMapping("/show1")
public ModelAndView show1(){
ModelAndView mv = new ModelAndView("hello");
mv.addObject("msg","这是使用注解的第一个SpringMVC程序!");
return mv;
}
}
第二步:在springmvc-servlet.xml配置包扫描
要想使用SpringMVC的注解控制器,只需要在springmvc-servlet.xml文件中增加一个配置包扫描,代码如下所示:
<!-- 使用SpringMVC注解:直接配置包扫描,不需要Controller的Bean了 -->
<context:component-scan base-package="cn.itcast.controller"/>
第三步:测试
测试结果OK!
第四步:默认的配置问题及推荐方案
我们使用SpringMVC默认配置没有问题,完全可以使用。但是呢,有一些小问题存在:
- 默认的HandlerMapping过时了,推荐我们使用RequestMappingHandlerMapping
- 默认的HandlerAdapter过时了,推荐使用RequestMappingHandlerAdapter
所以,这里要玩注解方式,就不能使用默认配置,必须手动设置这两个推荐的HandlerMapping和HandlerAdapter
在springmvc-servlet.xml文件中增加两个bean节点,代码如下所示:
<!-- 配置注解方式的HandlerMapping和HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
第五步:注解驱动及原理
经过上面第四步的修改,我们的配置又变的很麻烦了。为了解决这个问题,其实在SpringMVC中,给我们提供了一种解决方案:只需要配置一个标签即可
在springmvc-servlet.xml文件中取消第四步配置的两个bean节点,增加一个开启注解驱动的节点,代码如下所示:
<!-- 开启注解驱动,就不要配置HandlerMapping和HandlerAdapter了 -->
<mvc:annotation-driven />
原因:在spring-webmvc-4.1.3.RELEASE.jar –> org.springframework.web.servlet.config –> AnnotationDrivenBeanDefinitionParser.class这个类中已经注册了2个HandlerMapping和3个HandlerAdapter
因此,当我们配置了注解驱动开关,那么注解驱动的类被加载后,会自动加载这几个HandlerMapping和HandlerAdapter,所以我们不需要配置了。
注解驱动相当于是原来默认配置的升级版!
因此,玩注解驱动的最终配置方式就是:
- 开启注解驱动
- 开启扫描包
- 配置内部资源解析器
最终的springmvc-servlet.xml文件代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解包扫描 -->
<context:component-scan base-package="cn.itcast.controller"/>
<!-- 开启注解驱动 -->
<mvc:annotation-driven />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
注意:上面的最终配置只针对注解方式有效
5 . RequestMapping请求映射方式
第一种:标准映射
规则:
- @RequestMapping可以设置在类上,也可以设置在方法上
- 请求的映射规则是:类上的RequestMapping + 方法上的RequestMapping
- 如果没有写 / ,SpringMVC会自动补全
- 类上的RequestMapping可以省略,这时直接用方法的RequestMapping访问
- 路径不可重复
第二种:Ant风格映射
Ant风格,其实就是通配符映射,有以下三种方式:
- ? 匹配任何单”字符”
- 匹配0或者任意数量的”字符”
- ** 匹配0或者更多的”目录”
第三种:占位符映射
@RequestMapping(value=”/users/{userId}”) :
其中{userId}占位符,请求的 URL 可以是 “/users/123456”或“/users/abcd”
通过@PathVariable 可以提取 URI 模板模式中的{userId}中的userId变量
演示代码如下:
// 演示占位符路径映射
@RequestMapping("/show4/{userId}")
public ModelAndView show4(@PathVariable("userId") Integer userId) {
ModelAndView mv = new ModelAndView("hello");
mv.addObject("msg", "占位符:" + userId);
return mv;
}
第四种:请求方式限定
我们可以通过@RequestMapping注解中的 method属性来限定客户端的请求方式,method属性可以接收的是一个枚举数组
限定post请求方式代码演示如下:
// 演示请求方式的限定
@RequestMapping(value = "/only/post", method={RequestMethod.POST})
public ModelAndView show5() {
ModelAndView mv = new ModelAndView("hello");
mv.addObject("msg", "请求方式限定,只允许post请求方式访问");
return mv;
}
第四种:请求参数限定
我们还可以通过@RequestMapping注解的params属性来对请求的参数进行限定
需求如下:
- 限定必须携带参数userid
- 限定不能携带参数name
- 限定必须携带参数age但是不能等于18
- 限定必须携带参数nickname但是必须是lisi
代码演示如下:
// 演示请求参数的限定
@RequestMapping(value = "/query", params = { "userid", "!name", "age!=18", "nickname=lisi" })
public ModelAndView show6(Integer userid, String name, Integer age, String nickname) {
ModelAndView mv = new ModelAndView("hello");
mv.addObject("msg",
"请求参数限定,您输入的userId:" + userid + "--name:" + name + "--age:" + age + "--nickname" + nickname);
return mv;
}
6 . 视图解析
一、什么是视图解析?
视图就是展示给用户看的结果。可以是很多形式,例如:html、JSP、excel表单、Word文档、PDF文档、JSON数据、freemarker模板视图等等。
我们目前比较常用的就是JSP视图了。
二、传统JSP和JSTL视图
我们所使用的InternalResourceViewResolver解析器,默认支持JSP视图,而JSP中最常用的莫过于JSTL标签库了
1.导入JSTL标签库依赖如下:
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
</dependency>
2.编写用户的控制器代码如下:
package cn.itcast.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import cn.itcast.mybatis.pojo.User;
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("list")
public ModelAndView getUserList(){
// 创建模型和视图对象
ModelAndView mv = new ModelAndView("user");
// 创建一个集合对象
List<User> users = new ArrayList<User>();
for (Long i = 1L; i <= 10L; i++) {
User user = new User();
user.setId(i);
user.setuserName("lisi" + i);
user.setName("lisi" + (11 - i));
user.setAge(18);
users.add(user);
}
mv.addObject("users",users);
return mv;
}
}
3.编写用户视图代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
table {border-collapse: collapse;}
table, th, td {border: 1px solid green;padding: 10px}
</style>
</head>
<body>
<table>
<tr>
<th>ID</th>
<th>UserName</th>
<th>Name</th>
<th>Age</th>
</tr>
<c:forEach items="${users }" var="user">
<tr>
<td>${user.id }</td>
<td>${user.userName }</td>
<td>${user.name }</td>
<td>${user.age }</td>
</tr>
</c:forEach>
</table>
</body>
</html>
4.测试
测试结果OK!
三、JSON视图
在实际开发中,我们经常需要以JSON的格式进行数据的传输,所以在SpringMVC中提供了一种非常便捷的方式来返回一个JSON视图
第一步:引入依赖
SpringMVC的JSON功能,底层依赖的是Jackson这个JSON工具,引入依赖代码如下所示:
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
第二步:定义需要访问的Controller
代码如下所示:
// 演示:返回JSON视图
@RequestMapping("/ajaxlist")
// 这里没有返回ModelAndView,而是通过ResponseBody注解声明,返回的是JSON视图
// SpringMVC会自动的把返回值,本例中的是List<User>转为JSON格式返回
@ResponseBody
public List<User> getAjaxUserList(){
// 创建一个集合对象
List<User> users = new ArrayList<User>();
for (Long i = 1L; i <= 10L; i++) {
User user = new User();
user.setId(i);
user.setuserName("lisi" + i);
user.setName("lisi" + (11 - i));
user.setAge(18);
users.add(user);
}
return users;
}
第三步:测试
测试结果OK!
第四步:分析原理
- 当SpringMVC读取到方法上的@ResponseBody注解时,就知道该方法不再使用默认的视图解析器解析视图,而是直接把结果写到响应体中,这样就需要对结果进行转换
- SpringMVC会从框架中查找有没有定义MessageConvertor(消息转换器),通过消息转换器转换结果,返回对应视图
- 在SpringMVC的注解驱动类中,会进行默认的消息转换器注册,因为我们引入了jacksonJson包,所以会注册JSON的消息转换器
注解驱动类中的代码:在AnnotationDrivenBeanDefinitionParser.class中关键点代码如下所示:
if (jackson2Present) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
}
4 因为只有JSON消息转换器可以对Java对象序列化,因此这里默认用了JSON转换
第五步:问题:如果有多个消息转换的依赖被引入,那么就会注册多个消息转换器:例如JSON、XML等等,此时会用哪个转换器?
答案:此时会根据请求头中的accept来判断返回什么类型数据
四、直接返回视图名称
有时候,我们并不需要返回数据模型,而仅仅是访问某个JSP页面。如果是传统做法,你要创建一个ModelAndView对象,并且返回。很麻烦。
所以,SpringMVC允许直接返回一个String数据,作为视图名称。不需要数据模型,演示代码如下:
// 演示:直接返回视图名称,不需要数据模型
@RequestMapping("/index")
public String index(){
// 如果只是访问一个页面,就不需要创建ModelAndView对象,直接返回一个字符串
// 这个字符串就会作为视图的名称,从而去访问对应的页面
return "index";
}
编写index.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>欢迎光临首页</h1>
</body>
</html>
也可以把视图与模型分离。在参数中注入Model对象:
// 把视图与模型分离。在参数中注入Model对象
@RequestMapping("/index2")
// 直接返回视图,而不需要model中的数据
public String index2(Model model){
model.addAttribute("msg","我试试Model好不好用");
return "hello";
}
五、重定向和转发
在SpringMVC中,如果要实现重定向或转发,也非常简单,只需要在返回的字符串前以下面的格式做前缀即可:
redirect:/hello.do 重定向到hello.do
forward:/hello.do 转发到hello.do
注意:后面必须跟上URL路径而非视图名
演示代码如下所示:
// 演示重定向
@RequestMapping("/demo1")
// 直接返回视图,而不需要model中的数据
public String demo1(){
// 返回值以redirect开头,但是后面必须跟上URL路径而非视图名
return "redirect:/user/index.do";
}
// 演示转发
@RequestMapping("/demo2")
// 直接返回视图,而不需要model中的数据
public String demo2(){
// 返回值以redirect开头,但是后面必须跟上URL路径而非视图名
return "forward:/user/index.do";
}
注意:返回ModelAndView,或者没有写redirect和forward时,默认都属于转发
六、不返回视图
有的时候,我们接收一个请求,处理后并不打算给页面返回任何数据,只是返回成功的状态码即可,演示代码如下:
// 演示不返回视图
@RequestMapping("/demo3")
@ResponseStatus(HttpStatus.OK)
// 如果写void,表示不返回模型和视图,那么就需要加ResponseStatus,返回一个响应的状态码
public void demo3() {
System.out.println("你好,我收到了你的请求,但是什么也不想做");
}
7 . 请求参数绑定和获取
struts2获取参数的弊端:
获取页面参数,是javaee必不可少的一个环节,在struts中,是通过action定义属性,或者modle的方式进行数据绑定和获取,需要提供setter和getter方法,略显麻烦
SpringMVC获取参数的优点:
在SpringMVC中,却可以非常方便的获取请求参数,只需要在对应方法的参数列表中定义出来,即可获取,所见即所得!