SpringMVC——异常处理、SpringMVC运行流程、Spring与SpringMVC的整合
一、异常处理
1.1 异常处理概述
- Spring MVC 通过
HandlerExceptionResolver
处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。- SpringMVC 提供的 HandlerExceptionResolver 的实现类
DispatcherServlet 默认装配的 HandlerExceptionResolver :
- 没有使用 <mvc:annotation-driven/> 配置:
- 使用了 <mvc:annotation-driven/> 配置:
1.2 异常处理之ExceptionHandlerExceptionResolver
- 主要处理 Handler 中用 @ExceptionHandler 注解定义的方法。
- @ExceptionHandler 注解定义的方法优先级问题:例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法
- ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,会找 @ControllerAdvice中的@ExceptionHandler注解方法
实验代码:
-
页面链接:
<a href="handle01">handle01</a>
-
控制器方法:(存在异常)
@Controller public class ExceptionTestController { @RequestMapping("handle01") public String handle01(){ //控制器方法中产生一个异常 System.out.println(10/0); return "success"; } }
-
在控制器中增加处理异常的方法(处理异常,跳转到error.jsp)
/** * 在异常抛出的时候,Controller会使用@ExceptionHandler注解的方法去处理异常 * 1、value用来指明处理哪些异常 * 2、给方法上随便写一个Exception,用来接受发生的异常 */ @ExceptionHandler(value={ ArithmeticException.class}) public String handleException01(Exception exception){ System.out.println("进行异常处理..."); System.out.println("打印异常信息:"+exception); return "error";//返回到错误页面 }
如果需要将异常对象从控制器携带给页面,做异常信息的获取:
/** * 在异常抛出的时候,Controller会使用@ExceptionHandler注解的方法去处理异常 * 1、value用来指明处理哪些异常 * 2、给方法上随便写一个Exception,用来接受发生的异常 * 2、要携带异常信息不能给参数位置写Model * 3、返回错误页面,也可以返回ModelAndView(可以将异常信息携带到页面) */ @ExceptionHandler(value={ ArithmeticException.class}) public ModelAndView handleException01(Exception exception){ System.out.println("进行异常处理..."); ModelAndView view = new ModelAndView("error"); view.addObject("ex", exception); return view; }
@ExceptionHandler全局处理异常:
/**
* 集中处理所有异常:
*
* @ControllerAdvice标明专门处理异常的类,将其加入到ioc容器中
*/
@ControllerAdvice
public class MyJiZhongException {
@ExceptionHandler(value={
ArithmeticException.class})
public ModelAndView handleException01(Exception exception){
System.out.println("集中处理异常的类进行异常处理..."+exception);
ModelAndView view = new ModelAndView("error");
view.addObject("ex", exception);
return view;
}
}
总结:
- Controller会使用
@ExceptionHandler
注解的方法去处理异常,value属性用来指明处理哪些异常- 在给方法上加上Exception形参,用来接受发生的异常
- 参数位置不能写Model,如果需要将异常对象从控制器携带给页面,返回
ModelAndView
@ControllerAdvice
标明专门处理异常的类,将其加入到ioc容器中- 如果有多个@ExceptionHandler都能处理这个异常,
精确优先
- 全局异常处理与本类同时存在,
本类优先
1.3 异常处理之ResponseStatusExceptionResolver
- 在异常及异常父类中找到 @ResponseStatus 注解,然后使用这个注解的属性进行处理。
- 定义一个 @ResponseStatus 注解修饰的异常类
- 若在处理器方法中抛出了上述异常:若ExceptionHandlerExceptionResolver 不解析上述异常。由于触发的异常 UnauthorizedException 带有@ResponseStatus 注解。因此会被ResponseStatusExceptionResolver 解析到。最后响应HttpStatus.UNAUTHORIZED 代码给客户端。HttpStatus.UNAUTHORIZED 代表响应码401,无权限。 关于其他的响应码请参考 HttpStatus 枚举类型源码。
实验代码:
-
页面链接
<a href="handle02?username=123">handle02</a><br/>
-
自定义异常类
/** * 只有局部全局都不能解决才会使用这个 */ @ResponseStatus(reason="用户被拒绝登录",value=HttpStatus.NOT_EXTENDED) public class UserNameNotFoundException extends RuntimeException { private static final long serialVersionUID = -42863972324503L; }
-
控制器方法
@RequestMapping("/handle02") public String handle02(@RequestParam("username")String username){ if(!"admin".equals(username)){ System.out.println("登录失败"); throw new UserNameNotFoundException(); } System.out.println("登录成功"); return "success"; }
-
出现的错误消息
没使用注解时:@ResponseStatus(reason=“用户被拒绝登录”,value=HttpStatus.NOT_EXTENDED)
使用注解时:@ResponseStatus(reason=“用户被拒绝登录”,value=HttpStatus.NOT_EXTENDED)
1.4 异常处理之DefaultHandlerExceptionResolver
对一些特殊的异常进行处理,比如:
- NoSuchRequestHandlingMethodException、
- HttpRequestMethodNotSupportedException、
- HttpMediaTypeNotSupportedException、
- HttpMediaTypeNotAcceptableException等。
当前两个异常异常解析器没有解析时,会轮到DefaultHandlerExceptionResolver进行解析,只能接卸Spring自己的异常。例如HttpRequestMethodNotSupportedException异常。
1.5 异常处理之SimpleMappingExceptionResolver
- 如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
- 通过配置的方式进行异常处理
实验代码:
-
页面链接
<a href="handle04">handle04</a><br/>
-
增加控制器方法(方法中包含空指针异常)
@RequestMapping("/handle04") public String handle04(){ Object obj = null; System.out.println(obj.toString()); return "success"; }
-
配置异常解析器:自动将异常对象信息,存放到request范围内
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- exceptionMappings:配置哪些异常去哪些页面 --> <property name="exceptionMappings"> <props> <!-- key:异常全类名;value:要去的页面视图名 --> <prop key="java.lang.NullPointerException">error</prop> </props> </property> <!-- exceptionAttribute指定错误信息取出时使用的key。默认key为exception(通过ModelAndView传递给页面) --> <property name="exceptionAttribute" value="ex"></property> </bean>
-
error.jsp页面获取错误信息
<body> <h1>错误页面</h1> 异常信息:${requestScope.ex } </body>
二、SpringMVC运行流程
1、所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
2、根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)
3、根据当前处理器找到他的HandlerAdapter(适配器)
4、拦截器的preHandle先执行
5、适配器执行目标方法,并返回ModelAndView
1)、ModelAttribute注解标注的方法提前运行
2)、执行目标方法的时候(确定目标方法用的参数)
1)、有注解
2)、没注解:
1)、 看是否Model、Map以及其他的
2)、如果是自定义类型
1)、从隐含模型中看有没有,如果有就从隐含模型中拿
2)、如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常
3)、都不是,就利用反射创建对象
6、拦截器的postHandle执行
7、处理结果;(页面渲染流程)
1)、如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
2)、调用render进行页面渲染
1)、视图解析器根据视图名得到视图对象
2)、视图对象调用render方法;
3)、执行拦截器的afterCompletion;
三、Spring与SpringMVC的整合
3.1 Spring容器和SpringMVC容器的关系
实际上SpringMVC就运行在Spring环境之下,还有必要整合么?
通常情况下,类似于数据源,事务,整合其他框架都是放在spring的配置文件中(而不是放在SpringMVC的配置文件中),实际上放入Spring配置文件对应的IOC容器中的还有Service和Dao.而SpringMVC也搞自己的一个IOC容器,在SpringMVC的容器中只配置自己的Handler(Controller)信息。所以,两者的整合是十分有必要的,SpringMVC负责接受页面发送来的请求,Spring框架则负责整理中间需求逻辑,对数据库发送操作请求,对数据库的操作目前则先使用Spring框架中的JdbcTemplate进行处理。
Spring容器和SpringMVC容器的关系:
Spring容器是一个父容器,SpringMVC容器是一个子容器,它继承自Spring容器。因此,在SpringMVC容器中,可以访问到Spring容器中定义的Bean,而在Spring容器中,无法访问SpringMVC容器中定义的Bean。在Web开发中,Controller全部在SpringMVC中扫描,除了Controller之外的Bean,全部在Spring容器中扫描(Service、Dao),按这种方式扫描,扫描完完成后,Controller可以访问到Service。
即:SpringMVC子容器继承Spring父容器,所以SpringMVC子容器可以拿Spring父容器的属性,而Spring父容器没办法拿SpringMVC子容器具体属性
- 为什么不全部都在Spring中扫描
因为处理器映射器只会去SpringMVC中查找到Controller,如果没有,就找不到,不会去Spring中找,这就决定了,Controller必须在SpringMVC中扫描。- 为什么不全部在SpringMVC中扫描
在SSM整合或者Spring+SpringMVC+JdbcTemplate中,可以全部在SpringMVC中扫描,但是,在SSH整合中,这种方式不允许。
3.2 Spring整合SpringMVC的解决方案
-
监听器和前端控制器配置
<!-- 这里配置Spring配置文件的位置,param-name是固定的, param-value是文件位置 这个配置可以省略,如果省略, 系统默认去/WEB-INF/目录下查找applicationContext.xml作为Spring的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置前端控制器 --> <!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等) 如果不配置contextConfigLocation, 默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml) --> <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:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
Spring配置文件
<!-- 指定扫描时排除Controller注解的组件 --> <context:component-scan base-package="com.zb"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
-
SpringMVC配置文件
<!-- 指定只扫描包含Controller注解的组件 --> <context:component-scan base-package="com.zb" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
问题:
若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分, 就会导致有的 bean 会被创建 2 次.
解决:
使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分.
使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解