SpringMVC

一、异常处理器

springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。(它负责捕获,将异常放到我们自己编写的处理类中)

Spring3.0中对异常的处理方法一共提供了两种:

① 实现HandlerExceptionResolver接口:可以实现全局异常控制,并且Spring已经提供了一个默认的实现类SimpleMappingExceptionResolver;
② 使用@ExceptionHandler注解:可以在Controller内部实现更个性化点异常处理方式,灵活性更高。

1、异常处理器思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。

系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,SpringMVC提供全局异常处理器(一个系统只有一个异常处理器)进行统一异常处理。如下图:

2、异常处理器使用

测试准备

 - 错误页面:用于显示异常信息

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>系统发生异常了!</h1>
    <br />
    <h1>异常信息</h1>
    <br />
    <h2>${error }</h2>
</body>
</html>
error.jsp

- 自定义异常类:

为了区别不同的异常,通常根据异常类型进行区分,这里我们创建一个自定义系统异常。
如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
package com.sikiedu.exception;

public class MyException extends Exception {
    // 错误信息
    private String msg;

    public MyException(String msg) {
        super();
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}
MyException.java

- Service异常:发生异常,将异常向上抛出到控制器中

package com.sikiedu.service;

import 略...

/**
 * @author*/
@Service
public class ItemServiceImpl implements ItemService {

    @Autowired()
    private ItemMapper itemMapper;

    @Override
    public List<ItemInfo> selectByVo(ItemInfoVo vo) throws MyException {

        // 随机抛出一个异常
        int x = (int) (Math.random() * 10 - 1);
        if (x % 2 == 0) {
            // 制造运行时异常
            int i = 1 / 0;
        } else {
            // 抛出自定义异常
            throw new MyException("【操作没错 - 但我就是不给你通过】");
        }
        return itemMapper.selectByVo(vo);
    }
}
ItemServiceImpl.java

(1) 通过实现HandlerExceptionResolver接口完成异常处理

 - 编写异常处理器

package com.sikiedu.exception;

import 略...

/**
 * 自定义异常处理器实现
 * @author*/
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

    /**
     * @param request、response:请求信息
     * @param obj:异常对象的(全包名+类名+方法名)
     * @param e:异常信息
     * @return mav:模型视图
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj,Exception e) {

        ModelAndView mav = new ModelAndView();
        // 获取异常信息
        String message = "";
        // 判断异常信息类型
        if (e instanceof MyException) {
            // 执行自定义异常处理 - 需要强制类型转换
            message = "HandlerExceptionResolver - 自定义异常    <br/>" + ((MyException) e).getMsg() + "<br/>" + e.getStackTrace()[0];
        } else {
            // 运行时异常
            message = "HandlerExceptionResolver - 运行时异常    <br/>" + e.getMessage() + "<br/>" + e.getStackTrace()[0];
        }
        // 将异常信息输出到错误页面
        mav.addObject("error", message);
        // 设置要跳转的视图名称
        mav.setViewName("error");

        return mav;
    }
}

 - springmvc.xml配置异常处理器:所有的Controller出现的异常, 都会在这个异常处理器中查找对应的处理方法

<!-- 配置异常处理器 -->
<bean class="com.sikiedu.exception.MyHandlerExceptionResolver" />

 - Controller控制器:捕获异常 交给异常处理器

package com.sikiedu.controller;

import 略...

/**
 * @author*/
@Controller
@RequestMapping(value = "/item/")
public class ItemController {

    @Autowired
    private ItemService itemService;

    @RequestMapping(value = "selectByVo.do")
    public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 异常发生,抛出
List<ItemInfo> itemList = itemService.selectByVo(vo); model.addAttribute("itemList", itemList); return "item_list"; } }

 - 测试结果

 

(2) 通过@ExceptionHandler注解实现异常处理

 - 开启注解:SpringMVC中默认是没有加装载HandlerExceptionResolver

<mvc:annotation-driven />

 - Controller控制器:捕获异常 交给异常处理器

如果RequestMapping所注解的方法出现了异常,则自动寻找当前类中是否存在被@ExceptionHandler注解的方法,如果存在,则执行。
使用@ExceptionHandler处理,该注解只能处理当前控制器中的异常。 这个方法中可以加入Exception类型的参数,该参数即对于发生的异常对象。
package com.sikiedu.controller;

import 略...

/**
 * @author*/
@Controller
@RequestMapping(value = "/item/")
public class ItemController {

    @Autowired
    private ItemService itemService;

