全栈开发实战 | SpringMVC框架快速入门第三篇

一个人最好的状态:梦想藏在心里,行动落于腿脚。

目录

1、前言

2、异常

2.1 统一异常处理机制

2.2 未捕获异常处理

2.3 RESTful支持

3、拦截器


1、前言

上一篇我们已经学习了SpingMVC框架的Handler处理器(也就是Controller层)开发相关知识,包括参数绑定、数据回显、过滤器、转发重定向、文件上传等等,感兴趣的同学可以点击以下链接参考


全栈开发实战 | SpringMVC框架快速入门第二篇

2、异常

系统中的异常包括编译时异常和运行时异常RuntimeException,前者通过在代码中try/cath捕获异常后获取异常信息,后者则通过规范代码开发、提高代码质量、详细测试通过手段减少运行时异常的发生

在实际项目开发过程中,不管是控制层的处理过程、业务层的处理过程还是数据库层的处理过程,都不可避免会遇到各种可预知的和不可预知的异常需要处理。如果每一层都单独处理异常,代码的耦合度将会增高,并且工作量大不好统一,且维护成本还很高

那么我们能否将各处理层的异常处理统一解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常处理的统一处理和维护,接下来我们就开始学习SpringMVC框架的统一异常处理机制

2.1 统一异常处理机制

系统的Controller、service、dao层出现异常都通过throws Exception向上抛出异常,最后由SpringMVC的前端控制器交给异常处理器统一进行异常处理,我们的SpringMVC框架有且只提供了一个全局异常处理器进行统一异常处理。

SpringMVC框架以下三种方式:

  • Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;

  • 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; 

  • 使用@ExceptionHandler注解实现异常处理; 

第一种:简单异常处理器

SpringMVC框架自带了一个异常处理器叫SimpleMappingExceptionResolver,

该处理器已经实现了异常处理接口HandlerExceptionResolver(第二种就需要实现这个接口开发)

  • 首先我们先自定义一个异常模块

/**
 * 自定义异常处理模块
 */
public class CustomException extends Exception {
    //异常信息
    private String message;

    public CustomException() {
    }

    public CustomException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • 编辑异常处理页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>错误信息是:${ex}</h1>
</body>
</html>
  • 在dispatcher-servlet.xml文件中配置该处理器

 <!--(第一种:自带简单异常处理器),只需要配置好自定义的异常即可-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!--定义默认的异常处理页面-->
        <property name="defaultErrorView" value="error"></property>
        <!--定义异常处理页面用来获取异常信息的变量名,也可不定义,默认名称为Exception-->
        <property name="exceptionAttribute" value="ex"></property>
        <!--定义需要特殊处理的异常-->
        <property name="exceptionMappings">
            <props>
                <prop key="com.cn.exception.CustomException ">error</prop>
            </props>
            <!--还可以定义其他的异常-->
        </property>
    </bean>
  • 编辑测试程序

/**
 *  测试SimpleMappingExceptionResolver
 */
@Controller
public class ExceptionController{

    @RequestMapping("/testException")
    public void testException() throws Exception {

        //模拟抛出自定义异常
        if (true){
            throw new CustomException("自定义异常");
        }
        
        System.out.println("测试统一异常处理机制");
    }
}
  • 页面显示

 总结 : 使用SimpleMappingExceptionResolver进行异常处理,具有集成简单、有良好的扩展性(可以任意增加自定义的异常和异常显示页面)、对已有代码没有入侵性等优点,但该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。

第二种:自定义全局异常处理

  • 编辑HandlerExceptionResolver接口的实现类CustomExceptionResolver

/**
 *  定义统一异常处理器类
 * (第二种:实现HandlerExceptionResolver接口)
 */
public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //输出异常
        ex.printStackTrace();

        String message = null;
        CustomException customException = null;

        //如果异常是系统自定义的异常,直接从异常类中获取异常信息
        if(ex instanceof CustomException){
            customException = (CustomException)ex;
        }else {
            //如果非系统自定义异常,就新建一个异常
            customException = new CustomException("未知异常");
        }

        //读取错误信息
        message = customException.getMessage();

        //向前台返回错误信息
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("message",message);
        modelAndView.setViewName("error");

        return modelAndView;
    }
}

这个实现类必须声明到Spring中,让Spring来管理它,我们可以使用注解方式(@Componment等),或者在配置文件中使用<bean/>节点,下面我采用第二种方式

  • 在dispatcher-servlet.xml文件中配置统一异常处理器

<!--(第二种:实现HandlerExceptionResolver接口),定义全局异常处理类-->
<bean class="com.cn.exception.CustomExceptionResolver"></bean>
  • 编辑异常处理页面

  • 测试、页面显示

  • 模拟抛出非自定义异常

       //自定义异常
//        if (true){
//            throw new CustomException("自定义异常");
//        }
        //非自定义异常
        int a = 1/0;
  • 再次测试、页面显示

总结 : 我们可以看出使用实现HandlerExceptionResolver接口的异常处理器进行统一异常处理,在异常处理时能获取导致出现异常的对象,有利于提供更详细的异常处理信息。 一般用这种自定义的全局异常处理器比较多

第三种:@ExceptionHandler注解实现异常处理

该方法需要我们创建一个BaseController类,并在类中编辑异常处理方法用@ExceptionHandler声明,然后需要异常处理的类只需要继承这个类就可以

  • 编辑BaseController类

/**
 * 第三种:@ExceptionHandler注解实现异常处理
 * 需要异常处理的类只需要继承这个类就可以实现
 */
@Controller
public class BaseController {

    @ExceptionHandler
    public ModelAndView exp(HttpServletRequest request, Exception ex){
    //异常处理方法
    //省略代码,和第二种异常处理类方式相同
    }
}
  • 编辑测试代码


