使用WebMvcConfigurer接口定制Spring MVC功能

Spring Boot 抛弃了传统 xml 配置文件,通过配置类(标注 @Configuration 的类,相当于一个 xml 配置文件)以 JavaBean 形式进行相关配置。

Spring Boot 对 Spring MVC 的自动配置可以满足我们的大部分需求,但是我们也可以通过自定义配置类(标注 @Configuration 的类)并实现 WebMvcConfigurer 接口来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。

SpringBoot 1.5 及以前是通过继承 WebMvcConfigurerAdapter 抽象类来定制 Spring MVC 配置的,但在 SpringBoot 2.0 后,WebMvcConfigurerAdapter 抽象类就被弃用了,改为实现 WebMvcConfigurer 接口来定制 Spring MVC 配置。

WebMvcConfigurer接口

WebMvcConfigurer 是一个基于 Java  8 的接口,该接口定义了许多与 Spring MVC 相关的方法,其中大部分方法都是 default 类型的,且都是空实现。因此我们只需要定义一个配置类实现 WebMvcConfigurer 接口,并重写相应的方法便可以定制 Spring MVC 的配置。

//HandlerMappings 路径的匹配规则。
default void configurePathMatch(PathMatchConfigurer configurer) {}

//内容协商策略(一个请求路径返回多种数据格式)。
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}

//处理异步请求。
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}

//这个接口可以实现静态文件可以像 Servlet 一样被访问。
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}

//添加格式化器或者转化器。
default void addFormatters(FormatterRegistry registry) {}

//添加 Spring MVC 生命周期拦截器,对请求进行拦截处理。
default void addInterceptors(InterceptorRegistry registry) {}

//添加或修改静态资源(例如图片,js,css 等)映射;
//Spring Boot 默认设置的静态资源文件夹就是通过重写该方法设置的。
default void addResourceHandlers(ResourceHandlerRegistry registry) {}	

//处理跨域请求。
default void addCorsMappings(CorsRegistry registry) {}

//主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等
default void addViewControllers(ViewControllerRegistry registry) {}

//配置视图解析器,将 Controller 返回的字符串(视图名称),转换为具体的视图进行渲染。
default void configureViewResolvers(ViewResolverRegistry registry) {}

//添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持;
//要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}

//添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持;
//要自定义处理返回值的内置支持,请直接配置 RequestMappingHandlerAdapter。
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}

//用于配置默认的消息转换器(转换 HTTP 请求和响应)。
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}

//直接添加消息转换器,会关闭默认的消息转换器列表;
//实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}

//配置异常解析器。
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}

//扩展或修改默认的异常解析器列表。
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}

在 Spring Boot 项目中,我们可以通过以下 2种形式定制 Spring MVC:

  • 扩展 Spring MVC
  • 全面接管 Spring MVC

扩展 Spring MVC 

如果 Spring Boot 对 Spring MVC 的自动配置不能满足我们的需要,我们还可以通过自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类(标注 @Configuration,但不标注 @EnableWebMvc 注解的类),来扩展 Spring MVC。这样不但能够保留 Spring Boot 对 Spring MVC 的自动配置,享受 Spring  Boot 自动配置带来的便利,还能额外增加自定义的 Spring MVC 配置。

全面接管 Spring MVC 

在一些特殊情况下,我们可能需要抛弃 Spring Boot 对 Spring MVC 的全部自动配置,完全接管 Spring MVC。此时我们可以自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类,并在该类上标注 @EnableWebMvc 注解,来实现完全接管 Spring MVC

注意:完全接管 Spring MVC 后,Spring Boot 对 Spring MVC 的自动配置将全部失效。

configurePathMatch