    @RequestMapping(value = "selectByVo.do")
    public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 发生异常,抛出
List<ItemInfo> itemList = itemService.selectByVo(vo); model.addAttribute("itemList", itemList); return "item_list"; } // 处理运行时异常、自定义异常在,这个方法中可以加入Exception类型的参数,该参数即对于发生的异常对象
@ExceptionHandler(value = { MyException.class, RuntimeException.class }) public String selectException(Exception e, Model model) { // 获取异常信息 String message = ""; // 判断异常信息类型 if (e instanceof MyException) { // 执行自定义异常处理 - 需要强制类型转换 message = "@ExceptionHandler处理 - 自定义异常 <br/>" + ((MyException) e).getMsg() + "<br/>" + e.getStackTrace()[0]; } else { // 运行时异常 message = "@ExceptionHandler处理 - 运行时异常 <br/>" + e.getMessage() + "<br/>" + e.getStackTrace()[0]; } // 将异常信息输出到错误页面 model.addAttribute("error", message); // 视图名 return "error"; } }

 - 测试结果:

(3) 通过(@ControllerAdvice + @ ExceptionHandler)注解配置全局异常 - 推荐使用★

@ControllerAdvice:相当于是AOP,面向切面,给每一个controller都横插了该处理类。
通过@ControllerAdvice注解使@ExceptionHandler异常处理注解应用到所有使用@RequestMapping的方法上;

  - 开启注解:SpringMVC中默认是没有加装载HandlerExceptionResolver

<mvc:annotation-driven />

 - 全局异常处理类:

package com.sikiedu.exception;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 自定义全局异常类
 * @author*/
@ControllerAdvice
public class MyGlobalException {

    // 处理运行时异常
    @ExceptionHandler(value = RuntimeException.class)
    String runtime(RuntimeException e, Model model) {
        // 将异常信息输出
        model.addAttribute("error", "@ControllerAdvice+@ExceptionHandler处理 - 运行时异常    </br>" + e.getMessage() + "</br>" + e.getStackTrace()[0]);
        // 视图名
        return "error";

    }
    // 处理自定义异常
    @ExceptionHandler(value = MyException.class)
    String myHandler(MyException e, Model model) {
        // 将异常信息输出
        model.addAttribute("error","@ControllerAdvice+@ExceptionHandler处理 - 自定义异常    </br>" + e.getMsg() + "</br>" + e.getStackTrace()[0]);
        // 视图名
        return "error";
    }
}

 - Controller控制器:捕获异常 交给异常处理器

package com.sikiedu.controller;

import 略...

/**
 * @author*/
@Controller
@RequestMapping(value = "/item/")
public class ItemController {

    @Autowired
    private ItemService itemService;

    @RequestMapping(value = "selectByVo.do")
    public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 发生异常,抛出
        List<ItemInfo> itemList = itemService.selectByVo(vo);
        model.addAttribute("itemList", itemList);
        return "item_list";
    }
}

 - 测试结果

二、拦截器Interceptor

SpringMVC中的拦截器可以是运行在控制器(Controller)之前的组件,可以设置拦截器应用于哪些请求路径,当发生这些请求时,拦截器会自动执行,在执行过程中,可以对请求相关数据进行判断,选择阻止继续向后执行,或选择放行。

注意:拦截器是一个若干种请求都会经历的执行过程,但是,并不一定需要阻止继续运行,只要是若干种请求都需要做相同的事情,也许每种请求的处理过程都是选择放行,也可以使用拦截器。

 - 作用:权限检查,日志记录,性能检测等;

用户可以自己定义一些拦截器来实现特定的功能。例:访问特定页面前验证用户是否登陆等;

 - 拦截器链:HandlerExecutionChain

拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。

 - 拦截器与过滤器的区别:

① 过滤器:是 servlet 规范中的一部分, 任何 java web 工程都可以使用。
  拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
② 过滤器:在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
  拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp、html、css、image 或者 js 是不会进行拦截的。

1、主要相关类和方法

● HandlerExecutionChain:该类主要由 handler 和 handler interceptors 组成

HandlerMapping类通过getHandler方法会调用到该类

● HandlerInterceptor

Spring MVC中对于一个请求可以添加多个拦截器,而这个拦截器集合中会链式调用这些拦截器。每个拦截器会执行固定顺序的方法,而这些方法就定义在HandlerInterceptor类中。

这是拦截器的一个基础接口,里面有三个方法

 - boolean preHandle ( HttpServletRequest request,HttpServletResponse response,Object handler ) throws Exception

使用时机:预处理,在拦截方法前执行
应用场景:可以在该方法中放入一些初始化的操作,比如权限验证,日志管理等 注意:该方法的返回值是boolean类型,若返回值为true,则继续调用后面的拦截器和目标方法,若返回为false,则不会调用后面的拦截器和目标方法,表示请求结束

 - void postHandle ( HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView )throws Exception

使用时机后处理,在调用目标方法之后,渲染视图之前被调用。具体来说,是在调用了Controller中定义的方法之后,但在DispatcherServlet 处理视图返回渲染结果之前被调用。
应用场景:根据使用的时机就可以知道,该方法可以对Controller处理之后ModelAndView进行操作
注意:当有多个interceptor的时候,对于preHandler的调用顺序和postHandler的调用顺序是恰恰相反的。

 - void afterCompletion ( HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex ) throws Exception

使用时机:渲染后处理,在页面渲染后执行;
应用场景:释放资源

2、拦截器的执行流程

3、开发拦截器

(1) 自定义拦截器