/**
 * 测试统一异常处理机制
 */
@Controller
public class ExceptionController extends BaseController {

    @RequestMapping("/testException")
    public void testException() throws Exception {

        //自定义异常
//        if (true){
//            throw new CustomException("自定义异常");
//        }
        //非自定义异常
        int a = 1/0;

        System.out.println("测试统一异常处理机制");
    }
}
  • 测试、页面显示

 总结 : 使用@ExceptionHandler注解实现异常处理,虽然从整体上来看不需要我们再xml文件中配置任何东西,也不需要使用其他注解交给Spring来管理,但它对代码的侵入性非常大,需要异常处理的类都实现这个BaseController类,所以在日常开发中还是推荐使用第二种方式

2.2 未捕获异常处理

对于Unchecked Exception而言,由于代码不强制捕获,往往被忽略,如果运行期产生了UncheckedException,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的404、500……等服务器内部错误提示页面。


我们需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在Web.xml中通过<error-page>(Websphere/Weblogic)或者<error-code>(Tomcat)节点配置特定异常情况的显示页面。

  • 编辑web.xml文件

<!-- 出错页面定义 -->
<error-page>
    <error-code>404</error-code>
    <location>/error/404.html</location>
</error-page>

还可以继续增加服务器错误处理以及对应的显示页面

2.3 RESTful支持

Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

互联网所有的事物都可以被抽象为资源,每一个URL代表一个资源 

非RESTful的http的URL:http://localhost:8080/item/queryItem?id=1

RESTful的http的URL:http://localhost:8080/item/1

下面我们通过一个RESTful风格的例子来测试一下

  • 编辑测试代码

 /**
     * 测试RESTful风格
     * @param userid
     * @return
     */
    @RequestMapping("/testRESTful/{id}")
    @ResponseBody
    public User testRESTful(@PathVariable("id")Integer userid){
        //service层查询到具体的User信息,此处省略代码
        return user;
    }

@PathVariable是获取URL上数据的,它可以将URL中的占位符参数绑定到处理器类的方法形参中

@ResponseBody是返回json数据,如果不加就走视图解析器,返回页面

上面的只是一个RESTful风格的例子,后续我会单独写一篇文章详细解释RESTful,敬请期待。。。

3、拦截器

SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来判断用户是否登陆,或者是进行权限验证等

用户请求到前端控制器,前端控制器会调用处理器映射器去查找处理器,处理器映射器会返回一个拦截链,SpringMVC中的拦截器就是通过处理器映射器发起的,通常我们定义拦截器都需要实现HandlerInterceptor接口来实现

  • 定义HandlerInterceptor1拦截器

/**
 * 1号拦截器
 */
public class HandlerInterceptor1 implements HandlerInterceptor {
    
    //业务处理之前调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("1-preHandle()");
        //如果返回false,表示拦截,不继续处理handler
        //如果返回true,表示放行,继续处理后续的postHandle()和afterCompletion()方法
        return false;
    }

    //业务返回ModelAndView之前调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("1-postHandle()");
    }
    
    //业务处理完成后调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("1-afterCompletion()");
    }
}

接口中有三个方法需要我们去实现

  • preHandle()

    • 该方法在请求处理之前调用,在一个项目中可能存在多个拦截器,执行顺序就是它们的声明顺序,而且最先执行的就是preHandle()方法,所以一般我们会在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在方法中进行判断请求是否要执行下去

    • 如果返回false,表示拦截,不继续处理handler

    • 如果返回true,表示放行,继续处理后续的postHandle()和afterCompletion()方法

  • postHandle()

    • 该方法在当前请求进行中,返回ModelAndView之前调用,所以我们可以对处理后的ModelAndView对象进行操作

    • postHandle()调用顺序和preHandle()不同,先声明的拦截器的postHandle()会后执行

  • afterCompletion()

    • 该方法在处理器请求完毕,ModelAndView对象返回之后执行,这个方法也只能在当前这个拦截器的preHandle方法的返回值为true时才会执行,主要作用是进行资源清理工作或者统一日志记录

  • 配置HandlerInterceptor拦截器(同样的方法创建了两个拦截器)

<!--拦截器-->
    <mvc:interceptors>
        <!--多个拦截器顺序执行-->
        <mvc:interceptor>
            <!-- /**的意思是所有文件夹及里面的子文件夹 /*是所有文件,不含子文件夹 /是web项目的根目录 -->
            <mvc:mapping path="/**"/>
            <bean class="com.cn.util.HandlerInterceptor1"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.cn.util.HandlerInterceptor2"></bean>
        </mvc:interceptor>
        <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
    </mvc:interceptors>
  • 测试如果两个拦截器都放行

 可以看到先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法

  • 测试如果一号拦截器放行,二号拦截器不放行

可以看到先按顺序调用preHandle方法,如果有一个拦截器不放行,postHandle和afterCompletion方法都不执行

只要有一个不放行,Controller层就不能执行完成

  • 测试如果两个拦截器都不放行

 可以看到按顺序调用preHandle方法,只有第一个拦截器放行,后面的拦截器才会被调用

这一篇是SpringMVC系列的第三篇文章,主要介绍了统一异常处理、拦截器等方面的知识,到此SpringMVC就全部介绍完毕了,大家对之前的文章感兴趣可以点击以下链接:

全栈开发实战 | SpringMVC框架快速入门第一篇

全栈开发实战 | SpringMVC框架快速入门第二篇

下一篇我会详细介绍SSM框架整合,也就是Spring+SpringMVC+MyBatis框架

这也是我们以后开发WEB项目常用的整合框架

加油吧少年!立志把头发熬秃

猜你喜欢

转载自blog.csdn.net/vx1271487114/article/details/125832791