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);
}
}