Spring-MVC配置和扩展

原创
2019/07/12 11:05
阅读数 1.1K

一、外部化配置(WebMvcConfigurer)

简绍

​ 对Spring MVC的默认行为进行变更,或者进行扩展,通常要通过WebMvcConfigurer进行配置。它定义了很多课供重写的方法,通过对方法进行重写,就可以实现我们想要的效果。

​ 在Spring Boot 2.x之前,WebMvcConfigurerAdapterWebMvcConfigurer的实现抽象类,可以通过继承WebMvcConfigurerAdapter重写它的方法进行配置。到SpringBoot2.x时代的时候, 被标记为了@Deprecated,通过实现WebMvcConfigurer接口,通过重写默认方法来进行配置。总体来说,这两种配置方式基本相同。

主要配置说明

addInterceptors注册拦截器

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(sleuthInterceptor);
        registry.addInterceptor(idempotencyOperationInterceptor)
            // 拦截所有请求    
            .addPathPatterns("/**")
            // 排除掉查询类的 POST 请求
            .excludePathPatterns("/**/search/**","xx");
}

​ 此方法用来专门注册拦截器(Interceptor),通过registry#addInterceptor进行拦截器的注册,拦截器必须是HandlerInterceptor的子类,在下面在 《对SpringMVC进行扩展 》会进行详细的说明。

addArgumentResolvers添加参数解析器

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
	resolvers.add(new PropertiesHandlerMethodArgumentResolver());
}

​ 此方法可以用来添加参数解析器(argumentResolver),通过resolvers#add进行添加参数解析器。注意,此处添加的Resolver优先级会低于系统内建的Resolver,如果想添加优先级高于内建的Resolver,可以通过requestMappingHandlerAdapter#setArgumentResolvers方法进行覆盖,在下面在 《对SpringMVC进行扩展 》会进行详细的说明。

addReturnValueHandlers添加返回值处理程序

@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {

}

​ 这个是用来配置,注意,这个配置和 addArgumentResolvers添加参数解析器 的配置类似,添加的自定义Handler优先级会低于系统内建的Handler,如果想添加优先级高于内建的Handler,需要通过requestMappingHandlerAdapter#setReturnValueHandlers方法进行覆盖,在下面在《对SpringMVC进行扩展 》会进行详细的说明。

configureMessageConverters配置消息转换器

@Override 
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
     MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = jsonConverter.getObjectMapper();
        // 解决 18位 Long 类型转换成 JSON 时造成的数据丢失
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        converters.add(jsonConverter);
}

​ 消息转换器可以对接收或返回的消息进行转换,比如解决中文乱码、json中Long精度丢失等,我们可以使用系统提供的消息转换器,或我们自定义转换器,通过这个方法converters#add进行注册使用,会把这里添加的converter依次放在最高优先级(List的头部)。有多个自定义的converter时,可以改变相互之间的顺序,但是都在内置的converter前面。

这个配置的使用场景比较常见,在下面在《对SpringMVC进行扩展 》会进行详细的说明。

extendMessageConverters扩展消息转换器

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
     MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = jsonConverter.getObjectMapper();
        // 解决 18位 Long 类型转换成 JSON 时造成的数据丢失
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        converters.add(jsonConverter);riverroad
}

​ 这个与上个 configureMessageConverters 类似,不同点在于这个方法在 configureMessageConverters 之后运行,这时系统内置的converter已经添加完毕,此时我们同样可以可以通过改变converters列表中的converter实现处理顺序的变更。

addFormatters格式化配置

@Override
public void addFormatters(FormatterRegistry registry) {
}

​ 类型转换器和格式化器,部分功能和消息转换器相似。不同它的源类型必须是一个String, 目标类型是java类型。在下面在《对SpringMVC进行扩展 》 会进行说明。

addCorsMappings设置跨域

@Override
public void addCorsMappings(CorsRegistry registry) {
     registry.addMapping("/**")
            .allowedOrigins("*")
            .allowCredentials(true)
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .maxAge(3600);
}

这个是关于跨域问题的设置

addResourceHandlers自定义资源映射

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //将请求映射到指定的位置
    registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
}

可以设置静态资源的映射,这个在目前开发中用的不多。

configurePathMatch配置路径匹配

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    // 表示不区分URL的最后一个字符是否是斜杠/
    configurer.setUseSuffixPatternMatch(true);
}

​ 让开发人员可以根据需求定制URL路径的匹配规则,常用的setUseSuffixPatternMatch方法,用来设置使用后缀模式匹配的方式,比如设置为true的话(默认为true),URL后面加个斜杠并不影响路径访问,例如“/user”等同于“/user/。如果需要定制path匹配发生的过程,可以提供自己定制的PathMatcherUrlPathHelper,但是这种需求不常见。

configureViewResolvers视图解析器

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
    InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        //请求视图文件的前缀地址
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        //请求视图文件的后缀
        internalResourceViewResolver.setSuffix(".jsp");
    	registry.viewResolver(internalResourceViewResolver);
}

