Spring实现[拦截器+统一异常处理+统一数据返回]

时间:2021-09-12 01:07:33

Spring拦截器

1.实现一个普通拦截器

  • 关键步骤
    • 实现 HandlerInterceptor 接口
    • 重写 preHeadler 方法,在方法中编写自己的业务代码
@Component
public class LoginInterceptor implements HandlerInterceptor {


    /**
     * 此方法返回一个 boolean,如果为 true 表示验证成功,可以继续执行后续流程
     * 如果是 false 表示验证失败,后面的流程不能执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //用户登录业务判断
        HttpSession session = request.getSession(false);
        //有session就提取,没有也不创建
        if(session != null && session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
            //说明用户已经登录
            return true;
        }
        // 401 : 用户没有登录所以没有权限  403 : 用户登录了但没有权限
        response.setStatus(401);
        return false;
    }
}

2.将拦截器添加搭配系统配置中,并设置拦截的规则

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
//只要不是需求的页面,都进行拦截
    List<String> excludes = new ArrayList<String>() {{
        //放行数组
        add("/**/*.html");
        add("/js/**");
        add("/editor.md/**");
        add("/css/**");
        add("/img/**"); // 放行 img 下的所有文件
        add("/user/login"); // 放行登录
        add("/user/reg"); // 放行注册
        add("/art/detail"); // 放行详情页
        add("/user/author"); // 放行详情页个人信息的 username
        add("/art/list"); // 放行文章分页列表的接口
        add("/art/totalpage"); // 放行获取文章分页的总页数
        add("/art/artcount"); // 放行分页列表页个人信息的 文章数量 / 也是详情页的文章数量
    }};
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截规则
        InterceptorRegistration registration =
                registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**");
        //拦截器下放行 excludes 数组内的规则
        registration.excludePathPatterns(excludes);
    }
}

拦截器实现原理

用户调用–> controller ----> service ----> mapper---->数据库

实现拦截器之后:

用户调用–>拦截器预处理(拦截规则,黑白名单)----> controller ----> service ----> mapper---->数据库

实现原理源码分析

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 步骤1,获取执行链,重要重要重要重要重要重要重要重要重要
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 步骤2,获取适配器,重要重要重要重要重要重要重要重要重要
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//步骤3,拦截器pre方法,重要重要重要重要重要重要重要重要重要
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//步骤4,真正处理逻辑,重要重要重要重要重要重要重要重要重要
                //执行 Controller 中的业务
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				
				applyDefaultViewName(processedRequest, mv);
				//步骤5,拦截器post方法,重要重要重要重要重要重要重要重要重要
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			//步骤6,处理视图,重要重要重要重要重要重要重要重要重要
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			//步骤7,拦截器收尾方法,重要重要重要重要重要重要重要重要重要
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VVnpcXAY-1678274299650)(C:\Users\17512\AppData\Roaming\Typora\typora-user-images\1678269545822.png)]

统一异常处理

统一异常处理使用的是 @ControllerAdvice(控制器通知类) 和 @ExceptionHandler(异常处理器) 来实现。

1.创建统一封装类

2.使用 @ExceptionHandler 注解来订阅异常信息

/**
 * 异常类的统一处理
 */
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {

    @ExceptionHandler(Exception.class) // 异常类型
    public Object exceptionAdvice(Exception e) {
        return AjaxResult.fail(-1, e.getMessage());
    }
}

统一数据的返回

统一数据格式的返回可以使用 @ControllerAdvice + ResponseBodyAdvice 方法实现。

1.创建一个类,并添加 @ControllerAdvice

2.实现ResponseBodyAdvice接口,并且重写supports和beforeBodyWrite(统一返回对象就是在此方法中实现)

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
	//内容是否需要重写
    //返回 true 表示重写
    @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) {
        // 1.本身已经是封装好的对象 判断一个对象是否为一个类
        if(body instanceof  HashMap) {
            return body;
        }
        // 2.返回类型是 String (特殊)
        if(body instanceof String) {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        return AjaxResult.success(body);
    }
}