WebMvcConfigurer技术内幕

一、前言

在Spring 5后,官方废弃了WebMvcConfigurerAdapter。因此,Springboot 2.x后该类被标记为@Deprecated,Springboot官方也重新给出了两种实现自定义配置类的方式:

  • 实现WebMvcConfigurer接口 (本文重点
  • 继承WebMvcConfigurationSupport

在Spring的WebMvcConfigurer接口中,为我们提供了很多的方法去自定义SpringMVC的配置,使用者只需根据需求,让自定义配置类去实现相应的方法即可。

WebMvcConfigurer接口是何许人也呢?WebMvcConfigurer接口是Spring内部的提供给使用者自定义配置的一种方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架和业务个性化定制。是基于java-based方式的Spring MVC配置,只需要创建一个配置类并实现WebMvcConfigurer接口即可。而弃用的WebMvcConfigurerAdapter抽象类也是对WebMvcConfigurer接口的简单抽象,只是增加了一些默认实现。

public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {....}

二、WebMvcConfigurer源码分析

public interface WebMvcConfigurer {
	/** HandlerMappings 路径匹配选项配置,可自定义访问路径的匹配规则 */
    default void configurePathMatch(PathMatchConfigurer configurer) {}
	/** 内容协商配置 */
    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
	/** 异步请求支持配置,只能设置两个值:超时时间(毫秒)和AsyncTaskExecutor(异步任务执行器) */
    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
	/** 默认静态资源处理器 */
    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
	/** 注册自定义转化器*/
    default void addFormatters(FormatterRegistry registry) {}
	/** 拦截器配置 */
    default void addInterceptors(InterceptorRegistry registry) {}
	/** 资源处理 */
    default void addResourceHandlers(ResourceHandlerRegistry registry) {}
	/** CORS(跨域资源共享)配置 */
    default void addCorsMappings(CorsRegistry registry) {}
	/** 视图跳转控制器  */
    default void addViewControllers(ViewControllerRegistry registry) {}
	/** 配置视图解析 */
    default void configureViewResolvers(ViewResolverRegistry registry) {}
	/** 添加自定义方法参数处理器 */
    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
	/** 添加自定义返回结果处理器 */
    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
	/** 配置消息转换器,重载会覆盖默认注册的HttpMessageConverter */
    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
	/** 配置消息转换器,仅添加一个自定义的HttpMessageConverter. */
    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
	/** 配置异常转换器 */
    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
	/** 添加异常转化器 */
    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
	/** 获取校验器 */
    @Nullable
    default Validator getValidator() {return null;}
	/** 获取消息码处理器 */
    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {return null;}
}

2.1、configurePathMatch

实现configurePathMatch方法,可自定义的path的匹配规则 参考:Springboot官网学习 7

    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 是否存在尾/来进行匹配  如:/user和/user/等效的,同样可以进行匹配
        configurer.setUseTrailingSlashMatch(true);

        // 这个配置需要传入一个UrlPathHelper对象,UrlPathHelper是一个处理url地址的帮助类
        // 它里面有一些优化url的方法,比如:
        // getSanitizedPath,就是将// 换成/ ,所以我们在输入地址栏的时候,//也是没有问题的,默认即支持
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        configurer.setUrlPathHelper(urlPathHelper);

        // 路径匹配器 PathMatcher是一个接口,springmvc 默认使用的是AntPathMatcher,可根据需要配置 PathMatcher(路径匹配器)
        //configurer.setPathMatcher();

        // 配置路径前缀:对含有AdminController注解的controller添加/admin地址前缀,对含有AppController注解的controller添加/app地址前缀
        configurer.addPathPrefix("admin", c -> c.isAnnotationPresent(AdminController.class));
        configurer.addPathPrefix("app", c -> c.isAnnotationPresent(AppController.class));
        // 配置路径前缀:分包形式添加
        // configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.admin"));
        // configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.app"));

    }

2.2、configureContentNegotiation

