SpringMVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet是SpringMVC的总指挥,它负责截获请求并将其分配给其相应的处理器处理。SpringMVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理以及表单标签绑定等内容。
体系结构
SpringMVC是基于model2实现的技术框架,model2是经典的MVC(model、view、control)模型在Web应用中的变体,这个改变主要源于HTTP协议的无状态性。model2的目的和MVC一样,也是利用处理器分离模型、视图和控制,达到不同技术层级间松散层耦合的效果,提高系统灵活性、复用性和可维护性。
从接收请求到返回响应,SpringMVC框架中各个组件相互配合,各司其职。在整个框架中,DispatcherServlet处于核心位置,它负责协调和组织不同组件来完成请求处理并返回响应的工作。SpringMVC通过一个前端Servlet接收所有的请求,并将具体工作委托给其他组件进行处理,DispatcherServlet就是SpringMVC的前端Servlet。
- 从客户端发送一个HTTP请求开始,Web应用服务器接收到这个请求,如果匹配DispatcherServlet的请求映射路径(在web.xml指定),Web容器将该请求转交给DispatcherServlet处理。
- DispatcherServlet接收到请求后,将根据请求的信息(URL、HTTP方法、请求报文头、请求参数、Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。
- 当DispatcherServlet根据HandlerMapping得到对应当前请求的控制器后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。HandlerAdapter是SpringMVC的框架级接口,HandlerAdapter是一个适配器,它用统一的接口对各种Handler方法进行调用。
- 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息。
- ModelAndView中包含的是“逻辑视图名”而非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
- 当得到真实视图对象View后,DispatcherServlet就是用这个View对象对ModelAndView中的模型数据进行视图渲染。
- 最终客户端得到响应信息,可能是一个普通的HTML页面,也可能是一个XML或JSON串,甚至是一张图片或一个PDF文档等不同的媒体形式。
SpringMVC简单例子
SpringMVC应用一般包括以下步骤:
- 配置web.xml,指定业务层对应的Spring配置文件,定义DispatcherServlet;
- 编写处理请求的控制器(处理器);
- 编写视图对象(JSP等视图对象);
- 配置SpringMVC的配置文件,使控制器、视图解析器等生效。
配置XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name></display-name>
<!-- ①从类路径下加载Spring配置文件,classpath关键字特指类路径下加载 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!-- 负责启动Spring容器的监听器,它将引用①出的上下文参数获得Spring配置文件地址 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- Spring MVC的主控Servlet -->
<servlet>
<servlet-name>demo</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Spring MVC处理的URL -->
<servlet-mapping>
<servlet-name>demo</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在XML中声明了一个Servlet,SpringMVC也拥有一个Spring配置文件,该配置文件的文件名和此处定义的Servlet名有一个契约:即采用<Servlet 名>-servlet.xml的形式。在这里,Servlet名为demo,则在/WEB-INF目录下必须提供一个demo-servlet.xml的SpirngMVC配置文件,但这个配置文件无须通过web.xml的ContextLoaderListener上下文参数进行声明,因为SpringMVC的Servlet会自动将demo-servlet.xml和Spring其他配置文件进行拼装。
与此同时,对这个Servlet的URL路径映射进行定义,在这里让所有以.html为后缀的URL都被demo Servlet截获,进而转由SpringMVC框架进行处理。
请求被SpringMVC截获后,首先根据请求的URL查找到目标的处理控制器,并将请求参数封装成一个对象一起传给控制器处理,控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。
编写控制器:
.....
@Controller//标注称为一个Spring MVC的Controller
public class LoginController {
@Autowired
private UserService userService;
//负责处理index.html的请求
@RequestMapping(value="/index.html")
public String loginPage(){
return "login";
}
//负责处理loginCheck.html的请求
@RequestMapping(value="/loginCheck.html")
public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(), loginCommand.getPassword());
if(!isValidUser){
return new ModelAndView("login","error","用户名或密码错误。");
}else{
User user = userService.findUserByUserName(loginCommand.getUserName());
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
}
通过SpringMVC的@Controller注解可以将任何一个POJO的类标注为SpringMVC的控制器,处理HTTP的请求。当然标注了@Controller的类首先会是一个Bean,所以我们可以使用@Autowired进行Bean的注入。
一个控制器可以拥有多个对应不同的HTTP请求路径的处理方法,通过@RequestMapping指定方法如何映射请求路径。
请求的参数会根据参数名称默认契约自动绑定到响应方法的入参中,在loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按名称匹配绑定到loginCommand的入参中。
请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,SpringMVC会解析之并转向目标响应页面。ModelAndView对象既包括了视图信息又包括了视图渲染所需的模型数据信息。控制器根据登录处理结果分别返回ModelAndView(“login”,“error”,“用户名或密码错误。”)和ModelAndView(“main”)。ModelAndView的第一个参数代表视图的逻辑名,第二和第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中。
编写视图对象:
<%@ 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>登录首页</title>
</head>
<body>
<c:if test="${!empty error}">
<font color="red"><c:out value="${error}"/></font>
</c:if>
<form action="<c:url value="/loginCheck.html"/>" method="post">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录"/>
<input type="reset" value="重置"/>
</form>
</body>
</html>
login.jsp页面有两个用处,既作为登录页面又作为登录失败后的响应页面。所以定义了<c:if test="${!empty error}">,使用JSTL标签将登录错误返回的信息进行处理。JSTL标签中引用了error变量,这变量正是LoginController中返回的ModelAndView(“login”,“error”,“用户名或密码错误。”)对象所声明的error参数。其中,userName和password应该与实体类LoginCommand对应参数,这样才可以将请求参数按匹配入参。
login.jsp的登录表单提交到loginCheck.html,<c:url value="/loginCheck.html"/>的JSTL标签会在URL前自动加上应用程序部署根目录。
由于login.jsp放置在WEB-INF/jsp目录下,无法直接通过URL进行调用,它由LoginController控制类中标注了@RequestMapping(value="/index.html")的loginPage()进行转发。
配置SpringMVC文件:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引用Spring的多个Schema空间的格式定义文件 -->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 扫描web包,应用Spring的注解 -->
<context:component-scan base-package="web"/>
<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
</beans>
我们需要在demo-servlet.xml中声明该控制器,扫描Web路径,指定SpringMVC的视图解析器。通过InternalResourceViewResolver为视图逻辑名添加前后缀的方式进行解析。如视图逻辑名为“login”将解析为/WEB-INF/jsp/login.jsp,名为“main”的视图解析为/WEB-INF/jsp/main.jsp。
整个例子实现的过程:
- DispatcherServlet接收到客户端的/loginCheck.html请求;
- DispatcherServlet使用DefaultAnnotationHandlerMapping查找负责处理该请求的控制器为“/loginCheck.html”;
- DispatcherServlet将请求分发给名为“/loginCheck.html”的LoginController处理器;
- 处理器完成业务处理后,返回ModelAndView对象,其中View的逻辑名为“main”或“login”;
- DispatcherServlet调用InternalResourceViewResolver组件对ModelAndView中的逻辑视图名进行解析,得到真实的“/WEB-INF/jsp/login.jsp”或“/WEB-INF/jsp/main.jsp”视图对象;
- DispatcherServlet使用真实的视图对象对模型中的数据信息进行渲染;
- 返回响应页面给客户端。