SpringBoot——拦截器、统一结果返回、统一异常处理

一、拦截器

在前面学习的小项目(如图书管理系统),如果要完成强制登录,需要:

1.修改每个接口的处理逻辑(如什么情况下为未登录,什么情况下为已登录,根据不同情况返回不同结果);

2.修改每个接口的返回结果;

3.修改对应的前端代码。

可以看到,很多接口都涉及到一段相同的处理逻辑,都要写一段相同的代码,非常麻烦,对此Spring给我们提供了一种功能——拦截器

1.1 拦截器入门

拦截器是Spring提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据也需要执行预先设定的代码(如判断用户是否登录的代码)。

拦截器的使用大体分为两步:

<1>定义拦截器(实现HandlerInterceptor接口)

<2>注册配置拦截器(实现WebMvcConfigurer接口)

一、定义拦截器

定义一个类实现HandlerInterceptor接口即可:

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle目标方法执行前");
        return true;//true拦截     false不拦截
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle目标方法执行后执行");
    }


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion视图渲染完毕后执行");//现在Spring已经不用来处理视图了,因此这个方法无需理会
    }

}

二、注册配置拦截器

定义一个类实现WedMvcConfiguer接口即可:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/book/**");//拦截book下的任意路径
//                .excludePathPatterns("/user/login");//不拦截的路径
    }
}

实现WebMvcConfigurer接口后,通过addIntercepter方法将定义好的拦截器添加进来,再通过addPathPatterns方法指定拦截哪些请求,通过excludePathPatterns方法指定不拦截哪些请求。

下面是一些常见的拦截路径设置:

 拦截路径含义举例
/*一级路径/user、/login
/**任意级路径/user、/user/login、/book、/bookList
/book/*/book路径下的一级路径/book/selectById、/user/login
/book/**/book路径下的任意级路径/book、/book/bookList、/book/bookList/2

1.2 使用拦截器实现强制登录

要实现强制登录,只需要重写preHandle方法,加上一些登录逻辑判断即可,如:
 

  @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle目标方法执行前");

       if(!checkUser(request.getSession(false))){
           response.setContentType("text/html;charset=utf-8");
           response.setStatus(401);
           String msg = "用户未登录";
           response.getOutputStream().write(msg.getBytes("UTF-8"));
           return false;
       }

        //true:放行  false:拦截
        return true;
    }


public boolean checkUser(HttpSession session){
        if(session == null || session.getAttribute(Constants.SESSION_USER_KEY)==null){
            //用户未登录
            log.warn("用户未登录");
            return false;
        }
        UserInfo userInfo = (UserInfo)session.getAttribute(Constants.SESSION_USER_KEY);
        if(userInfo == null || userInfo.getId()<=0){
            //用户未登录
            log.warn("用户未登录");
            return false;
        }
        log.info("用户已登录");
        return true;
    }

接下来,不通过登录页面登录,直接进入图书列表页面:

可以看到,访问被成功拦截


二、统一结果返回

 在项目的各种接口中,会涉及到各种不同的返回结果,如果需要对这些返回结果进一步封装,加上code(判断用户是否登录)和errMsg(错误信息),岂不是需要对所有接口的返回结果手动在封装一层,这样非常麻烦,因此Spring提供了统一结果返回的功能。

如需要将所有Controller返回结果封装为下面的Result:

@Data
public class Result<T> {
    //用于验证用户是否登录
    private ResultCodeEnum code;//code = -1 用户未登录、code = -2 后端出错,code = 200 正常
    private String errMsg;//告知用户错误信息
    private T data;//将Controller层返回结果封装一层

    public static <T>Result success(T data){
        Result result = new Result();
        result.setCode(ResultCodeEnum.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }

    public static Result fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        return result;
    }

    public static <T>Result fail(String errMsg,T data){
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

    public static Result unLogin(){
        Result result = new Result();
        result.setCode(ResultCodeEnum.UNLOGIN);
        result.setErrMsg("用户未登录");
        return result;
    }
}

使用Spring的统一结果返回主要有以下步骤:

1.实现ResponseBodyAdvice接口,并使用注解@ControllerAdvice(通过实现 ResponseBodyAdvice 接口,@ControllerAdvice 可以‌全局拦截控制器的返回值‌,并统一封装为固定格式)

2.重写supports方法和beforeBodyWriter方法(supports的返回值为true表示执行beforeBodyWriter方法,反之不执行)

接下里使用Spring的统一结果返回对图书管理系统中的返回结果进行封装(分装为上方的Result):

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof String){
                //当返回结果为String时,需要额外处理
                return objectMapper.writeValueAsString(Result.success(body));
        }
        if(body instanceof Result){
            return body;//如果返回结果已经是Result类型,直接返回
        }
        return Result.success(body);
    }
}

接下来在postman进行测试:

可以看到,成功封装为Result

但是,需要注意的一点是,在上面的代码中,对于类型为String的返回值,需要进行额理,主要是因为Spring的 HttpMessageConverter 机制对String类型和其他对象类型的处理方式不同。


三、统一异常处理

 统一异常处理是使用注解@ControllerAdvice和@ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler表示异常处理器,两者结合表示出现异常是执行某个通知

实现代码如下:

@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    @ExceptionHandler//这个注解也可以指定捕获的异常类型
    public Result handler(Exception e){//也可以接收具体的异常
        log.error("发生异常,e:",e);
        //如果代码出现Exception异常(包括Exception的⼦类),就返回⼀个Result的对象
        return Result.fail("内部错误,请联系管理员");
    }
}

上述代码表示,只要出现Exception异常(以及它的子类),就会返回一个Result对象,下面写一个接口,进行测试,代码如下:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public Integer test(){
        int a = 10/0;
        return 1;
    }
}

测试结果:
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值