configureContentNegotiation:内容协商配置
????什么是内容协商呢?很迷惑吧?第一次看到时,我也头大了。我们知道服务端可以以多种形式进行响应:即MIME(MediaType)媒体类型(如JSON、XML、cbor)。但对于某一个客户端(浏览器、APP…)来说它只需要一种响应形式。所以,这样就需要在客户端和服务端有一种机制来保证统一的响应形式,而这种机制就是内容协商机制。通俗来说,就是统一客户端与服务器端的响应格式的一种机制。参考文章

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    /* 是否通过请求Url的扩展名来决定media type */
    configurer.favorPathExtension(true)
            /* 是否检查Accept请求头 */
            .ignoreAcceptHeader(true)
            /**
            * 通过什么样的参数来获取返回值,如:
            * http://localhost:8080/login/getMediaType?mediaType=json
            */
            .parameterName("mediaType")
            /* 设置默认的mediatype媒体类型 */
            .defaultContentType(MediaType.TEXT_HTML)
            /* 请求以.html结尾的会被当成MediaType.TEXT_HTML*/
            .mediaType("html", MediaType.TEXT_HTML)
            /* 请求以.json结尾的会被当成MediaType.APPLICATION_JSON*/
            .mediaType("json", MediaType.APPLICATION_JSON);
}

2.3、configureDefaultServletHandling和addResourceHandlers

在Spring MVC中,当项目访问静态资源(如.html.js.css等)时,是不需要经过servlet的,但是如果配置了DispatcherServlet核心servlet的url-pattern为"/xx",这就导致所有的请求都会经过DispatcherServlet。因此,在项目中就需要单独对静态资源处理进行配置。参考文章
WebMvcConfigurer配置静态资源处理有两种种方式:

    /**
     * 方式一:使用默认servlet处理静态资源
     */
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        //启用默认servlet支持
        configurer.enable();
    }

    /**
     * 方式二:使用spring mvc处理静态资源
     * @param registry
     */
     
    // 可通过配置文件来配置静态文件路径
    @Value("${file.staticFilePath}")
    private String filePath;
    
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    	// 默认的资源映射需要填写,不然不能正常访问
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        //配置外部资源目录的映射,/image目录为前端访问的路径,后面配置静态资源的绝对路径
        registry.addResourceHandler("/image/**").addResourceLocations("file:"+uploadFolder);
   		//调用基类的方法
        super.addResourceHandlers(registry);
    }

2.4、addFormatters

可以自定义转化器与数据库做交互或者可以将时间按照要求配置格式,比如URL传如userNameuserId,经过转化器可以拿到一个User对象。参考文章

2.5、addInterceptors

较为常用的方法,可自定义编写拦截器,并指定拦截路径。参考文章

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle,请求执行前执行");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle,请求结束后执行");
    }
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion,整个请求(DispatcherServlet渲染了视图执行)结束后执行");
    }
}

然后编写自定义配置类,如下:

@Configuration
public class MyConfigurer implements WebMvcConfigurer {
    @Bean
    public MyInterceptor getMyInterceptor(){
        return  new MyInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.getMyInterceptor())
        .addPathPatterns("/login","/configurePathMatch");
    }
}

运行结果:

preHandle,请求执行前执行
执行业务需求
postHandle,请求结束后执行
afterCompletion,整个请求(DispatcherServlet渲染了视图执行)结束后执行

2.6、addReturnValueHandlers

当使用者在需要Controller的返回对象作统一的封装时,可以配置addReturnValueHandlers方法,返回统一格式。ResponseBody,Convertor或者View,也可以实现。参考文章

/**
 * 统一的结果封装类 ResultInfo,并序列化成json
 */
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, 
							NativeWebRequest webRequest) throws Exception {
    ResultInfo resultInfo = new ResultInfo<>(ResultInfo.OK, "success", returnValue);
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    response.addHeader("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
    response.getWriter().append(JSON.toJSONString(resultInfo));
}

2.7、extendHandlerExceptionResolvers

在项目中,我们无可避免的会出现异常,而且这些异常并没有进行捕获,经常出现的bug如空指针异常等等。那么我们就需要有对异常的全局处理,进行兜底处理,从而避免不可预料的问题。extendHandlerExceptionResolvers方法可以添加自定义的或者修改已配置默认的HandlerExceptionResolver参考文章

    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 自定义异常处理器一般请放在首位
        exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
            @Override
            protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
                Object handler, Exception ex) {
                ModelAndView mv = new ModelAndView();
                MappingJackson2JsonView view = new MappingJackson2JsonView();
                view.setJsonPrefix("fsxJson"); // 设置JSON前缀,有的时候很好用的哦
                //view.setModelKey(); // 让只序列化指定的key
                mv.setView(view);

                // 这样添加key value就非常方便
                mv.addObject("code", "100001");
                mv.addObject("message", "业务异常,请联系客服处理");
                return mv;
            }
        });
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值