自定义拦截器必须要实现HandlerInterceptor接口。

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("LoginInterceptor.preHandle()");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("LoginInterceptor.postHandle()");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("LoginInterceptor.afterCompletion()");
    }
}
View Code

接口方法:

① preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,处理器也不会调用;
② postHandle方法:在调用完处理器后调用该方法;
③ afterCompletion方法:只要该拦截器中的preHandle方法返回true,该方法就会被调用;

在拦截器的3个方法中,只有preHandle()方法是运行在控制器(Controller)之前的,另2个方法是运行在控制器之后的,所以,只有preHandle()具有真正意义的“拦截”功能,

该方法的返回值是boolean类型的,当返回true时表示放行,返回false时将阻止继续向后执行,即控制器并不会被执行;

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        String URL = request.getRequestURI();
        if (!URL.contains("login")) {
            // 不是登录请求 - 拦截
            Object user = request.getSession().getAttribute("user");
            if (user == null) {
                // 没用登录 - 重定向到登录页面/请求
                response.sendRedirect(request.getContextPath() + "/login/login.do");
                return false;
            }
        }
        return true;
    }
}
!!!注意:即使已经决定了重定向,还是需要return false;否则处理流程会继续向执行,控制器中的方法还是会被调用,达不到阻止运行的效果!

(2) 在SpringMVC核心配置文件中注册自定义拦截器

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <!-- 不拦截静态资源 -->
        <mvc:exclude-mapping path="/admin/**/*.*" />
        <bean class="com.sikiedu.interceptor.LoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

 - 配置若干个拦截器

允许使用若干个拦截器,形成拦截器链,即某个请求可能需要经过多个拦截器,仅当每个拦截器都放行时,才会执行控制器中的方法!

在配置文件中,配置的先后顺序决定了多个拦截器的执行顺序

<mvc:interceptors>
    <!-- 拦截器1 -->
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="com.sikiedu.interceptor.MyInterceptor1" />
    </mvc:interceptor>
    <!-- 拦截器2 -->
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="com.sikiedu.interceptor.MyInterceptor2" />
    </mvc:interceptor>
</mvc:interceptors>
View Code

 - 配置若干个拦截路径

<mvc:interceptor>
    <!-- 拦截路径 -->
    <mvc:mapping path="/main/index.do" />
    <mvc:mapping path="/user/password.do" />
    <mvc:mapping path="/user/info.do" />
    <mvc:mapping path="/user/handle_password.do" />
    <mvc:mapping path="/user/handle_info.do" />
    <!-- 拦截器类 -->
    <bean class="com.sikiedu.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
View Code

 - 使用通配符 * 拦截一个星号 * 只能匹配一层路径

例如:/main/*可以匹配上/main/index.do,也可以匹配/main/hello.do,但是,不可以匹配上/main/a/index.do!

<mvc:interceptor>
    <!-- 拦截路径 -->
    <mvc:mapping path="/main/*" />
    <!-- 拦截器类 -->
    <bean class="com.sikiedu.interceptor.LoginInterceptor" />
</mvc:interceptor>
View Code

如果一定要匹配若干层路径,必须使用两个星号 **

例如:配置为/main/**,可以匹配上/main/index.do,也可以匹配/main/a/hello.do,甚至可以匹配/main/a/b/c/d/hello.do

即无视路径中后续的层级。

<mvc:interceptor>
    <!-- 拦截路径 -->
    <mvc:mapping path="/main/**" />
    <mvc:mapping path="/user/**" />
    <!-- 拦截器类 -->
    <bean class="com.sikiedu.interceptor.LoginInterceptor" />
</mvc:interceptor>
View Code

 - 排除指定的路径:如果通配符匹配的路径过多,还可以排除某些请求路径

<mvc:interceptor>
    <!-- 拦截路径:黑名单 -->
    <mvc:mapping path="/main/**" />
    <mvc:mapping path="/user/**" />
    <!-- 例外路径:白名单 -->
    <mvc:exclude-mapping path="/user/reg.do" />
    <mvc:exclude-mapping path="/user/handle_reg.do" />
    <mvc:exclude-mapping path="/user/login.do" />
    <mvc:exclude-mapping path="/user/handle_login.do" />
    <!-- 拦截器类 -->
    <bean class="com.sikiedu.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
View Code

也就是说,凡/user/下的路径都会经过该拦截器,但是,/user/login.do是不被处理的!添加到“例外”中的路径,在请求时,并不是拦截器直接放行,而是拦截器根本就不执行!

三、总结 - 拦截器规则

① preHandle预处理:--------------根据拦截器定义的顺序,正向执行;

② postHandle后处理:-------------根据拦截器定义的顺序,逆向执行;

③ afterCompletion渲染后处理:---根据拦截器定义的顺序,逆向执行;

④ postHandle预处理:-------------所有拦截器都返回成功调用;

⑤ atterCompletion渲染后处理:---preHandle返回true调用;

原文地址:https://www.cnblogs.com/Dm920/p/12148231.html