​ 这个对我们来说很熟悉,配置html、Jsp页面视图时就会用到InternalResourceViewResolver配置类,然后设置preffixsuffix参数进行配置视图文件路径前缀与后缀,不过现在目前开发中开发全部转向了前后端分离方式,这个已经配置几乎不会在遇到了。

二,对SpringMVC进行扩展

简绍

Spring MVC提供众多的常用的功能,基本上能满足我们日常的使用,但有时候我们会有特殊的需求,而Spring MVC没有默认的支持,此时就是需要我们对它进行扩展,下面就对Spring MVC常见的扩展方式进下介绍说明。

扩展点说明:

  1. HandlerMapping(处理请求映射)

    处理请求的映射。保存请求URL到具体的方法的映射关系,我们可以编写任意的HandlerMapping实现类,依据任何策略来决定一个web请求到 HandlerExecutionChain 对象的生成。通常我们不需要对他进行扩展。

  2. HandlerAdapter(处理适配器)

    真正调用Controller的地方,其实就是适配各种Controller。HandlerAdapter就是你可以提供自己的实现类来处理handler对象,我们一般不会对他进行扩展。

  3. HandlerInterceptor (接口拦截器)

    通过自定义拦截器,我们可以在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情,这个是我们在Spring MVC中用到最多的一种扩展方式。

  4. HandlerMethodArgumentResolver(处理方法参数解释器)

    接收到请求参数的时候,会通过它的不同实现类对参数进行处理,通过对它进行扩展,能让我们实现对参数进行自定义操作,之前超哥写过一个自定注入Header参数到接收类的参数解释器,这个是我们对Spring MVC常用的扩展方式之一。

  5. HandlerMethodReturnValueHandler(处理方法返回值处理器)

    程序方法运行结束后的返回值进行处理,转换成我们所需要的格式写入返回请求。

  6. Converter(类型转换器)

    对数据类型进行转换,主要是用到的是 HttpMessageConverter( http消息转换器),用来对请求和响应的数据进行处理。

  7. Formatter(格式化器)

    对接收到的参数进行处理和格式化,只能应用于输入为String类型的数据。

  8. ViewResolver(视图解析器)

    完成从ModelAndView到真正的视图的过程,ViewResolver接口是在DispatcherServlet中进行调用的,当DispatcherServlet调用完Controller后,会得到一个ModelAndView对象,然后DispatcherServlet会调用render方法进行视图渲染。在目前前后端分离的情况下,这个我们一般不会进行扩展。

  9. HandlerExceptionResolver(异常处理)

    用不到,略

HandlerInterceptor (接口拦截器)

HandlerInterceptor 是一个接口,用过实现这个接口,我们可以实现出一个自定义的拦截器,这个接口有三个方法,如下图所示:

public interface HandlerInterceptor {
    //在业务处理器处理请求之前被调用,其返回值表示是否中断后续操作
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception{
    }
    // 在业务处理器处理请求完成之后,生成视图之前执行
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception{
    }
     // 在DispatcherServlet完全处理完请求之后被调用,可用于清理资源,日志打印
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception{
    }
}

最后将拦截器通过WebMvcConfigurer配置类中的 addInterceptors 方法进行注册,即可生效。

HandlerMethodArgumentResolver(处理方法参数解释器)

​ 处理方法参数解释器可以对方法参数进行处理,通过它可以获取该方法参数上的一些信息 ,如方法参数中的注解信息等,根据获取到的信息对参数数据进行处理。通过实现这个接口或继承它的实现类,就可以实现一个自定义的处理方法参数解释器,然后将Resolver通过WebMvcConfigurer配置类中的 addArgumentResolvers 方法进行注册,即可生效。

