掘金 后端 ( ) • 2024-04-15 17:39

WebMvcConfigurer 介绍:

WebMvcConfigurer配置其实是Spring内部的配置方式,采用javaBean的形式代替XMl配置形式实现定制.可以实现一些全局的定制需求.

WebMvcConfigurer存在的方法:

  • addViewControllers:用于注册简单的视图控制器。
    addResourceHandlers:用于注册静态资源处理器,可以将静态资源映射到指定的URL路径。
    configureViewResolvers:用于配置视图解析器,可以将逻辑视图名称解析为实际的视图。
    configureContentNegotiation:用于配置内容协商策略,可以根据请求头中的Accept字段来返回不同的响应格式。
    configureDefaultServletHandling:用于配置静态资源的处理方式,可以将请求转发给默认的Servlet。
    addReturnValueHandlers:用于注册自定义的返回值处理器,可以将控制器方法的返回值转换为响应体。
    addInterceptors 拦截器
    addArgumentResolvers:用于注册自定义的方法参数解析器,可以将请求参数解析为控制器方法的参数。
    addCrosmappings: 实现跨域
    

WebMvcConfigurer addInterceptors 详解:

拦截器配置,主要是实现WebMvcConfigurer中的一个方法,方法中存在使用的对应参数:

  • addInterceptor: 需要一个实现HandlerInterceptor接口的拦截器实现
    addPathPatterns: 用于设置拦截器的过滤路径规则, addPathPatterns("/**") 对所有请求都拦截
    excludePathPatterns: 用于设置不需要拦截的过滤规则
    

WebMvcConfigurer addArgumentResolvers 详解:

主要作用用于对Controller中的方法参数,传入之前对参数进行处理。然后将处理好的参数传给Controller中的方法.

场景描述:

  •  在权限场景中,通常会要求用户登录之后才能有权限访问的场景.对于这些问题通常有很多实现的方案,cookies+session,拦截器,其他安全组件等等
     Cookie+Session的逻辑:
     用户第一次登陆会存在一个cookie,在以后每次的访问过程中都会携带cookie进行访问.在后台的Controller中对于需要登陆权限的访问需要获取Cookie中的Token,再使用Token从Session中获取用户登录信息再判断用户登陆情况决定是否放行.
    

WebMvcConfigurer addCrosmappings 详解:

跨域问题,是浏览器为了安全做的保证,前后端都对跨域做了支持. 重写addCorsMappings

 public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")   
                .allowedOrigins("https://www.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }     

解析: 使得所有以 /api/ 开头的请求都允许来自 https://www.example.com 域名的访问,并且支持 GET、POST、PUT、DELETE 请求方法,允许携带身份验证信息,设置了缓存时间为1小时。

具体WebMvcConfigurer + 拦截器 + 注解 实现代码:

拦截器代码实现:

实现WebMvcConfigurer 类

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    /**
     * 添加拦截器
     *   注册拦截器,制定拦截和放行路径,先注册的会先执行
     *
     * @param registry 注册表
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*
        * addInterceptor 添加自定义的拦截器
        * addPathPatterns 拦截的请求路径,支持正则
        * excludePathPatterns 排除的路径,这些路径不拦截
        *
        * */
        registry.addInterceptor(new TestInterceptor()).addPathPatterns("/interceptor/*").excludePathPatterns("/login");
    }
}

new TestInterceptor() 为具体拦截器实现代码

@Slf4j
public class TestInterceptor implements HandlerInterceptor {