HandlerMappings 路径的匹配规则。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

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

        // 这个配置需要传入一个UrlPathHelper对象,UrlPathHelper是一个处理url地址的帮助类,
        // 他里面有一些优化url的方法
        // 比如:getSanitizedPath,就是将// 换成/  所以我们在输入地址栏的时候,//也是没有问题的,
        // 这里使用springmvc默认的就可以了,如果想要深入了解,那么我们后续在深入
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        configurer.setUrlPathHelper(urlPathHelper);

        // 路径匹配器 PathMatcher是一个接口,springmvc默认使用的是AntPathMatcher
        // 这里也就不深入了,使用springmvc默认的就可以,如果想要深入了解,那么我们后续在深入,查看AntPathMatcher的源码
        // configurer.setPathMatcher();

        // 配置路径前缀
        // 下面这样写的意思是:对含有AdminController注解的controller添加/admin地址前缀
        // 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.harvey.tim.web.controller"));
        // configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.admin"));
        // configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.app"));
    }
}

configureContentNegotiation

内容协商策略(一个请求路径返回多种数据格式)。

内容协商机制这个太专业的名称,头一次听的话,估计是无法理解它其中的含义的。

简单点说,就是客户端向服务端发送一个请求,然后服务端给客户端返回什么格式的数据的,是需要两端进行协商的,既然是协商,那么它们有什么协议或者规则呢?

一般现在服务端返回的数据基本都是json格式的数据,以前返回的是xml,那么现在如果要返回xml格式的数据,spring mvc也是提供有方法的。

@RequestMapping系列注解中produces可以指定返回得格式,(@GetMapping是@RequestMapping得一种变形)

写法:

@GetMapping(value = “xxx”,produces = MediaType.APPLICATION_XML_VALUE)

注意:在返回的实体中指定@XmlRootElement(name = "xxx"),指定xml的根元素。

@GetMapping(value = "getUser",produces = MediaType.APPLICATION_XML_VALUE)
public User getUser(){
    User user = new User();
    user.setName("我是admin用户");
    return user;
}

返回实体:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "user")
public class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

最后得到的结果就是:

<user>
    <name>我是admin用户</name>
</user>

但是这只是服务端指定的,客户端不能够决定,如果客户端请求这个路径需要换成json格式的数据,那么是做不到的,所以就有了内容协商机制,也就是可以相互协商,客户端需要什么样的格式,给个参数说明,服务端就可以返回客户端需要的数据了。

在springmvc中,我们需要达到内容协商机制,那么就需要覆盖WebMvcConfigurer中的configureContentNegotiation方法

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 是否通过请求参数来决定返回数据,默认为false,当false的时候,只能是@RestController默认的json格式
        configurer.favorParameter(true)
            //这个方法过期了,这里不讨论
            //.favorPathExtension(true)
            .ignoreAcceptHeader(true) // 不检查Accept请求头
            .parameterName("zyMediaType")// 参数名称,就是通过什么样的参数来获取返回值
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml",MediaType.APPLICATION_XML);
        // admin/getUser?zyMediaType=xml 时返回结果为xml格式
        // admin/getUser?zyMediaType=json 时返回结果为json格式
    }
}

我们再创建一个控制器:(注意,返回的实体中,在类上面必须打上@XmlRootElement注解,不然在请求xml格式的时候会报错):

@GetMapping(value = "testMediaType")
public User testMediaType(){
    User user = new User();
    user.setName("testMediaType");
    return user;
}

configureAsyncSupport

处理异步请求。

对异步处理调优的一些参数配置,Spring默认异步线程是不使用线程池的,可以自己设定一些可以重用的线程。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        //注册callable拦截器
        configurer.registerCallableInterceptors();
        //注册deferredResult拦截器
        configurer.registerDeferredResultInterceptors();
        //异步请求超时时间
        configurer.setDefaultTimeout(10000);
        //设定异步请求线程池callable等, spring默认线程不可重用
        configurer.setTaskExecutor(taskExecutor());
    }

    private AsyncTaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("system");
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setMaxPoolSize(50);
        threadPoolTaskExecutor.setKeepAliveSeconds(20);
        //LinkedBlockingQueue
        threadPoolTaskExecutor.setQueueCapacity(1000);
        threadPoolTaskExecutor.setPrestartAllCoreThreads(true);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return threadPoolTaskExecutor;
    }
}

