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注解方法

实验代码:

  1. 页面链接:

    <a href="handle01">handle01</a>
    
  2. 控制器方法:(存在异常)

    @Controller
    public class ExceptionTestController {
          
          
    	@RequestMapping("handle01")
    	public String handle01(){
          
          
    		//控制器方法中产生一个异常
    		System.out.println(10/0);
    		return "success";
    	}
    }
    
  3. 在控制器中增加处理异常的方法(处理异常,跳转到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 枚举类型源码。

实验代码:

  1. 页面链接

    <a href="handle02?username=123">handle02</a><br/>
    
  2. 自定义异常类

    /**
     * 只有局部全局都不能解决才会使用这个
     */
    @ResponseStatus(reason="用户被拒绝登录",value=HttpStatus.NOT_EXTENDED)
    public class UserNameNotFoundException extends RuntimeException {
          
          
    
    	private static final long serialVersionUID = -42863972324503L;
    	
    }
    
  3. 控制器方法

    @RequestMapping("/handle02")
    public String handle02(@RequestParam("username")String username){
          
          
    	if(!"admin".equals(username)){
          
          
    		System.out.println("登录失败");
    		throw new UserNameNotFoundException();
    	}
    	System.out.println("登录成功");
    	return "success";
    }
    
  4. 出现的错误消息

    没使用注解时:@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,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
  • 通过配置的方式进行异常处理

实验代码:

  1. 页面链接

    <a href="handle04">handle04</a><br/>
    
  2. 增加控制器方法(方法中包含空指针异常)

    @RequestMapping("/handle04")
    public String handle04(){
          
          
    	Object obj = null;
    	System.out.println(obj.toString());
    	return "success";
    }
    
  3. 配置异常解析器:自动将异常对象信息,存放到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>
    
  4. 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子容器具体属性

  1. 为什么不全部都在Spring中扫描
      因为处理器映射器只会去SpringMVC中查找到Controller,如果没有,就找不到,不会去Spring中找,这就决定了,Controller必须在SpringMVC中扫描。
  2. 为什么不全部在SpringMVC中扫描
      在SSM整合或者Spring+SpringMVC+JdbcTemplate中,可以全部在SpringMVC中扫描,但是,在SSH整合中,这种方式不允许。

在这里插入图片描述

3.2 Spring整合SpringMVC的解决方案

  1. 监听器和前端控制器配置

    <!-- 这里配置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>
    
  2. 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>
    
  3. 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 子节点来规定只能扫描的注解

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44630656/article/details/115278032