    /**
     * 前置处理程序
     *   在业务处理器请求完之前被调用(Controller之前)
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理程序
     * @return boolean
     * @throws Exception 例外
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            log.info("执行了 preHandle 的拦截器 这个拦截在@RequestMapping 中标注");
        } else {
            log.info("执行了 preHandle 的拦截器");
        }
        return false;
    }


    /**
     * 在业务处理器请求完成之后,生成视图之前执行(Controller 执行完成,前端生成视图之前)
     *
     * @param request      请求
     * @param response     响应
     * @param handler      处理程序
     * @param modelAndView 模型和视图
     * @throws Exception 例外
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
        log.info("执行了 postHandle 拦截器");
    }

    /**
     * 在DispatcherServlet完全处理请求之后被调用,可用于清理资源
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理程序
     * @param ex       ex
     * @throws Exception 例外
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) throws Exception {
        log.info("执行了 afterCompletion 拦截器");
    }
}

以上可以测试拦截器实现demo,如果preHandle返回false则不会继续执行 如果需要对是否登陆进行校验,并且登录用户存储session则可以实现以下代码 重写(preHandle 方法)

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    if (!(handler instanceof HandlerMethod)) {
        return true;
    }
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    // 查看这个方法上是否存在指定注解,如果不存在则不进行处理
    Method method = handlerMethod.getMethod();
    CmsAccountRequired permission = method.getAnnotation(CmsAccountRequired.class);
    if (permission == null) {
        return true;
    } 
   
     /**
     * 获取这个用户的信息从数据库 or redis (通过token获取session)
     *     1 校验登陆状态
     *     2 校验权限信息
     *     3 校验自定义的登陆前信息
     *     4 如果校验通过则把session存储进入request,controller可以使用
     */
    String sessionId = request.getHeader(CmsAccountSession.SESSION_ID);
    CmsAccountSession accountSession = cmsAccountService.getBySessionId(sessionId);

    // 检查登录状态
    if (!this.checkLogin(accountSession, permission, request, response)) {
        return false;
    }

    // 检查角色权限
    if (!this.checkRole(accountSession.getRoles(), permission, request, response)) {
        return false;
    }

    request.setAttribute(CmsAccountSession.REQUEST_ATTR_NAME, accountSession);

    return true;
}

具体WebMvcConfigurer + addArgumentResolvers + 注解 实现代码:

WebMvcConfigurer 实现的代码

/**
* Controller代码, @CurrentCmsAccount 注解标注用于后续判断
*   主要用途: 后端不想判断request大量的参数,可能还需要class解析等问题,只想要试用几个参数进行后续处理
*           所以这里可能对request处理,并且拿到一些共用参数存入CmsAccountSession中
*           而且这些参数前端不存储,后端通过拦截器注入到request中,这里相当于二次处理
*/ 
@CmsAccountRequired 
@GetMapping("/test")
public Result<CmsAccountSessionVo> currentAccountInfo(@CurrentCmsAccount CmsAccountSession session) {
    CmsAccountSessionVo userSessionVo = cmsAccountService.info(session);
    return Result.success(userSessionVo);
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    /**
     * 添加参数解析器
     *
     * @param resolvers 解析器
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new UserArgumentResolver());
    }
}

new UserArgumentResolver() 需要自己实现自定义需求

// 主要用于对传入参数系统自动赋值
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
    /**
     * 该方法用于判断Controller中方法参数中是否有符合条件的参数:
     *   有则进入下一个方法resolveArgument
     *   没有则跳过不做处理
     * 通常在这里拦截一些请求,所有的请求都会经过这里判断
     * 常规做法:
     *   查看传入的controller是否存在指定注解,和指定参数,如果存在则解析出来注入
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return parameter.getParameterType().isAssignableFrom(CmsAccountSession.class)
    && parameter.hasParameterAnnotation(CurrentCmsAccount.class);
    }

    /**
     * 该方法在上一个方法同通过之后调用:
     * 在这里可以进行处理,根据情况返回对象——返回的对象将被赋值到Controller的方法的参数中
     *
     * @param methodParameter       方法参数
     * @param modelAndViewContainer 模型和视图容器
     * @param nativeWebRequest      原生Web请求
     * @param webDataBinderFactory  Web数据绑定器工厂
     * @return {@link Object}
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        assert request != null;
        HttpSession session = request.getSession();
        // 获取cookie
        Cookie[] cookies = request.getCookies();
        String token = null;
        for (Cookie c : cookies){
            if ("token".equals(c.getName())){
                token = c.getValue();
                break;
            }
        }
        // 如果token不存在,则返回null
        if (token == null) {
            return null;
        }
        // 获取session中对象
        return session.getAttribute(token);
    }
}

以上为通用的拦截器,用于拦截指定的正则接口,并且自动注入session信息