spring mvc原理深度解析(二)
spring mvc拦截处理
HandlerExceptionResolver 异常处理
该组件用于表示出现异常时spring mvc 的处理方案。
dispatcherServlet 会调用org.springframework.web.servlet.DispatcherServlet#processHandlerException() 方法,遍历 handlerExceptionResolvers 处理异常,处理完成之后返回errorView 跳转到异常视图
演示自定义异常捕捉
<dependencies>
<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>
</dependencies>
自定义异常处理类,并实现HandlerExceptionResolver 接口
package com.yemuxia.mvc01.controller;
import com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView modelAndView = new ModelAndView("error");
if(ex instanceof IllegalArgumentException){
modelAndView.addObject("errorType","参数非法");
}else{
modelAndView.addObject("errorType",ex.getClass().getSimpleName());
}
modelAndView.addObject("message",ex.getMessage());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ex.printStackTrace(new PrintStream(out,true));
modelAndView.addObject("stack",out.toString());
return modelAndView;
}
}
<?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">
<bean name="/hello.do" class="com.yemuxia.mvc01.controller.SimpleController"/>
<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.yemuxia.mvc01.controller.MyExceptionResolver"/>
</beans>
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>
默认异常-内部错误:<h1>500</h1>
<h3>${message}</h3>
<h5>异常类别:${errorType}</h5>
<pre><code>${stack}</code></pre>
</body>
</html>
package com.yemuxia.mvc01.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 request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("userView");
mv.addObject("name","hhh");
if(request.getParameter("name").length()<2){
throw new IllegalArgumentException("名称不能少于2个字符");
}
int i= 5/0;
return mv;
}
}
异常演示如下:
源码分析
HandlerExceptionResolver 结构
ResponseStatusExceptionResolver(默认):用于解析带@ResponseStatus的自定义异常
DefaultHandlerExceptionResolver(默认):spring mvc 默认异常处理。
SimpleMappingExceptionResolver:异常映射,将指定异常与错误页面相对应
SimpleMappingExceptionResolver案例
<?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">
<bean name="/hello.do" class="com.yemuxia.mvc01.controller.SimpleController"/>
<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="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error"/>
<property name="defaultStatusCode" value="500"/>
<property name="exceptionMappings">
<map>
<entry key="java.lang.RuntimeException" value="error"/>
<entry key="java.lang.IllegalArgumentException" value="argumentError"/>
</map>
</property>
</bean>
</beans>
package com.yemuxia.mvc01.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 request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("userView");
mv.addObject("name","hhh");
if(request.getParameter("name").length()<2){
throw new IllegalArgumentException("名称不能少于2个字符");
}
int i= 5/0;
return mv;
}
}
error.jsp和argumentError.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>500</h1>
<h3>${exception.message}</h3>
</body>
</html>
HandlerInterceptor调用拦截
HandlerInterceptor用于对请求拦截,与原生Filter区别在于 Filter只能在业务执行前拦截,而HandlerInterceptor 可以在业务处理前、中、后进行处理。
案例
拦截器需要配置拦截范围
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.yemuxia.mvc01.controller.SimpleHandlerInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
自定义拦截器
package com.yemuxia.mvc01.controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
运行项目,并调用接口
其实现机制是基于 HandlerExecutionChain 分别在 doDispatch 方法中执行以下方法:
- preHandle :业务处理前执行
- postHandle:业务处理后(异常则不执行)
- afterCompletion:视图处理后
dispatchServlet 初始化流程
1.创建WebApplicationContext
2.基于策略模型加载各组件。
创建WebApplicationContext 源码解析
org.springframework.web.servlet.HttpServletBean#init
org.springframework.web.servlet.FrameworkServlet#initServletBean
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
// 基于当前存在的Spring 上下文做为Root 创建Mvc上下文。
org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext
org.springframework.context.support.AbstractApplicationContext#refresh
基于策略模型加载各组件源码解析
RequestMapping注解的使用与原理
案例
<?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.yemuxia.mvc01.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>
package com.yemuxia.mvc01.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
@RequestMapping("/my/")
public class UserController {
@RequestMapping("hello")
public void hello(HttpServletResponse response) throws IOException {
response.getWriter().println("hello you shi ge hao ren");
}
}
为什么基于 <mvc:annotation-driven/ > 配置就能实现mvc 的整个配置了
告知Spring,我们启用注解驱动,自动向ioc 里面注册了两个BeanDefinition。分别是:RequestMappingHandlerMapping与RequestMappingHandlerAdapter。Spring会自动为我们注册上面说到的几个Bean到工厂中,来处理我们的请求。
RequestMapping实现的原理
- 1.RequestMappingHandlerMapping :URL 映射器
- 2.RequestMappingHandlerAdapter:执行适配器
- 3.InvocableHandlerMethod:Control目标对象,包含了control Bean 及对应的method 对像,及调用方法
- a. HandlerMethodArgumentResolverComposite:参数处理器
- b. ParameterNameDiscoverer:参数名称处理器
- c. HandlerMethodReturnValueHandlerComposite:返回结构处理器
源码解析
查找mapping源码解析
// 基于注解查找 mapping
org.springframework.web.servlet.DispatcherServlet#getHandler
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl
调用执行过程源码解析
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
org.springframework.web.method.support.InvocableHandlerMethod#doInvoke