configureDefaultServletHandling

实现静态文件可以像 Servlet 一样被访问(一般也不会用)。

/**
 * 将请求的静态资源经由spring mvc交回web容器本身默认的servlet去处理,
 * @param configurer
 */
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
}

addFormatters

添加格式化器或者转化器。

Formatter继承树如下:

可以发现整个继承关系并不复杂,甚至可以说非常简单。只有一个抽象子类,AbstractNumberFormatter,这个类抽象了对数字进行格式化时的一些方法,它有三个子类,分别处理不同的数字类型,包括货币,百分数,正常数字。其余的子类都是直接实现了Formatter接口。其中我们比较熟悉的可能就是DateFormatter了。

addInterceptors

添加 Spring MVC 生命周期拦截器,对请求进行拦截处理。

我们在项目中,有时候需要监听一下从发出请求到后台处理渲染页面完成这一时间段内的生命周期,并根据实际业务加以限制,比如比较常用的就是拦截所以验证是否登录。
那么springmvc给我们提供了添加自定义生命周期拦截器的配置,那就是addInterceptors。

addResourceHandlers

添加或修改静态资源(例如图片,js,css 等)映射;

Spring Boot 默认设置的静态资源文件夹就是通过重写该方法设置的。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 添加静态资源路径,然后跟项目的路径进行映射
        // 当访问 http://localhost:8080/static/hello.html 时,会去类路径的static/html目录查找hello.html页面
        registry.addResourceHandler("/static/**","/**").addResourceLocations("classpath:/static/html/");
    }
}

addCorsMappings

处理跨域请求。

跨域这个词,在前后端分离项目中,应该是比较常见的一个词语。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 添加请求映射,就是对哪些地址进行跨域处理
                .allowedOrigins("*") // 特定来源的允许来源列表。这里配置*表示所以网站都可进行跨域,这里生产介意指定特定的地址
                .allowedHeaders("*") // 允许请求头携带的标题: "Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"
                .allowCredentials(true) // 浏览器是否携带凭证
                .allowedMethods("*") // 允许跨域的请求方式,可以进行指定:GET,POST
                .maxAge(3600); // 客户端缓存的时间,默认为1800(30分钟)
    }
}

addViewControllers

主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 这里前提得有404和500这一个html存在,不然它会返回默认的404页面
        // 我们在static/error目录下创建404.html和500.html
        // 直接访问 http://localhost/404 会去找 /static/error/404.html
        // 默认是forward方式的, 地址栏链接是是不会变的
        // forward方式:http://localhost/404
        // redirect方式:http://localhost/error/404.html
        // registry.addViewController("/500").setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR).setViewName("error/500.html");
        // registry.addViewController("/404").setStatusCode(HttpStatus.NOT_FOUND).setViewName("error/404.html");

        //注意:页面跳转需要考虑是否需要配合addResourceHandlers对静态资源进行处理
        registry.addViewController("/500").setViewName("redirect:/error/500.html");
        registry.addViewController("/404").setViewName("redirect:/error/404.html");
    }
}

configureViewResolvers

配置视图解析器,将 Controller 返回的字符串(视图名称),转换为具体的视图进行渲染。

从方法名称我们就能看出这个方法是用来配置视图解析器的,该方法的参数ViewResolverRegistry 是一个注册器,用来注册你想自定义的视图解析器等。ViewResolverRegistry 常用的几个方法:

(1)enableContentNegotiation()

该方法会创建一个内容裁决解析器ContentNegotiatingViewResolver ,该解析器不进行具体视图的解析,而是管理你注册的所有视图解析器,所有的视图会先经过它进行解析,然后由它来决定具体使用哪个解析器进行解析。具体的映射规则是根据请求的media types来决定的。

