Spring MVC拦截器和全局异常处理(RESTful+状态码)

Spring MVC拦截器和全局异常处理(RESTful+状态码),第1张

Spring MVC拦截器和全局异常处理(RESTful+状态码)

文章目录
          • 概述
          • 第一步:实现`HandlerInterceptor`接口
          • 第二步:配置拦截器
          • 登录拦截
            • 数据准备
            • 视图 jsp 准备
            • 业务层
            • 控制层
            • 拦截器类
            • 配置文件
            • 测试
      • RESTful支持
      • Spring MVC 统一异常处理
          • 全局处理 也有两种方式
          • springmvc 提供的
          • 自定义的
          • 注解
          • 用 `@ControllerAdvice+@ ExceptionHandler` 注解(推荐)

概述
  • 对于任何优秀的MVC框架,都会提供一些通用的 *** 作,如请求数据的封装、类型转换、数据校验、解析上传的文件、防止表单的多次提交等。早期的MVC框架将这些 *** 作都写死在核心控制器中,而这些常用的 *** 作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低。
  • Spring MVC中的拦截器 (Interceptor)类似于 Servlet 中的过滤器(Filter),主要用于拦截用户的请求并作相应的处理。例如:权限验证、记录请求信息的日志、判断用户是否登录等。
  • Spring MVC拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器。

要使用拦截器需要对拦截器类进行定义和配置。有两种实现方式

  • 实现 HandlerInterceptor接口
  • 实现 WebRequestInterceptor接口【两个使用时一样的】

以第一种方式为练习。

第一步:实现HandlerInterceptor接口

