SpringMVC基础及使用
基本使用配置
Spring的jar包官网下载地址
http://repo.spring.io/release/org/springframework/spring/
springMVC流程
1、请求首先到达DispatcherServlet(控制器)
2和3、找Handlermapping,由它负责将url交给某个controller处理。
4、Controller与service等交互,处理完成后返回视图名称
5、根据返回的视图名称找到对应视图ViewResolver
6、返回视图。
HandlerMapping的继承结构:
默认是用BeanNameUrlHandlerMapping,
但一般都不用,而是使用DefaultAnnotationHandlerMapping
源码:
jsp:



domain:
controller:
配置:
1、加入jar包:
1、加入springmvc的jar
选择3.2.0的版本即可下载。
libs中包含了所有jar,其中有doc(文档包)和sources(源码包)
将doc和sources包去掉即可,
放入web工程lib中。
2、加入第三方包:
commons-logging-1.1.3.jar、commons-io-2.2.jar、commons-fileupload-1.3.jar
2A、基于配置文件方式配置DispatcherServlet(BeanNameUrlHandlerMapping,不常用)
2A.1、web.xml中配置
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
其中DispatcherServlet可以用ctrl shift T快捷键找到,关联的源代码包为spring-webmvc-3.2.0.RELEASE-sources.jar。
这里的<url-pattern>/</url-pattern>而不是/*,/*的话会拦截jsp请求,而/不会,因此为了避免返回的jsp也被拦截,就用/。
2A.2、新建WEB-INF/hello-servlet.xml(就是以前的applicationContext.xml)
名字=web.xml中配置的servlet-name加上"-servlet"
内容:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
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-3.1.xsd">
<bean name="/welcome.html" class="com.test.controller.WelcomeController"></bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
其中/welcome.html表示请求路径,InternalResourceViewResolver表示返回资源的处理,返回资源地址由配置的"prefix+返回字符串+suffix"组成。
2A.3、新建com.test.controller.WelcomeController类,
继承org.springframework.web.servlet.mvc.AbstractController,内容:
package com.test.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class WelcomeController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("welcome");
return new ModelAndView("welcome");
}
}
如:请求地址为http://localhost:8077/springmvc_hello/welcome.html
则会到AbstractController,找到/WEB-INF/jsp/welcome.jsp返回给客户端。
2A.4创建/WEB-INF/jsp/welcome.jsp,用于返回
2B、基于注解方式配置DispatcherServlet(DefaultAnnotationHandlerMapping,常用)
2B .1、web.xml中配置DispatcherServlet
同2A.1。
2B .2、新建WEB-INF/hello-servlet.xml
类似2A.2的内容,不过不用配置具体的controller了
内容:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
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-3.1.xsd">
<context:component-scan base-package="com.test.controller"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
其中component-scan表示扫描controller的包的根路径
<mvc:annotation-driven/>表示开启annotation
InternalResourceViewResolver含义同2A.3中一样。
2B.3新建com.test.controller.HelloController类
不用继承任何类,内容:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("cdd")
public class HelloController {
@RequestMapping({"hello1", "hello2"})
public String hello() {
System.out.println("hello");
return "hello";
}
}
其中@Controller表示这是一个controller,扫描的时候可以被发现。
@RequestMapping表示匹配的路径,比如这里访问路径是:
http://localhost:8077/springmvc_hello/cdd/hello1
或者
http://localhost:8077/springmvc_hello/cdd/hello2
@RequestMapping("cdd")
等同于
@RequestMapping(value="cdd")
2A.4创建/WEB-INF/jsp/hello.jsp,用于返回
传参数:
接收参数
public String hello(@RequestParam("username") String username) {
System.out.println(username);
return "hello";
}
或者
public String hello(String username) {
System.out.println(username);
return "hello";
}
访问方式:
http://localhost:8077/springmvc_hello/cdd/hello1?username=kkk
则后台会打印出kkk,如果加了@RequestParam而又没有带参数访问会报400错误,因为
@RequestParam表示参数是请求的一部分。
@RequestParam("username")中的username是浏览器带的实参名,String username中的username是形参名,两个可以不同。浏览器可以不为参数设置值例如"&username=",此时服务器接收到的是空串,但必须带上,否则报错。
请求路径参数
可以在方法中声明带某个参数的请求才进该方法,如:
@RequestMapping(value="/users", method=RequestMethod.GET, params="jj")
public String list(String a, Model model) {
…
}
此时带了jj这个参数的请求该路径请求才会进入这个方法。
http://localhost:8077/springmvc_hello/user/users不会进入该方法,
http://localhost:8077/springmvc_hello/user/users?jj才会进入
返回参数到jsp
public String hello(String username, Model model) {
System.out.println(username);
model.addAttribute("hello", "12345");
return "hello";
}
jsp中用${username}即可输出"12345"。
hello方法的第一个参数是接收,第二个model参数是返回,类似于returnMap。model.addAttribute以键值对的方式设置返回值。
简化的方式:
model.addAttribute("666");
相当于
model.addAttribute("string", "666");
没有key时,默认以value的类型作为key,如:
model.addAttribute(new User());
相当于
model.addAttribute("user", new User());
struts2的参数是放到成员变量中的,因此为了避免冲突不能用单例,而springmvc的controller不存在这种问题因为可以用单例,不用每次都创建对象,效率会高一些,速度快一些。
REST风格:
用目录名代替参数的方式,如:
/user_show?id=120
/user/120
/user_delete?id=120
/user/120/delete
/user_update?id=120
/user/120/update
/user_list
/users或者/user/users
REST风格不等于使用了REST技术。
可以根据不同的请求类型GET、POST、PUT、DELETE等执行不同的方法,如:
//链接到add页面时是GET请求,会访问这段代码
@RequestMapping(value="/add",method=RequestMethod.GET)
//在具体添加用户时,是post请求,就访问以下代码
@RequestMapping(value="/add",method=RequestMethod.POST)
使用方法:
1、查询用户详情
@RequestMapping(value="/{username}",method=RequestMethod.GET)
public String show(@PathVariable String username, Model model) {
model.addAttribute(users.get(username));
return "user/show";
}
这个show方法是根据用户名查询详情,返回单个用户详情页面,@PathVariable String username表示传入的用户id,users.get(username)是为了简化,user的查询过程,把所有user都放到users这个Map中,实际开发时应该从数据库查询user。
这个方法相当于拦截了本类中的所有查询详情的请求,然后根据不同的id查找user,不过如果没有找到,则model.addAttribute(null);会报错" Model object must not be null"
2、跳转到更新页面
@RequestMapping(value="/{username}/update", method=RequestMethod.GET)
public String update(@PathVariable String username, Model model) {
model.addAttribute(users.get(username));
return "user/update";
}
value="/{username}/update"用于拦截类似123/update这种rest风格的跳转到更新页面的请求
3、更新
@RequestMapping(value="/{username}/update", method=RequestMethod.POST)
public String update(@PathVariable String username, @Validated User user, BindingResult br) {
if (br.hasErrors()) {
return "user/update";
}
users.put(username, user);
return "redirect:/user/users";
}
拦截地址跟跳转更新相同,只是拦截的是POST请求。
4、删除
@RequestMapping(value="/{username}/delete", method=RequestMethod.GET)
public String delete(@PathVariable String username) {
users.remove(username);
return "redirect:/user/users";
}
springMVC标签库
jsp头部引入:
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
使用:
<sf:form method="post" modelAttribute="user">
用户名:<sf:input path="username"/><sf:errors path="username"/><br/>
密码:<sf:input path="password"/><sf:errors path="password"/><br/>
昵称:<sf:input path="nickname"/><br/>
邮箱:<sf:input path="email"/><sf:errors path="email"/><br/>
<input type="submit" value="提交"/>
</sf:form>
其中modelAttribute="user"表示自动取服务器端的user对象,提交的时候也封装user对象提交。
<sf:input path="username"/>是user对象的一个属性,用path表示。
<sf:errors path="username"/>是出错提示,这里用的是JSR303。
开启model driven
显示新增页面的对应controller方法是:
@RequestMapping(value="/add", method=RequestMethod.GET)
public String add(@ModelAttribute("user")User user) {
return "user/add";
}
参数这里加了@ModelAttribute("user"),如果不加,则前台页面modelAttribute="user"会取得null,而<sf:input path="username"/>再取其属性显示就没有,会报错。
加了@ModelAttribute("user")以后,表示开启了model driven前台页面会自动创建user对象。
JSR303验证
JSR303由sun公司提供。可以用于在bean类的get属性方法上增加注解的方式验证get出来的值是否合法。
用法:
1、导入bean-validator.jar
2、实体类的get属性的方法上加注解,如:
@NotEmpty(message="用户名不能为空")
public String getUsername() {
return username;
}
@Size(min=1,max=10,message="密码长度1到10")
public String getPassword() {
return password;
}
其中message是出错后的错误信息。
3、controller中这样使用:
@RequestMapping(value="/add", method=RequestMethod.POST)
public String add(@Validated User user, BindingResult br) {
if (br.hasErrors()) {
return "user/add";
}
users.put(user.getUsername(), user);
// 重定向跳转
return "redirect:/user/users";
}
其中参数@Validated User user, BindingResult br
表示user类是需要验证的,验证的结果放到br中。通过br.hasErrors()判断验证是否通过。注意这里的BindingResult br定义必须紧跟@validate User user
4、前台jsp页面提示错误:
<sf:form method="post" modelAttribute="user">
用户名:<sf:input path="username"/><sf:errors path="username"/><br/>
密码:<sf:input path="password"/><sf:errors path="password"/><br/>
昵称:<sf:input path="nickname"/><br/>
邮箱:<sf:input path="email"/><sf:errors path="email"/><br/>
<input type="submit" value="提交"/>
</sf:form>
里面的<sf:errors>标签就是提示错误信息的位置,如:
重定向
return "redirect:/user/users"
重定向到另一个方法中去。
取request、response、session等容器对象
直接在方法参数中声明即可使用,如:
@RequestMapping(value="login", method=RequestMethod.POST)
public String login(String username, String password, HttpSession session) {
…
}
这样方法里面就可以使用session了。
异常处理
局部异常处理
处理某个控制器中的特定异常,如用户登录失败时自定义的UserException可以这样手动处理:
// 局部异常处理,仅仅只能处理这个控制器中的异常
@ExceptionHandler(value={UserException.class})
public String handlerException(UserException e, HttpServletRequest request) {
request.setAttribute("e", e);
return "error";
}
error.jsp可以用${e}输出异常信息
全局异常处理
全局异常处理对所有的控制器都起作用,方法是在hello-servlet.xml中配置:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.test.domain.UserException">error</prop>
</props>
</property>
</bean>
其中SimpleMappingExceptionResolver是spring提供的一个异常处理类,对它的exceptionMappings属性进行注入Map即可,如注入的key是com.test.domain.UserException,value是error,当捕获到UserException异常就会自动跳转到error.jsp。
在error.jsp中用${exception}可以看到异常信息,exception这个变量名默认的。
定义resources静态资源文件排除springmvc拦截
css、js这种文件路径默认会被springmvc拦截,需要配置排除的资源路径,如在hello-servlet.xml中配置:
<!-- 将静态文件指定到某个文件夹统一处理 -->
<mvc:resources location="/resources/" mapping="/resources/**"/>
此时resources及其子目录下的所有资源文件都不会被springmvc拦截,可以直接访问,一般将所有资源文件都放到resources目录下,统一排除拦截。
文件上传
单文件上传
1、hello-servlet.xml中加入配置:
<!-- 设置multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000"/>
</bean>
其中CommonsMultipartResolver是处理文件上传的类,继承自CommonsFileUploadSupport。
maxUploadSize是maxUploadSize的方法,表示文件最大值,这里是5M
2、页面上的form标签增加属性:enctype="multipart/form-data"。
file的输入域,如:
<input type="file" name="attach"/>
3、后台接收:
@RequestMapping(value="/add", method=RequestMethod.POST)
public String add(MultipartFile attach, HttpSession session) throws IOException {
// 得到文件绝对路径
String realPath = session.getServletContext().getRealPath("/resources/upload")
+ "/" + attach.getOriginalFilename();
// 保存文件
FileUtils.copyInputStreamToFile(attach.getInputStream(), new File(realPath));
System.out.println(attach.getOriginalFilename()); // 文件原始名
System.out.println(attach.getName()); // 文件输入域的name,如"attach"
// 重定向跳转
return "redirect:/user/users";
}
多文件上传
代码:
页面上:
<input type="file" name="attachs"/>
<input type="file" name="attachs"/>
<input type="file" name="attachs"/>
后台:
@RequestMapping(value="/add", method=RequestMethod.POST)
public String add(@RequestParam("attachs")MultipartFile[] attachs, HttpSession session) throws IOException {
// 保存文件
for (MultipartFile multipartFile : attachs) {
if (multipartFile.isEmpty()) {
continue;
}
String realPath = session.getServletContext().getRealPath("/resources/upload")
+ "/" + multipartFile.getOriginalFilename();
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), new File(realPath));
}
// 重定向跳转
return "redirect:/user/users";
}
跟单文件上传类似,只是参数必须加@RequestParam指定别名,接收类型改成了数组。遍历的时候要判断multipartFile.isEmpty(),因为用户可能没有选择所有的输入域。
返回JSON
1、加入jackson-all-1.9.4.jar
2、后台:
@RequestMapping(value="/testjson")
@ResponseBody
public User testjson() {
return users.get("ww");
}
增加了@ResponseBody,方法直接返回user对象,会自动转成json字符串,如:
{"username":"ww","password":"12356","nickname":"王五","email":"cc@qq.com"}
也可以是Map等集合,如:
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "zs");
map.put("age", "33");
return map;
返回:
{"age":"33","name":"zs"}
进阶使用
配置
maven配置
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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>testspringmvc2</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>testspringmvc2 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
<version>2.5</version>
</dependency>
<!-- spring mvc,以下的core、context、beans可以不要-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<!-- jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>testspringmvc2</finalName>
<plugins>
<!-- 部署web应用到Tomcat下的插件,并设置Tomcat启动端口与访问路径 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
ps:jstl的依赖根据实际情况而定(这里使用到了jstl,所以加了它的依赖)
web.xml配置
src/main/webapp/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"
version="2.5">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:aaa.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
ps:就配置了一个DispatcherServlet
servlet的spring配置
src/main/resources/aaa.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
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-3.1.xsd">
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- viewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 处理器 -->
<bean name="/hello" class="com.test.controller.HelloWorldController"/>
</beans>
ps:
BeanNameUrlHandlerMapping:
表示将请求的URL和Bean名字映射,如URL为 "上下文/hello",则Spring配置文件必须有一个名字为"/hello"的Bean,上下文默认忽略。
SimpleControllerHandlerAdapter:
表示所有实现了org.springframework.web.servlet.mvc.Controller接口的Bean可以作为Spring Web MVC中的处理器,这是由它的supports方法决定的,它会调用controller的handleRequest方法。
BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter也可以不用配置,因为DispatcherServlet.properties提供了默认使用的
编写HelloWorldController处理类
src/main/java/com/test/controller/HelloWorldController.java
内容:
package com.test.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 HelloWorldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1、收集参数、验证参数
// 2、绑定参数到命令对象
// 3、将命令对象传入业务对象进行业务处理
// 4、选择下一个页面
ModelAndView mv = new ModelAndView();
// 添加模型数据 可以是任意的POJO对象
mv.addObject("message", "Hello kkk!");
// 设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面
mv.setViewName("hello");
return mv;
}
}
ps:实现Controller接口只用实现handleRequest方法接口。
编写jsp
src/main/webapp/WEB-INF/jsp/hello.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>Hello World</title>
</head>
<body>
${message}
</body>
</html>
访问
http://localhost/hello
AbstractController和WebContentGenerator
AbstractController实现了Controller接口,因此可以直接继承AbstractController。
而AbstractController的handleRequest调用了抽象方法handleRequestInternal,因此只需要实现handleRequestInternal方法即可。
如:
AbstractController继承了WebContentGenerator,而WebContentGenerator里面有众多web配置,如
AbstractController在handleRequest时会对请求进行校验,也会把配置的是否使用缓存等放到response
WebContentGenerator的参数
cacheSeconds:设置浏览器缓存的时间
测试:
controller
spring配置:
<bean name="/hello" class="com.test.controller.HelloWorldController">
<property name="cacheSeconds" value="5"/>
</bean>
页面:
设置缓存5秒
此时点this链接,间隔5秒以上才会去后台请求,5秒以内重复点击,页面显示的时间不变(对F5刷新无效)。
原理是response返回的Headers里面增加了Cache-Control:max-age=5参数。
ps:
1、对于一般的页面跳转(如超链接点击跳转、通过js调用window.open打开新页面都是会使用浏览器缓存的,在未过期情况下会直接使用浏览器缓存的副本,在未过期情况下一次请求也不发送);
2、对于刷新页面(如按F5键刷新),会再次发送一次请求到服务器的;
设置为0时,表示不缓存,每次都用最新
而设置成负数,则不会有Cache-Control属性,表示不控制缓存,由浏览器默认自己控制。
lastModified由服务端控制页面是否用缓存数据
服务端返回的header中可以写入Last-Modified属性,是一个时间,如:
客户端下次请求相同的链接时,会把该参数以If-Modified-Since带上,如:
服务端可根据该参数判断这段时间是否有内容改变,如果没有,可以像客户端返回304状态码,客户端就会使用缓存数据了,如:
springmvc中使用lastModified只需要controller实现LastModified接口即可。
如:
实现getLastModified方法即可。
DispatcherServlet执行dispatch方法时,会先做lastModified的判断,该方法会判断如果controller实现了LastModified接口,则会去调用它的getLastModified取得时间,判断该时间如果小于等于客户端传来的时间,则表示服务端没有更新,直接返回304,不执行handler.handle方法,也不进controller。
Etag由服务端控制页面是否用缓存数据
web.xml中增加shallowEtagHeaderFilter的配置
<filter>
<filter-name>shallowEtagHeaderFilter</filter-name>
<filter-class>
org.springframework.web.filter.ShallowEtagHeaderFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>shallowEtagHeaderFilter</filter-name>
<servlet-name>hello</servlet-name>
</filter-mapping>
这里可以用<servlet-name>也可以用<url-mapping>
Etag由filter实现,与lastModified类似,它会自动把服务端返回的内容通过md5加密生成一个字符串,response的Headers会带上它,key是Etag,客户端下次请求时会headers带上(key是If-None-Match),filter会自动根据传入的key和需要返回的内容生成的key判断倆key是否相同。如果相同则返回304,告诉客户端使用缓存。
ServletForwardingController(转向servlet)
将请求转给servlet处理,如:
ForwardServlet:
public class ForwardServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public ForwardServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("forwardServletGet");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("forwardServletPost");
}
}
web.xml中的servlet配置:
<servlet>
<servlet-name>ForwardServlet</servlet-name>
<servlet-class>com.test.controller.ForwardServlet</servlet-class>
</servlet>
springmvc配置:
<bean name="/forwardToServlet"
class="org.springframework.web.servlet.mvc.ServletForwardingController">
<property name="servletName" value="ForwardServlet"></property>
</bean>
此时访问该controller会将请求转到servlet处理:
源码可以看到直接forward过去了
ParameterizableViewController(配置转向页,精确匹配)
配置文件中指定转向页面,没有java代码,通常用于帮助页面等静态页。
如:
springmvc配置:
<bean name="/parameterizableView"
class="org.springframework.web.servlet.mvc.ParameterizableViewController">
<property name="viewName" value="hello2" />
</bean>
直接转向hello2.jsp
UrlFilenameViewController (配置转向页,模糊匹配)
与ParameterizableViewController类似,通过配置指定转向页面,不需要java代码,不过是模糊匹配路径。如:
<bean name="/bbb/*"
class="org.springframework.web.servlet.mvc.UrlFilenameViewController" >
<property name="prefix" value="ccc/"/>
</bean>
转向到页面ccc/hello3.jsp
匹配规则:
/index1/*:可以匹配/index1/demo,但不匹配/index1/demo/demo,如/index1/demo逻辑视图名为demo;
/index2/**:可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo,"/index2/demo"的逻辑视图名为demo,而"/index2/demo/demo"逻辑视图名为demo/demo;
/*.html:可以匹配如/abc.html,逻辑视图名为abc,后缀会被删除(不仅仅可以是html);
/index3/*.html:可以匹配/index3/abc.html,逻辑视图名也是abc;
? 匹配一个字符,如/index? 可以匹配 /index1 , 但不能匹配 /index 或 /index12
* 匹配零个或多个字符,如/index1/*,可以匹配/index1/demo,但不匹配/index1/demo/demo
** 匹配零个或多个路径,如/index2/**:可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo
如果我有如下模式,那Spring该选择哪一个执行呢?当我的请求为"/long/long"时如下所示:
/long/long
/long/**/abc
/long/**
/**
Spring的AbstractUrlHandlerMapping使用:最长匹配优先;
如请求为"/long/long" 将匹配第一个"/long/long"
请求"/long/acd" 则将匹配 "/long/**"
请求"/long/aa/abc"则匹配"/long/**/abc
请求"/abc"则将匹配"/**"
Annotation方式使用
配置和原理
使用的HandlerMapping和HandlerAdapter配置如下:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
由于RequestMappingHandlerMapping和RequestMappingHandlerAdapter被springmvc默认使用,所以可以不用配置。
springmvc加载bean时,发现有@Controller配置的bean就会把它当做Controller来处理。
请求到来时,springmvc会通过反射调用Controller里面有@RequestMapping方法。
使用
使用如:
@Controller
@RequestMapping("t1")
public class HelloWorldController {
@RequestMapping("tt")
public String tt() {
return "ccc/hello3";
}
}
其中@Controller替代了继承Controller的方式,@RequestMapping
对应了路径的映射,这里的请求地址是:
http://localhost/t1/tt
也可以用返回ModelAndView的方式:
@Controller
@RequestMapping("t1")
public class HelloWorldController {
@RequestMapping("tt")
public ModelAndView tt() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("ccc/hello3");
return modelAndView;
}
}
请求的映射
springmvc对请求的各部分有不同的注解支持。
其中1、2、4、6是可变的,因此主要是对这几部分进行注解支持。
URL映射
通过@RequestMapping实现:
Controller类上的@RequestMapping是该Controller所有方法路径的前缀,类似于namespace,全路径=Controller类上的@RequestMapping路径+方法上的@RequestMapping路径。
需要被映射的方法必须加@RequestMapping,否则无法映射。
@RequestMapping的value值前面可以不加斜杠,springmvc会自动补上,但如果没有指定value值,则不会自动补上。
如:
Controller类和方法上都没有指定value值,此时通过localhost访问会报404,因为localhost默认映射/。
指定value值为空即可:
value值如果是/,则必须带/访问,如:
访问路径:
正确:http://localhost/t1/
错误:http://localhost/t1
而value为空的情况下,带不带/都可以访问
普通URI映射
@RequestMapping(value={"/test1", "/user/create"})
/test1或者/user/create都匹配
同一个Controller中有普通映射路径相同的方法(参数、请求类型等都相同),启动不会报错,但访问时会提示报错。
如:
不同的Controller中有普通映射路径相同的方法,启动就会报错(参数、请求类型等都相同)
如:
启动报错:
java.lang.IllegalStateException: Cannot map handler 'com.test.controller.HelloWorldController2#0' to URL path [/t1/t2]: There is already handler of type [class com.test.controller.HelloWorldController] mapped.
URI模板模式映射
@RequestMapping(value="/users/{userId}")
匹配如:/users/123456或/users/abcd
@RequestMapping(value="/users/{userId}/create")
匹配如:/users/123/create
@RequestMapping(value="/users/{userId}/topics/{topicId}")
匹配如:/users/123/topics/456
Ant风格的URL路径映射
@RequestMapping(value="/users/**"):
匹配如:/users/abc、/users/a/b、/users/a/b/c/d
但/users/123会被/users/{userId}优先匹配
@RequestMapping(value="/product?")
匹配如:/product1、/producta
不匹配如:/product、/productaa";
@RequestMapping(value="/product*")
匹配如:/productabc、/product
不匹配如:/productabc/abc
@RequestMapping(value="/product/*")
匹配如:/product/abc
不匹配如:/productabc
@RequestMapping(value="/products/**/{productId}")
匹配如:/products/abc/abc/123、/products/123
正则表达式匹配:
@RequestMapping(value="/products/{categoryCode:\\d+}-{pageNumber:\\d+}")
匹配如:/products/123-1
不匹配如:/products/abc-1
其中categoryCode和pageNumber是自定义的变量名字
请求类型限定映射
如:
@RequestMapping(value="tt", method=RequestMethod.POST)
可以在Controller上或者方法上加类型限定。比如此时如果GET访问,就会提示:
Controller上和方法上的请求类型过滤是独立的,必须都通过才能访问,如:
@Controller
@RequestMapping(value="t1", method=RequestMethod.POST)
public class HelloWorldController {
@RequestMapping(value="tt", method=RequestMethod.GET)
public String inCookie(HttpServletResponse response) {
return "ccc/hello3";
}
}
此时所有请求都不能通过,因为Controller上和方法上的请求类型没有交集。
ps:
请求一共有GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE七种。
不配置时,默认支持GET、POST、PUT、DELETE、HEAD五种
如果需要支持OPTIONS、TRACE,需要在web.xml中为DispatcherServlet配置初始化参数:dispatchOptionsRequest 和 dispatchTraceRequest 为true
请求参数限定映射
如:
@RequestMapping(value="tt", params="kkk")
请求中必须有kkk参数,否则显示404.