(2)UrlBasedViewResolverRegistration()

public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(prefix);
    resolver.setSuffix(suffix);
    this.viewResolvers.add(resolver);
    return new UrlBasedViewResolverRegistration(resolver);
}

方法会注册一个内部资源视图解析器InternalResourceViewResolver 显然访问的所有jsp都是它进行解析的。该方法参数用来指定路径的前缀和文件后缀,如:registry.jsp("/WEB-INF/jsp/", ".jsp");  对于以上配置,假如返回的视图名称是example,它会返回/WEB-INF/jsp/example.jsp给前端,找不到则报404。

(3)beanName()

public void beanName() {
    BeanNameViewResolver resolver = new BeanNameViewResolver();
    this.viewResolvers.add(resolver);
}

该方法会注册一个BeanNameViewResolver 视图解析器,这个解析器是干嘛的呢?它主要是将视图名称解析成对应的bean。什么意思呢?假如返回的视图名称是example,它会到spring容器中找有没有一个叫example的bean,并且这个bean是View.class类型的?如果有,返回这个bean。

(4)viewResolver()

public void viewResolver(ViewResolver viewResolver) {
    if (viewResolver instanceof ContentNegotiatingViewResolver) {
        throw new BeanInitializationException(
            "addViewResolver cannot be used to configure a ContentNegotiatingViewResolver. Please use the method enableContentNegotiation instead.");
    }
    this.viewResolvers.add(viewResolver);
}

这个方法想必看名字就知道了,它就是用来注册各种各样的视图解析器的,包括自己定义的。

addArgumentResolvers

添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持;

要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。

addReturnValueHandlers

添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持;

要自定义处理返回值的内置支持,请直接配置 RequestMappingHandlerAdapter。

configureMessageConverters

用于配置默认的消息转换器(转换 HTTP 请求和响应)。

向列表中添加转换器将关闭默认转换器注册,不覆盖默认转换器请使用extendMessageConverters。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //1.需要先定义一个 convert 转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

        //2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        // 不忽略对象属性中的null值
        fastJsonConfig.setSerializerFeatures(
                PrettyFormat,
                WriteNullListAsEmpty,
                WriteNullStringAsEmpty);
        //3、在convert中添加配置信息.
        fastConverter.setFastJsonConfig(fastJsonConfig);
        //4、将convert添加到converters当中.
        converters.add(fastConverter);
    }
	
}

extendMessageConverters

直接添加消息转换器,会关闭默认的消息转换器列表;

实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	//1.需要先定义一个 convert 转换消息的对象;
	FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

	//2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据;
	FastJsonConfig fastJsonConfig = new FastJsonConfig();
	// 不忽略对象属性中的null值
	fastJsonConfig.setSerializerFeatures(
			PrettyFormat,
			WriteNullListAsEmpty,
			WriteNullStringAsEmpty);
	//3、在convert中添加配置信息.
	fastConverter.setFastJsonConfig(fastJsonConfig);
	//4、将convert添加到converters当中.
	converters.add(fastConverter);
	// converters.add(0, fastConverter);
}

configureHandlerExceptionResolvers

配置异常解析器。

HandlerExceptionResolver顾名思义,就是处理异常的类,接口就一个方法,出现异常之后的回调,四个参数中还携带了异常堆栈信息:

@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

自定义全局异常:

@Slf4j
@Component
public class GlobalException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        log.info("系统发生异常");
        // 统一处理异常
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("message", "系统发生异常,请稍后重试");
        modelAndView.setViewName("/error/500.html");
        return modelAndView;
    }
}

添加自定义异常:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new GlobalException());
    }

}

extendHandlerExceptionResolvers

扩展或修改默认的异常解析器列表。

 

posted @ 2022-01-19 08:35  残城碎梦  阅读(1156)  评论(0编辑  收藏  举报