深入理解Spring MVC及其组件
spring mvc 设计思想与体系结构组成
servlet 与jsp 执行过程
spring mvc 执行流程
spring mvc 体系结构
Spring MVC案例演示
mvc 执行流程解析
mvc各组件执行流程
HandlerMapping 详解
演示基于 BeanNameUrlHandlerMapping 配置映谢。
HandlerAdapter详解
演示基于Servlet 处理 SimpleServletHandlerAdapter
ViewResolver 与View 详解
HandlerExceptionResolver详解
HandlerInterceptor 详解
注解配置
案例演示
为什么基于 配置就能实现mvc 的整个配置了,之前所提到的 handlerMapping 、与handlerAdapter 组件都不使用了?
spring mvc 设计思想与体系结构组成
servlet 与jsp 执行过程

处理流程说明
1.请求Servlet
2.处理业务逻辑
3.设置业务Model
4.forward jsp Servlet
5和6. jsp Servlet 解析封装html 返回
注:这是一个典型的MVC场景,M是model数据,V是jsp视图,C是Servlet
spring mvc 执行流程
spring mvc本质上还是在使用Servlet处理,并在其基础上进行了封装简化了开发流程,提高易用性、并使用程序逻辑结构变得更清晰。主要体现在一下几点:
a.基于注解的URL映谢
b.http表单参数转换
c.全局统一异常处理
d.拦截器的实现

spring mvc 体系结构
HandlerMapping
url与控制器的映谢HandlerAdapter
控制器执行适配器ViewResolver
视图仓库view
具体解析视图HandlerExceptionResolver
异常捕捕捉器HandlerInterceptor
拦截器

Spring MVC案例演示
1.新建模块,maven项目的web工程,

目录结构如下:
并在pom文件中添加如下依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- jstl依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2.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" xmlns:web="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_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>springMVC</display-name>
<!-- 部署 DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring-mvc.xml</param-value>
</init-param>
<!-- 表示容器再启动时立即加载servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 处理所有URL -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--注解配置需要配这两项,不使用注解可不配-->
<context:component-scan base-package="com.example.mvc.controller" >
<!--注解配置需要配这两项,不使用注解可不配-->
<!-- 注解驱动 -->
<mvc:annotation-driven/>
<bean id="simple" class="com.example.mvc.controller.SimpleController"/>
<!--<bean id="/luban.do" class="com.example.mvc.controller.BeanNameController"/>-->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<props>
<prop key="/hello.do">simple</prop>
</props>
</property>
<property name="interceptors">
<bean class="com.example.mvc.interceptor.TestInterceptor"/>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
<bean class="com.example.mvc.exception.SimpleExceptionHandler"/>
</beans>
4.新建SimpleController.java
package com.example.mvc.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv = new ModelAndView("userView");
mv.addObject("name","hhh");
return mv;
}
}
5.配置拦截器
package com.example.mvc.interceptor;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("pre=========");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle=========");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterCompletion=========");
}
}
6.userView.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>
测试
${name}
</body>
</html>
7.查看运行结果


8.配置异常HandlerExceptionResolver
package com.example.mvc.exception;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return new ModelAndView("error");
}
}
error.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>
异常页面
</body>
</html>
package com.example.mvc.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv = new ModelAndView("userView");
mv.addObject("name","hhh");
int i = 5/0;
return mv;
}
}
9.查看运行结果


mvc 执行流程解析
mvc各组件执行流程

HandlerMapping 详解
其为mvc 中url路径与Control对像的映射,DispatcherServlet 就是基于此组件来寻找对应的Control,如果找不到就会报Not Found mapping 的异常。
HandlerMapping 接口方法

HandlerMapping 接口结构

目前主流的三种mapping 如下:
BeanNameUrlHandlerMapping: 基于ioc name 中已 “/” 开头的Bean时行 注册至映谢.
SimpleUrlHandlerMapping:基于手动配置 url 与control 映谢
RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映谢
演示基于 BeanNameUrlHandlerMapping 配置映谢。
编写mvc 文件
<bean id="/hello1.do" class="com.example.mvc.controller.BeanNameController"/>
control 控制器
package com.example.mvc.controller;
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class BeanNameController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
httpServletResponse.getWriter().println("hehehe");
}
}
运行结果

HandlerAdapter详解
当IOC 中实例化这些类之后 DispatcherServlet 就会通过org.springframework.web.servlet.DispatcherServlet#getHandler() 方法基于request查找对应Handler。但找到对应的Handler之后我们发现他是一个Object类型,并没有实现特定接口。如何调用Handler呢?
这里spring mvc 采用适配器模式来适配调用指定Handler,根据Handler的不同种类采用不同的Adapter,其Handler与 HandlerAdapter 对应关系如下:
HandlerAdapter 接口方法

HandlerAdapter 接口结构图

演示基于Servlet 处理 SimpleServletHandlerAdapter
编写mvc 文件
<bean id="/hello2.do" class="com.example.mvc.controller.HelloServlet"/>
<bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/>
control 控制器
package com.example.mvc.controller;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("hello nihao ");
}
}
运行结果

ViewResolver 与View 详解
找到应的Adapter 之后就会基于适配器调用业务处理,处理完之后业务方会返回一个ModelAndView ,在去查找对应的视图进行处理。其在org.springframework.web.servlet.DispatcherServlet#resolveViewName() 中遍历 viewResolvers 列表查找,如果找不到就会报一个 Could not resolve view with name 异常。
在下一步就是基于ViewResolver.resolveViewName() 获取对应View来解析生成Html并返回 。对应VIEW结构如下:
HandlerExceptionResolver详解
该组件用于指示 当出现异常时 mvc 该如何处理。dispatcherServlet 会调用org.springframework.web.servlet.DispatcherServlet#processHandlerException() 方法,遍历 handlerExceptionResolvers 处理异常,处理完成之后返回errorView 跳转到异常视图。
HandlerInterceptor 详解
其实现机制是基于 HandlerExecutionChain 分别在 doDispatch 方法中执行以下方法:
preHandle :业务处理前执行
postHandle:业务处理后(异常则不执行)
afterCompletion:视图处理后
注解配置
案例演示
mvc配置文件
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<context:component-scan base-package="com.example.mvc.controller" >
<!-- 注解驱动 -->
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
</beans>
controller配置
package com.example.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class RequestMappingController {
@RequestMapping("/hello3.do")
public ModelAndView hello() {
ModelAndView mv = new ModelAndView("userView");
mv.addObject("name", "hello3");
return mv;
}
}
运行结果

为什么基于 mvc:annotation-driven/ 配置就能实现mvc 的整个配置了,之前所提到的 handlerMapping 、与handlerAdapter 组件都不使用了?
只要查看以类的源就可以知晓其中原因:
认识 NamespaceHandler 接口
查看 MvcNamespaceHandler
查看AnnotationDrivenBeanDefinitionParser
结论:
在<mvc:annotation-driven /> 对应的解析器,自动向ioc 里面注册了两个BeanDefinition。分别是:RequestMappingHandlerMapping与BeanNameUrlHandlerMapping