@RequestMapping(value="tt", params="kkk=5")
必须有kkk参数,且它的值为5,否则404
如:http://localhost/t1/tt?kkk=5
如:
@RequestMapping(value="tt", params="!kkk")
必须没有kkk参数,如果有就404.
@RequestMapping(value="tt", params="kkk!=5")
没有kkk参数或者kkk不为5都可以,如果有kkk且为5才404.
@RequestMapping(value="tt", params={"kkk=5", "ccc=6"})
kkk=5且ccc=6才能访问,否则404
localhost/t1/tt?kkk=5&ccc=6
请求头参数限定映射
如
@RequestMapping(value="tt", headers="k3")
headers中包含名字为k3的参数才能访问,否则404
firefox搜索modify headers插件,安装打开设置

添加k3参数,start
访问即可带上k3的header了
@RequestMapping(value="tt", headers="!k3")
请求头中必须没有k3,否则404
@RequestMapping(value="tt", headers="k3!=3")
没有k3或者k3不等于3,否则404
@RequestMapping(value="tt", headers={"k2=3", "k3=4"})
k2=3且k3=4,否则404
数据绑定
自动绑定ServletRequest、ServletResponse、HttpServletRequest、HttpServletResponse参数
如:
@RequestMapping(value = "hello3")
public String hello1(ServletRequest servletRequest, ServletResponse servletResponse, HttpServletRequest request, HttpServletResponse response) {
return "ccc/hello3";
}
自动绑定InputStream、OutputStream、Reader、Writer
其中InputStream/OutputStream和Reader/Writer一次只能用其中一组。
测试:
页面:
<form action="upload" method="post" enctype="multipart/form-data">
文件:<input type="file" name="k5"/><br/>
<input type="submit" value="上传"/>
</form>
后台:
@RequestMapping(value = "upload")
public String upload(InputStream inputStream) throws IOException {
String inputStr = StreamUtils.copyToString(inputStream, Charset.forName("utf-8"));
System.out.println(inputStr);
return "ccc/hello3";
}
操作:
上传文件内容:
后台打印:
查看浏览器请求体:
由于inputStream中除了包含文件内容,还包含了
------WebKitFormBoundary405G2nXIHhp6lzwQ—
等这些分隔符信息,因此不能直接使用,需要用上传组件进行封装后使用。
自动绑定WebRequest、NativeWebRequest
WebRequest可以直接操作session和ServletContext,(感觉这个用处不大,HttpServletRequest也可以getSession操作,不如直接绑HttpServletRequest)。
如:
第一次访问打印null,因为没有设置过,以后每次访问都会打印,都是从session中取。
NativeWebRequest可以取得HttpServletRequest,如:
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
(感觉这个没啥用,不如直接在方法上绑定HttpServletRequest)
自动绑定HttpSession
如:
@RequestMapping(value = "hello3")
public String hello3(HttpSession session) {
return "ccc/hello3";
}
自动绑定方法参数
如:

访问:
http://localhost/ccc/hello3?userName=555&&group.groupName=666
打印:
会自动去找user的setUserName设置属性。对group.groupName属性也会自动创建Group对象设置属性。
但是对Map是不会自动设置的,如:
访问:
http://localhost/ccc/hello3?map.userName=555
打印:
自动绑定Model、 Map 、ModelMap
如:
页面:
访问显示:
可以看出Model、 Map 、ModelMap设置的属性都被渲染到页面了,后台打印:
,说明它们其实是同一个对象。这个对象同时继承了Model、 Map 、ModelMap。
断点看到这个类是BindingAwareModelMap。
继承结构如下:
如果使用返回ModelAndView的方式,也会自动添加model里的键值对进去(其实Model在调用方法之前就已经创建,这里只是暴露出来,所以不是model的键值对添加到了ModelAndView,而是model本来就有)。
如:
此时虽然modelAndView是新建的,但springmvc依然会把model里的键值对设置进去
使用modelAndView方式的方法返回以后,springmvc会将modelAndView里面设置键值对再覆盖到model里,传给页面渲染,如:
此时Model里的modelKey键被ModelAndView的覆盖了
自动绑定Locale 、Principal
如:
可以用
request.getLocale();
request.getUserPrincipal();
代替。
@RequestParam
页面参数可以直接对应到方法参数,而且名字一致,是因为java文件编译成class时为局部变量指定了LocalVariableTable属性,该属性中有参数的名字,springmvc反射时才能通过参数名字对应到页面参数变量。
如果在项目属性中将生成变量属性到class文件选项去掉,则不会生成LocalVariableTable属性.
此时springmvc在反射的时候就不知道方法对应参数名字,进而报错:
用@RequestParam指定参数名字可以让springmvc在反射的时候取得参数名字,而且名字可以自己定,如:
参数名uuu(不一定跟变量名一样用userName),
required=false,表示可以不传,默认是true,如果不用@RequestParam注解,而依赖LocalVariableTable属性直接写参数,则默认为false(但如果形参是基本数据类型,则必须传值)。
设置为true时不传会报错
设置为false时对象类型不传默认为null,Boolean包装类型默认为false,基本数据类型(int、long等)即使设置成false也不能不传,会报错,因此最好用基本数据类型的包装类型。
defaultValue="999",表示如果不传,默认值是999.
页面:
访问测试1:
有参数时取参数,没有参数时用默认的999
测试2:required默认为true,不传参数时会报错。
测试3:required设置为false时,参数是int型,不传参数时也会报错
多个同名参数处理
访问:
http://localhost/ccc/hello3?userName=555&userName=666
对于名字相同的参数(表单提交时为name指定),传统方式是用request.getParameterValues获取到数组,用数组方式处理。
springmvc会自动处理为逗号分隔的字符串,但如果参数值中本来就有逗号,就会引起混乱。
推荐方式使用数组或者List接收。
如:
用List接收时必须加@RequestParam,否则会报错
@PathVariable取路径中的参数值(URI模板变量值)
如:
打印:

@CookieValue绑定cookie值
如:
value:cookie的key
require:是否必须存在该key,默认true。
defaultValue:不存在该key时,取默认值
@RequestHeader绑定请求头数据
同样有value、required、defaultValue,含义同上。
如:
@ModelAttribute绑定视图层对象
1、方法参数上的@ModelAttribute
@ModelAttribute在方法参数上时自动加到model中(默认也开启@ModelAttribute,所以可以不加这个注解),不过@ModelAttribute可以通过value设置对象的key,如:
设置成了uuu
页面也用uuu取
访问测试效果:
注意:不加@ModelAttribute注解时,绑定model里时,key是类名的首字母小写,而不是变量名字,如:
此时页面需用user取,而不是user1取
2、普通方法上的@ModelAttribute
@ModelAttribute在普通方法声明上时,执行controller的任何一个方法都会先执行这个方法,并且会将返回值放入ModelMap中,如:
@ModelAttribute的方法可以接收参数,如:
3、@RequestMapping方法上的@ModelAttribute
@ModelAttribute在有@RequestMapping方法声明上时,返回值会自动加入Model(默认也开启@ModelAttribute,可以不加这个注解),但此时返回值已经不能指明jsp路径了,会自动根据访问路径找jsp(这样不太灵活),如:
3、@ModelAttribute匿名时的默认key和默认绑定
普通对象类型默认key为类名首字母小写,List类型默认key为类名List,如:
访问:
默认String类型的参数是不会自动绑定到Model的,其他的对象或者List等类型可以。方法参数为List时,不能用List接口,而只能用ArrayList等实现类。但方法声明上绑定Model则可以用List接口。
maven依赖关系及mybatis的集成
从依赖关系可以看出spring-webmvc依赖了spring-beans、spring-context、spring-core,因此后面3个其实可以不用配置到maven了。
集成mybatis时,所需的必要依赖如下:
<!-- spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.4</version>
</dependency>
<!-- mysql-jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!-- spring数据源(可以替换为druid、c3p0、dbcp等其他的数据源) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- servlet api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
<version>2.5</version>
</dependency>
一般还会添加一些常用的如:
<!-- jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.33</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
原理分析
配置
web.xml配置一个DisptchServlet,它实现了HttpServlet。
继承结构图:
HttpServletBean的init方法会去加载spring的bean,默认找配置文件的路径是WEB-INF/[servlet名字]-servlet.xml"。
也可以通过contextConfigLocation参数指定文件路径,如:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:aaa.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
使用listener也可以配置spring加载的xml文件,如:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:aaa.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
不过一般只用servlet的配置就够了
加载的bean对象放到了XmlWebApplicationContext中。
总体设计
servlet中使用mvc一般想法是在servlet中创建一个map成员变量,key是url路径,value是controller实例,servlet的init执行时加载controller的配置文件,将配置的controller创建实体对象存放到这个map中,servlet的doPost和doGet等方法调用时通过url路径从map中get取出controller实体,执行controller的方法。
springmvc也是采用类似的思路,只是多了一层HandlerMapping封装,HandlerMapping对象内部有一个handlerMap,key是url路径,value即是controller对象,跟上面方案中的map一致,HandlerMapping作为Servlet的成员变量。servlet执行init时也是加载controller配置文件,生成controller实体,放到handlerMap中,再把handlerMap赋值给HandlerMapping成员变量,最后把HandlerMapping赋值给DispatcherServlet的成员变量
请求到来时,根据url路径去DispatcherServlet的HandlerMapping的handlerMap中去get出controller来执行里面的方法。
springmvc中的controller、HandlerMapping等都是作为bean管理。controller对象放到了HandlerMapping的成员变量handlerMap里面。请求来的时候会依次到每个HandlerMapping的handlerMap里面去找controller(根据path找),最先找到的就返回了。(HandlerMapping对象一共只有3个到4个,在配置controller的时候可以配置将它放到哪个HandlerMapping里,不配置也会使用默认的,如xml配置的controller会默认使用BeanNameUrlHandlerMapping,注解方式配置的controller则默认使用RequestMappingHandlerMapping)
HandlerMapping实现了ApplicationContextAware接口,在其HandlerMapping这个bean的回调方法中,它会通过配置或注解找到url和controller的bean的对应关系(会根据配置的beanName调用beanFactory.getBean(beanName)),将其put到HandlerMap里面。
DispatcherServlet中的HandlerMappings:
HandlerMapping中的handlerMap
初始化流程
1、ContextLoaderListener加载spring配置,创建WebApplicationContext,把它放到ServletContext里
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:server_spring.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
如配置了spring的ContextLoaderListener,则它会在容器初始化完成后调用,它的主要作用是创建WebApplicationContext,把它放到ServletContext里,传给servlet,让servlet把它当做父容器,让所有servlet都可以使用里面的bean。
ContextLoaderListener实现contextInitialized方法:
其中的ApplicationContext默认是使用XmlWebApplicationContext,可以在<context-param>标签中配置成其他的。
默认的WebApplicationContext配置在ContextLoader.properties中
2、HttpServletBean的init()方法加载servlet的spring参数配置
DispatcherServlet的继承关系:
因此首先调用HttpServletBean的init()方法。
它默认会去读取WEB-INF/[servlet名字]-servlet.xml中的spring beans配置。将其放到spring的一个bean里面,后面通过这个bean就能取出配置。
3、FrameworkServlet实现initServletBean()方法
创建或获取WebApplicationContext(XmlWebApplicationContext)的BeanFactory用于创建bean,此时会判断有没有ContextLoaderListener的listener加载的容器,如果有,则会将它作为父容器(双亲父容器)。getBean时会采用类似双亲委派机制,先到父容器中找bean。
最后将创建好的BeanFactory也就是XmlWebApplicationContext放到DispatcherServlet的成员变量中。以便后面HandlerMapping初始化时去getBean(也就是Controller的Bean)
然后调用onRefresh方法。
4、DispatcherServlet实现onRefresh方法,创建beans,初始化DispatcherServlet使用的策略的组件(这些组件是DispatcherServlet的成员变量)。
其中核心组件是HandlerMapping、HandlerAdapters、ViewResolvers三个。
4.1、创建HandlerMapping
HandlerMapping一般会有多个,每个HandlerMapping的handlerMap成员变量中存放若干url和controller的对应关系。根据url查找controller的时候会依次到每个HandlerMapping的handlerMap成员变量中去get,最先被找到的controller返回使用。
常用的3个HandlerMapping实现类:SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping、RequestMappingHandlerMapping三个,在controller的bean配置文件中可以指定该controller放到哪个HandlerMapping中去,默认是放到BeanNameUrlHandlerMapping,用注解配置的Controller默认放到RequestMappingHandlerMapping中。
由于HandlerMapping中的HandlerMap需要保存url和handler的映射关系,因此HandlerMapping对象创建后,需要为HandlerMap设置值。而这是通过Ware接口和BeanPostProsser机制实现的。
每个HandlerMapping主要有2步工作:第一是将配置文件中的controller实例化成bean,放到handlerMap成员变量中去。第二是将HandlerMapping赋值给DispatcherServlet的成员变量。
ps:HandlerMapping的设计:
HandlerMapping只有一个方法
HandlerExecutionChain getHandler(HttpServletRequest request);
这个方法就是根据url查找对应的controller,不过返回一个HandlerExecutionChain对象,可以将HandlerExecutionChain理解成对Handler。也就是对Controller的一层包装,HandlerExecutionChain本身没有存在handlerMap里面,而是每次调用getHandler的时候临时包装称HandlerExecutionChain。
HandlerExecutionChain里面除了有Handler,还有一个inteceptor的数组。
3.1.1、实例化controller的bean放到handlerMap(注册handler)
ps:Ware接口的回调:
class ApplicationContextAwareProcessor implements BeanPostProcessor
实现了BeanPostProcessor接口,而这个类是springmvc默认会创建的,因此在创建每个bean对象的时候,它的postProcessBeforeInitialization方法必然被调用,它会调用
ApplicationContextAwareProcessor这个方法,如果目标bean实现了Ware接口,它会根据不同的子接口去回调不同的方法,如目标bean实现了ApplicationContextAware子接口,则会回调setApplicationContext方法。
因此实现Ware的各个子接口则可以让bean在创建时执行指定的回调代码。
SimpleUrlHandlerMapping实现了ApplicationContextAware接口,因此会在创建SimpleUrlHandlerMapping的时候自动调用setApplicationContext方法。
继承结构:
在ApplicationObjectSupport中实现了setApplicationContext,它调用了initApplicationContext方法,这个方法在AbstractHandlerMapping和SimpleUrlHandlerMapping中都有实现。
最后是交给registerHandlers去注册controller,将其放到handlerMap中
3.1.2、将handlerMapping赋值给DispatcherServlet的成员变量。
前面一个步骤都是在spring创建HandlerMapping实例对象的时候触发,HandlerMapping实例创建好以后,在handlerMap就已经有了controller,此时再把handlerMapping赋值给DispatcherServlet的成员变量即可。
问题1:urlMap什么时候put到controller中去?
这里以BeanNameUrlHandlerMapping为例,它在spring中的一个配置示例如下:
可以看出其实url和bean的对应关系全都在配置里面了,并且注入到了urlMap里,因此在genBean的时候就会自动注入进去。使用其他的HandlerMapping也是类似,如使用的注解方式,也是在配置(注解)中就完全定义好了url的bean的对应关系。
问题2:
class ApplicationContextAwareProcessor implements BeanPostProcessor
中的ApplicationContextAwareProcessor对象是否是必然被创建,默认就有这个BeanPostProcessor吗?
参见springioc。
4.2、创建HandlerAdapter
HandlerAdapter的创建与HandlerMapping的创建类似,也是先到bean工厂中去找,找到则使用,没有找到则使用DispatcherServlet.properties配置文件中默认的。
HandlerAdapter有2个重要方法:
boolean supports(Object handler):判断某否handler是否用该HandlerAdapter
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler):
HandlerAdapter的几个实现:
SimpleServletHandlerAdapter:
Handler是一个Servlet,请求交给servlet处理,返回的ModelAndView为null。
SimpleControllerHandlerAdapter:
请求交给Controller处理,返回ModelAndView对象。
HttpRequestHandlerAdapter:
请求交给HttpRequestHandler处理,返回的ModelAndView对象为null。
AbstractHandlerMethodAdapter(RequestMappingHandlerAdapter)
请求交给了HandlerMethod处理,最后调用了RequestMappingHandlerAdapter里面的handleInternal方法,返回ModelAndView对象。
平时用的@Controller注解就是使用的这个HandlerAdapter
RequestMappingHandlerAdapter跟其他几个HandlerAdapter的回调有所区别,它不是回调一个特定的方法,而是根据url,动态反射回调相应方法,极大增加了灵活性。
请求处理流程
1、调用DispacherServlet的doDispatch方法,请求最后调用到了doDispatch方法处理请求。该方法负责整个请求的处理:查找handlerChain,调用拦截器,handler的处理,视图渲染等工作。
2、根据url获取对应的HandlerExecutionChain(含Handler),然后根据handler找到HandlerAdapter.
查找HandlerAdapter时会调用它的supports方法,它会根据Handler是什么类而返回对应的HandlerAdapter。
3、调用HandlerExecutionChain的拦截器方法和handler的handle方法处理请求。
handle方法会返回ModelAndView类型。
4、调用render方法渲染。
遍历在spring中配置的viewResolver列表(一般只配置一个),找到以后,就调用它的resolveViewName去生成view。
如:
在ViewResolver都配置有实现View接口的类,如这里是JstlView,因此生成什么样的View,是ViewResolver决定的。
最后调用view的render方法渲染
view渲染:
view最后调用了renderMergedOutputModel渲染输出,该方法有众多实现,如Excel、pdf、json等。
以JstlView为例:
ModelAndView结构
ModelAndView由Object 类型的view和ModelMap类型的model构成。
model:
ModelMap继承了LinkedHashMap,用于存储键值对,扩展一些方法,如:
addAttribute(Object attributeValue)
addAllAttributes(Collection<?> attributeValues)
addAllAttributes(Map<String, ?> attributes)
mergeAttributes(Map<String, ?> attributes)
等方法。
其中addAttribute(Object attributeValue)、addAllAttributes(Collection<?> attributeValues)这两个方法存储时key是它的值对应的类名字。
view:
view可以是String或者View类型(使用RequestMappingHandlerAdapter时是String),为String时表示view的名字,为View时就是实际的View。
View是一个接口,它有一个render方法用于渲染页面等。
第一个参数通常是一个ModelMap,里面存的是给前端渲染的键值对。
浙公网安备 33010602011771号