该接口的位置:org.springframework.web.servlet包中,定义了三个 default 方法,它们都有方法体。自定义的拦截器类实现该接口的时候需要实现这三个方法。【注意实现方法的时候方法修饰符是 public, 粘贴过来记得改】

  • boolean preHandle()
    • 该方法在执行控制器方法之前执行。返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行(如果后面没有其他Interceptor,就会执行controller方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化 *** 作及对请求进行预处理。
  • void postHandle()
    • 该方法在执行控制器方法调用之后,且在返回ModelAndView之前执行。由于该方法会在DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图【处理完视图再去跳转】,可通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion
    • 该方法在执行完控制器之后执行,由于是在Controller方法执行完
    • 毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理 *** 作。
第二步:配置拦截器

我的 ssm 项目的主体配置都是 springmvc.xml 中进行的,拦截器配置是有先后顺序的。

  • 拦截器链标签
  • 拦截器标签
  • 配置拦截路径
    • userinfo/login"/>:不拦截的路径,如果分不清楚可以在拦截全部的下面选出不拦截的
    	
    		
    		
    			
    			
    			
    			
    			
    			
    			
    			
    			
    		
            
    	
    
    登录拦截 数据准备
    1. 用户实体:UserInfo

      public class UserInfo implements Serializable {
      	private static final long serialVersionUID = 1L;
      	private Integer id;
      	private String userName;
      	private String userPass;
          
      	public Integer getId() {
      		return id;
      	}
      	public void setId(Integer id) {
      		this.id = id;
      	}
      	public String getUserName() {
      		return userName;
      	}
      	public void setUserName(String userName) {
      		this.userName = userName;
      	}
      	public String getUserPass() {
      		return userPass;
      	}
      	public void setUserPass(String userPass) {
      		this.userPass = userPass;
      	}
      }
      
    2. 用户映射文件:UserInfoMapper接口和UserInfoMapper.xml映射文件

      
      
      
      	
      	
      	
      		select * from userinfo where username = #{userName}
      	
      
      
      public interface UserInfoMapper {
      	public UserInfo login(String userName);
      }
      
    视图 jsp 准备
    • login文件夹下面的 login.jsp
    • 注意这些都是配置视图解析器之后在 WEB-INF/views下面的 *** 作
    • el 表达式都是取的错误信息
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    
    
    
    
    登录
    
    
    用户登录${loginErr0}
    
    
    
    
    业务层
    • 传输用户名到这 调用接口的方法去查询数据【条件是用户名】

    • 不顺便判断密码是为了在 controller 里面返回不同的错误信息回显

      @Service
      public class UserInfoService {
      	@Autowired
      	UserInfoMapper mapper = null;
      	
      	
      	public UserInfo login(String userName) {
      		UserInfo userInfo = mapper.login(userName);
      		if (userInfo!=null) {
      			return userInfo;
      		}
      		return null;
      	}
      }
      
    控制层
    • 先整个跳转到 login.jsp的controller, WEB-INF 路径不可直接在外部访问

    • 尤其要注意最后跳转的路径即页面和映射的访问路径之间的区别

    • 把查询到的用户信息 user 放到 session 中,通过 session 保存

    • old 是页面输入传递过来的数据组合的实体 user 是数据库中存在的实体数据

      @Controller
      @RequestMapping("/userinfo")
      public class UserInfoController {
      	@Autowired
      	UserInfoService service = null;
      	
      	@RequestMapping("/login")
      	public String login() {
      		return "/login/login";
      	}
      	
      	@RequestMapping("/doLogin")
      	public String doLogin(Model model ,String userName, String userPass, HttpSession session) {
      		UserInfo user = service.login(userName);
      		UserInfo old = new UserInfo();
      		old.setUserName(userName);
      		old.setUserPass(userPass);
      		if (user != null) {
      			if (user.getUserPass().equals(userPass)) {
      				session.setAttribute("user", user);
      				return "index";
      			}
      			model.addAttribute("loginErr1", "用户密码错误");
      			model.addAttribute("old", old);
      			return "/login/login";
      		}
      		model.addAttribute("loginErr", "用户账号错误");
      		model.addAttribute("old", old);
      		return "/login/login";
      	}
      }
      
    拦截器类
    • 拦截器包

    • 拦截器类:LoginHandlerInterceptor

    • 顺便可以打印测试顺序

    • session 取出存放的用户实体,为空则没有登陆,跳到登录页面,不为空则已经登录,可以正常使用,拦截器向下走

      public class LoginHandlerInterceptor implements HandlerInterceptor{
      	
      	@Override
      	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      			throws Exception {
      //		System.out.println("pre");
      		Object obj  = request.getSession().getAttribute("user");
      		if (obj == null) {
      			request.setAttribute("loginErr0", "您还没有登录");
      			request.getRequestDispatcher("/WEB-INF/views/login/login.jsp").forward(request, response);
      			return false;
      		}
      		return true;
      	}
      	
      	
      	@Override
      	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      			@Nullable ModelAndView modelAndView) throws Exception {
      		System.out.println("return"
      				+ "之前");
      	}
      	
      	
      	@Override
      	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
      			@Nullable Exception ex) throws Exception {
      		System.out.println("controller执行结束之后");
      	}
      }
      
    配置文件
    • 前面的配置文件
    测试 RESTful支持

    概念:RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被Javascript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。

    资源:REST 是面向资源的,每个资源都有一个唯一的资源定位符(URI)。每个URI代表一种资源(resource),所以URI中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以URI中的名词也应该使用复数。

    请求方式:

    请求方式含义GET(SELECT)从服务器取出资源(一项或多项)POST(CREATE)在服务器新建一个资源PUT(UPDATE)在服务器更新资源(更新完整资源)PATCH(UPDATE)在服务器更新资源, PATCH更新个别属性DELETE(DELETE)从服务器删除资源

    【1】查询

    查询	传统	REST	REST 后台接收
    查询所有	http://localhost:8080/employee/list	http://localhost:8080/employees	@RequestMapping(value = “/employees”, method = RequestMethod.GET)
    查询单个	http://localhost:8080/employee/list?id=1	http://localhost:8080/employees/1	@RequestMapping(value = “/employees/{id}”, method = RequestMethod.GET)
    @ResponseBody
    public Employee queryById(@PathVariable Long id) {}
    

    【2】添加

    添加	传统	REST	REST 后台接收
    添加	http://localhost:8080/employee/add	http://localhost:8080/employees	@RequestMapping(value = “/employees”, method = RequestMethod.POST)
    public Employee add(@ModelAttribute(“emp”) Employee employee) {}
    

    【3】修改

    修改	传统	REST	REST 后台接收
    修改	http://localhost:8080/employee/update	http://localhost:8080/employees	@RequestMapping(value = “/employees”, method = RequestMethod.PUT)
    public Employee update(@ModelAttribute(“emp”) Employee employee) {}
    

    【4】删除

    查询	传统	REST	REST 后台接收
    删除	http://localhost:8080/employee/delete	http://localhost:8080//employees/{id}	@RequestMapping(value = “/employees/{id}”, method = RequestMethod.DELETE)
    @ResponseBody
    public JsonResult delete(@PathVariable Long id) {}
    

    注意:
    【1】当参数非常多的时候,不建议使用参数路径方式;
    【2】如果参数名非常敏感,建议使用参数路径方式,可以隐藏参数名。

    • 相关注解

      @RestController 由 @Controller + @ResponseBody组成(返回 JSON 数据格式)
      @PathVariable URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到控制器处理方法的形参中
      @RequestMapping 注解用于请求地址的解析,是最常用的一种注解
      @GetMapping 查询请求
      @PostMapping 添加请求
      @PutMapping 更新请求
      @DeleteMapping 删除请求
      @RequestParam 将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

      • @RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)

        • value:参数名

        • required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。

        • defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

    • HTTP响应状态码

    200 OK - [GET]	服务器成功返回用户请求的数据
      201 CREATED - [POST/PUT/PATCH]	用户新建或修改数据成功
      202 Accepted	表示一个请求已经进入后台排队(异步任务)
      204 NO ConTENT - [DELETE]	用户删除数据成功
      400 INVALID REQUEST - [POST/PUT/PATCH]	用户发出的请求有错误,服务器没有进行新建或修改数据的 *** 作,该 *** 作是幂等的
      401 Unauthorized - [*]	表示用户没有权限(令牌、用户名、密码错误)
      403 Forbidden - [*]	表示用户得到授权(与401错误相对),但是访问是被禁止的
      404 NOT FOUND - [*]	用户发出的请求针对的是不存在的记录,服务器没有进行 *** 作,该 *** 作是幂等的
      405 Method Not Allowed	方法不允许,服务器没有该方法
      406 Not Acceptable - [GET]	用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)
      410 Gone -[GET]	用户请求的资源被永久删除,且不会再得到的
      422 Unprocesable entity - [POST/PUT/PATCH]	当创建一个对象时,发生一个验证错误
      500 INTERNAL SERVER ERROR - [*]	服务器发生错误,用户将无法判断发出的请求是否成功
    
    Spring MVC 统一异常处理 全局处理 也有两种方式
    • 实现HandlerExceptionResolver接口,自定义异常处理器。
    • 使用HandlerExceptionResolver接口的子类,也就是SpringMVC提供的异常处理器。
    springmvc 提供的
    • DefaultHandlerExceptionResolver,默认的异常处理器。根据各个不同类型的异常,返回不同的异常视图。
    • SimpleMappingExceptionResolver,简单映射异常处理器。通过配置异常类和view的关系来解析异常。
    • ResponseStatusExceptionResolver,状态码异常处理器。解析带有@ResponseStatus注释类型的异常。
    • ExceptionHandlerExceptionResolver,注解形式的异常处理器。对@ExceptionHandler注解的方法进行异常解析。
    自定义的
    • 自定义异常类

      package com.kafamiao.myexception;
      
      public class CustcomException extends RuntimeException{
      	private String message;
      	public CustcomException(String message) {
      		this.message = message;
      	}
      	public String getMessage() {
      		return message;
      	}
      	public void setMessage(String message) {
      		this.message = message;
      	}
      }
      
    • 实现 HandlerExceptionResolver接口定义一个异常处理器

      @Component
      public class CustcomExceptionResolver implements HandlerExceptionResolver{
      
      	@Override
      	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
      			Exception ex) {
      		//声明自定义异常
      		CustcomException custException = null;
      		if(ex!=null && ex instanceof CustcomException) {
      			custException = (CustcomException) ex;
      		}
      		else {
      			custException = new CustcomException(ex.getMessage());
      		}
      		//转向出错页面
      		ModelAndView modelAndView = new ModelAndView();
      		modelAndView.addObject("message", custException.getMessage());
      		modelAndView.setViewName("err/error");
      		return modelAndView;
      	}
      }
      
    • 测试

      	@RequestMapping("/findall")
      	public String findAll(Model model) {
      		if(0==0) {
      			throw new CustcomException("程序员主动抛出异常,查询数据出错了。");
      		}
      		//用业务类调用mapper
      		List goodslist= service.findAll();
      		model.addAttribute("goodslist", goodslist);
      		return "goods/findall";
      	}
      
    • 异常现实页

      <%@ page language="java" contentType="text/html; charset=UTF-8"
          pageEncoding="UTF-8"%>
      
      
      
      
      Insert title here
      
      
      出错原因:${message}
      
      
      
    注解
    • @ExceptionHandler注解用来将一个方法标注为异常处理方法。该注解中只有一个可选的属性value,是一个Class数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。被该注解修饰的方法的返回值为异常处理后的跳转页面,其返回值可以是ModelAndView、String,或void;方法名随意,方法的参数可以是 Exception 及其子类对象、Model、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。

      @ExceptionHandler注解处理异常的作用域:单个类,只针对当前Controller。

      @Controller
      public class ExceptionController {
          @RequestMapping(value = "/exception1")
          public String exception1() {
              // 模拟出现异常
              System.out.println(10 / 0);
              return "success";
          }
      
          @RequestMapping(value = "/exception2")
          public void exception2() throws CustomException {
              // 模拟出现异常
              throw new CustomException("我抛出了一个异常!!!");
          }
      
          //处理自定义异常
          @ExceptionHandler({CustomException.class, ArithmeticException.class})
          public String exceptionHandler1(Exception e, Model model) {
              // 打印错误信息
              System.out.println(e.getMessage());
              e.printStackTrace();
              // 将错误数据存入请求域
              model.addAttribute("exception", e);
              return "show-annotation-message";
          }
      }
      
      • 注意:如果在Controller中单独使用这个注解是有缺陷的,就是不能够全局处理异常,因为进行异常处理的方法必须与出错的方法在同一个Controller里面,也就是说每个Controller类中都要写一遍,所以实用性不高。

      • 解决方案:可以将处理异常的信息抽取出来放在一个baseController,然后对需要处理异常的Controller继承该类即可。

        public class baseController {
            //处理自定义异常
            @ExceptionHandler({CustomException.class, ArithmeticException.class})
            public String exceptionHandler1(Exception e, Model model) {
                // 打印错误信息
                System.out.println(e.getMessage());
                e.printStackTrace();
                // 将错误数据存入请求域
                model.addAttribute("exception", e);
                return "show-annotation-message";
            }
        }
        
        • 但是还是存在同样的问题,每个类都得继承它,可见这种方式同样不可取,所以一般使用下面这种方式:@ControllerAdvice和@ExceptionHandle 注解配合使用。
    用 @ControllerAdvice+@ ExceptionHandler 注解(推荐)
    • @ExceptionHandler注解标注的异常处理方法必须与出错的方法在同一个Controller里面,所以这种方式是只对应单个Controller类。那么此时有一种更好的解决方案:可以使用@ControllerAdvice+@ExceptionHandler注解来解决,这个是 Spring 3.2 带来的新特性。

      ​ 两者一起使用的作用域:全局异常处理,针对全部Controller中的指定异常类

      // 这个注解表示当前类是一个异常映射类
      @ControllerAdvice
      public class MyException {
      
          // 在@ExceptionHandler注解中指定异常类型
          @ExceptionHandler(value = {CustomException.class, ArithmeticException.class})
          public ModelAndView exceptionMapping(Exception exception) {// 方法形参位置接收SpringMVC捕获到的异常对象
      
              // 可以将异常对象存入模型;将展示异常信息的视图设置为逻辑视图名称
              ModelAndView modelAndView = new ModelAndView();
              modelAndView.addObject("exception", exception);
              modelAndView.setViewName("show-annotation-message");
              // 打印一下信息
              System.out.println(exception.getMessage());
              return modelAndView;
          }
      }
      

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/zaji/5697782.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存