package org.springframework.web.method.support;
public interface HandlerMethodArgumentResolver {
	//判断是否使用这个组件
	boolean supportsParameter(MethodParameter parameter);
	//对参数进行处理
	Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

​ 在Spring MVC中,系统已经提供了多种不同用处的 处理方法参数解释器 ,当有请求到来时,系统首先会从系统提供的解释器中寻找合适的Resolver,如果匹配到,才会查找注册的自定义实现Resolver,所以通常我们要创建自定义注解放在要处理的参数上,方便使用自定义的Resolver进行处理。

​ 选择 Resolver 进行处理的流程:

// class:HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    //先从缓存中查找
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    // 未找到默认的Resolver
    if (result == null) {
        //遍历Resolver
        for (HandlerMethodArgumentResolver methodArgumentResolver : 		this.argumentResolvers) {
            //判断是否支持此Resolver
            if (methodArgumentResolver.supportsParameter(parameter)) {
                result = methodArgumentResolver;
                //写入缓存
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

​ 如果我们确实需要配置高于系统内置的自定义Resolver的时候,可以通过如下的方式进行配置:

​ 在WebConfig配置类中,通过@PostConstruct注解在一个方法上,可以让这个方法在Bean依赖注入完成后被自动调用,然后在这个方法里对 Resolver 集合进行重新设置,就可以实现将自定义 Resolver 优先级提升到内置的之前了。

@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
        List<HandlerMethodArgumentResolver> resolvers = 
            requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newResolvers = 
            new ArrayList<>(resolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
        newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
        // 添加 已注册的 Resolver 对象集合
        newResolvers.addAll(resolvers);
        // 重新设置 Resolver 对象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
    }
}

题外话:可以通过实现自定义自动把Tid一类信息注入到参数里,下面用一个简单的demo进行展示:

按需把请求头参数写入方法参数的Spring MVC扩展处理器

HandlerMethodReturnValueHandler(处理方法返回值处理程序)

HandlerMethodReturnValueHandler可以用来对程序方法运行结束后的返回值进行处理,转换成我们所需要的格式并返回。

package org.springframework.web.method.support;

public interface HandlerMethodReturnValueHandler {
	//检验是否支持本处理器处理
	boolean supportsReturnType(MethodParameter returnType);
	//具体处理方法
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 
        throws Exception;
}

​ 与HandlerMethodArgumentResolver一样,系统已经提供了多种不同的 Handler 了,自定义的Handler 添加进去优先级都会在内置Handler 之后。常规配置的方式通过 WebConfig 进行配置,如果要配置高于系统内置的自定义 Handler 时,可以参照下方的配置方式,与参数解释器的配置方式基本相同。

@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
        List<HandlerMethodReturnValueHandler> handlers = 
            requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newHandlers = 
            new ArrayList<>(handlers.size() + 1);
        // 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
        newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
        // 添加 已注册的 Handler 对象集合
        newHandlers.addAll(handlers);
        // 重新设置 Handler 对象集合
        requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
    }
}

​ 选择 Handler 进行处理的流程:

// class: HandlerMethodReturnValueHandlerComposite
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
    //遍历Handler
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        //找到并返回
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

HttpMessageConverter(Http消息转换器)

HttpMessageConveter 是用来处理请求和响应数据的,我们经常会用到@RequestBody@ResponseBody,通过这两个注解,可以在 Controller 中直接使用 Java 对象作为请求参数和返回内容,而完成这之间转换作用的便是HttpMessageConverter。Spring 为我们内置了大量的HttpMessageConverter,例如, MappingJackson2HttpMessageConverterStringHttpMessageConverter 等。

已包含常用的消息转换器:

名称 作用 读支持MediaType 写支持MediaType
MappingJackson2HttpMessageConverter 使用Jackson的ObjectMapper转换Json数据 application/json application/json
StringHttpMessageConverter 数据与String类型的相互转换 text/* text/plain
ByteArrayHttpMessageConverter 数据与字节数组的相互转换 / application/octet-stream

​ 下面是HttpMessageConverter 接口,实现Http消息转换就必须实现这个接口,不过我们通常不会直接实现这个接口,而是通过继承它的子类进行扩展处理,默认提供的Converter对视通过继承它的抽象类进行扩展。

public interface HttpMessageConverter<T> {
	//判断是否对接收请求进行处理
	boolean canRead(Class<?> clazz, MediaType mediaType);
	//判断是否对响应进行处理
	boolean canWrite(Class<?> clazz, MediaType mediaType);
	//返回此转换器支持的MediaType对象列表
	List<MediaType> getSupportedMediaTypes();
	//对接收的请求进行处理
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;
	//对响应进行处理
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;
}

​ 通常被继承的抽象类是AbstractHttpMessageConverter,例如常用的MappingJackson2HttpMessageConverter就是对它的继承,这样能减少编写处理方法的工作量,如果我们要进行扩展,通常会在系统已有Converter上进行配置,少数时候会进行继承扩展。

​ 配置方式见上方 configureMessageConverters 配置消息转换器和 extendMessageConverters 扩展消息转换器的说明。

​ 注:目前在目前开发中的开发中,对请求的消息和响应进行完全自定义的场合不多,多数都是对已有的MessageConverter进行设置和增强。

Formatter(格式化器)

FormatterConverter类似, 是将一种类型转换成另一种类型, 但是, Formatter的源类型必须是一个String, 目标类型是java类型。在SpringMVC中,处理的多数输入都是文本方式的输入,因此, 选择Formatter比选择Converter更合适。

Formatter接口的结构如下:

package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
    
}
public interface Printer<T> {
    String print(T var1, Locale var2);
}
public interface Parser<T> {
    T parse(String var1, Locale var2) throws ParseException;
}

​ 这里的T表示输入字符串要转换的目标类型。parse方法利用指定的Locale将一个String解析成目标类型。print方法相反,它是返回目标对象的字符串表示法。

​ 配置方式见上方addFormatters

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部