在处理网络请求时,有一部分功能是需要抽出来统一处理的,与业务隔开。

登录校验

可以利用spring mvc的拦截器Interceptor,实现HandlerInterceptor接口即可。实现该接口后,会在把请求发给Controller之前进行拦截处理。
拦截器的实现分为以下两个步骤:

  • 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
  • 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。

我们使用session来作为登录校验的例子,实现如下:

package com.demo;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;/** * 登录拦截器 */@Component@Slf4jpublic class LoginInterceptor implements HandlerInterceptor {    /**     * 为 false 则不能继续往下执行;为 true 则可以。     */     @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        // 判断session的信息是否合法        HttpSession session = request.getSession(false);        if (session != null && session.getAttribute("userinfo") != null) {            return true;        }        log.error("当前用户没有访问权限");        response.setStatus(401);        return false;    }}

将写好的⾃定义拦截器通过WebMvcConfigurer注册到容器中,使得拦截器生效,具体实现代码如下:

package com.demo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class MyConfig implements WebMvcConfigurer {    @Autowired    private LoginInterceptor loginInterceptor;    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(loginInterceptor)                .addPathPatterns("/**") // 拦截所有请求                .excludePathPatterns("/user/login"); // 排除不拦截的 url    }}

如果不注入对象的话,addInterceptor() 的参数也可以直接 new 一个对象:

@Configuration // 一定不要忘记public class MyConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new LoginInterceptor())                .addPathPatterns("/**") // 拦截所有请求                .excludePathPatterns("/user/login"); // 排除不拦截的 url    }}

原理

所有的 Controller 执⾏都会通过spring mvc的调度器 DispatcherServlet 来实现,所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse        response) throws Exception {    HttpServletRequest processedRequest = request;    HandlerExecutionChain mappedHandler = null;    boolean multipartRequestParsed = false;    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);    try {        try {            ModelAndView mv = null;            Object dispatchException = null;            try {                // ...  忽略不重要的代码                // 调⽤预处理                if (!mappedHandler.applyPreHandle(processedRequest, respon                        se)) {                    return;                }                // 执⾏ Controller 中的业务                mv = ha.handle(processedRequest, response, mappedHandler.g                        etHandler());               // ...  忽略不重要的代码            } catch (Exception var20) {                dispatchException = var20;            } catch (Throwable var21) {                dispatchException = new NestedServletException("Handler di                        spatch failed", var21);            }            this.processDispatchResult(processedRequest, response, mappedH                    andler, mv, (Exception)dispatchException);        } catch (Exception var22) {            this.triggerAfterCompletion(processedRequest, response, mapped                    Handler, var22);        } catch (Throwable var23) {            this.triggerAfterCompletion(processedRequest, response, mapped                    Handler, new NestedServletException("Handler processing failed", var23));        }    } finally {        if (asyncManager.isConcurrentHandlingStarted()) {            if (mappedHandler != null) {                mappedHandler.applyAfterConcurrentHandlingStarted(processe                        dRequest, response);            }        } else if (multipartRequestParsed) {            this.cleanupMultipart(processedRequest);        }    }}

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽ applyPreHandle ⽅法的实现源码如下:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {    for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex            = i++) {        // 获取项⽬中使⽤的拦截器 HandlerInterceptor        HandlerInterceptor interceptor = (HandlerInterceptor)this.intercep        torList.get(i);        if (!interceptor.preHandle(request, response, this.handler)) {            this.triggerAfterCompletion(request, response, (Exception)null            );            return false;        }    }    return true;}

异常处理

请求时的异常处理也是比较常见的统一处理的对象。

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件,具体实现代码如下:

package com.demo;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;/** * 统一处理异常 * 一般都需要自定义一个异常对象,这里为了简单说明只用一个map对象来说明 */@ControllerAdvicepublic class ErrorAdive {    @ExceptionHandler(Exception.class)    @ResponseBody    public HashMap<String, Object> exceptionAdvie(Exception e) {        HashMap<String, Object> result = new HashMap<>();        result.put("code", "-1");        result.put("msg", e.getMessage());        return result;    }    @ExceptionHandler(ArithmeticException.class)    @ResponseBody    public HashMap<String, Object> arithmeticAdvie(ArithmeticException e) {        HashMap<String, Object> result = new HashMap<>();        result.put("code", "-2");        result.put("msg", e.getMessage());        return result;    }}

此时若出现异常就不会报错了,代码会继续执行,但是会把自定义的异常信息返回给前端!

原理

@ControllerAdvice是spring的aop对于Controller进行切面所有属性的,包括切入点和需要织入的切面逻辑,配合@ExceptionHandler来捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。

返回数据结构

统⼀的返回数据结构可以使用 @ControllerAdvice + ResponseBodyAdvice接口 的方式实现,具体实现代码如下:

package com.demo;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.HashMap;/** * 统一返回数据的处理 */@ControllerAdvicepublic class ResponseAdvice implements ResponseBodyAdvice {    /**     * 内容是否需要重写(通过此⽅法可以选择性部分控制器和⽅法进⾏重写)     * 返回 true 表示重写     */    @Override    public boolean supports(MethodParameter returnType, Class converterType) {        return true;    }    /**     * ⽅法返回之前调⽤此⽅法     */    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType,                                  MediaType selectedContentType,                                  Class selectedConverterType, ServerHttpR                                          equest request,                                  ServerHttpResponse response) {        // 构造统⼀返回对象        HashMap<String, Object> result = new HashMap<>();        result.put("state", 1);        result.put("msg", "");        result.put("data", body);        return result;    }}