extends WebMvcConfigurationSupport 和 implements WebMvcConfigurer之间的区别

事情是这样的,springboot项目中原有一个类是 implements WebMvcConfigurer这样的

package com.davidhu.shopguide.web.config;

//import javax.annotation.Resource;

//import com.davidhu.shopguide.web.interceptor.CorsInterceptor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;


/**
 * @author <a href="hugaoxiang8619@adpanshi.com">hugaoxiang</a>
 * @version V1.0.1
 * @Description
 * @date 2019年5月9日 下午7:16:15
 */
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {


//    @Resource
//    private CorsInterceptor corsInterceptor;

//    @Override
//    public void addInterceptors(InterceptorRegistry registry) {
//
//        registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
//
//
//    }


//    @Override
//    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//        registry.addResourceHandler("/api/doc/**").addResourceLocations("/api/doc/");
//    }

    @Override
    public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
        // Turn off suffix-based content negotiation
        configurer.favorPathExtension(false);
    }


    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();

        List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();

        MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter();

        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();

        List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();

        MediaType jsonMedis = new MediaType(MediaType.APPLICATION_JSON, Charset.forName("UTF-8"));

        supportedMediaTypes.add(jsonMedis);

        jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
        converters.add(jsonConverter);
        supportedMediaTypes = new ArrayList<MediaType>();
        MediaType xmlMedia = new MediaType(MediaType.TEXT_XML, Charset.forName("UTF-8"));
        supportedMediaTypes.add(xmlMedia);

        xmlConverter.setSupportedMediaTypes(supportedMediaTypes);

        converters.add(xmlConverter);

        adapter.setMessageConverters(converters);

        return adapter;
    }


    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

        Jackson2ObjectMapperFactoryBean factory = new Jackson2ObjectMapperFactoryBean();
        factory.setSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        factory.afterPropertiesSet();
        MappingJackson2HttpMessageConverter jackson2Converter = new MappingJackson2HttpMessageConverter();
        jackson2Converter.setObjectMapper(factory.getObject());
        converters.add(jackson2Converter);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).allowedHeaders("*");
    }
}

因为java后端的Long型数据返回前端时,js的数字会丢失精度,于是在configureMessageConverters中加上一个MappingJackson2HttpMessageConverter

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

//解决Long型在js里精度丢失
        MappingJackson2HttpMessageConverter jackson2Converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        jackson2Converter.setObjectMapper(objectMapper);

        converters.add(jackson2Converter);
        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

    }

但问题总是没有生效,看到网上有人用 extends WebMvcConfigurationSupport ,结果改了之后原先没有的跨域问题却出现了。addCorsMappings不生效了。

后来在github上有人讨论这个问题:https://stackoverflow.com/questions/17898606/difference-between-webmvcconfigurationsupport-and-webmvcconfigureradap

 

The answer is in the doc you referenced above:

If the customization options of WebMvcConfigurer do not expose something you need to configure, consider removing the @EnableWebMvc annotation and extending directly from WebMvcConfigurationSupport overriding selected @Bean methods

In short, if @EnableWebMvc works for you, there is no need to look any further.

也就是 @EnableWebMvc 会做很多默认设置,要自定义的话需要把@EnableWebMvc去掉然后extends WebMvcConfigurationSupport,我这里虽然改成了WebMvcConfigurationSupport,但没有把 @EnableWebMvc去掉所以跨域不生效了。

 但是这样改要注意,有可能会出现之前没有的问题,这次extends WebMvcConfigurationSupport之后发现RequestMappingHandlerAdapter的配置也失效了。之前配置RequestMappingHandlerAdapter是为解决不支持xml/text的请求的。

看WebMvcConfigurationSupport代码,这里已经配置了一个 RequestMappingHandlerAdapter


/**
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
* <ul>
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
 @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcValidator") Validator validator) {

        RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
        adapter.setContentNegotiationManager(contentNegotiationManager);
        adapter.setMessageConverters(getMessageConverters());
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
        adapter.setCustomArgumentResolvers(getArgumentResolvers());
        adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

        if (jackson2Present) {
            adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
            adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
        }

        AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
        configureAsyncSupport(configurer);
        if (configurer.getTaskExecutor() != null) {
            adapter.setTaskExecutor(configurer.getTaskExecutor());
        }
        if (configurer.getTimeout() != null) {
            adapter.setAsyncRequestTimeout(configurer.getTimeout());
        }
        adapter.setCallableInterceptors(configurer.getCallableInterceptors());
        adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

        return adapter;
    }

重点是黑体这句 adapter.setMessageConverters(getMessageConverters()); 支持的MediaType在MessageConverter里设置。

    /**
     * Provides access to the shared {@link HttpMessageConverter HttpMessageConverters}
     * used by the {@link RequestMappingHandlerAdapter} and the
     * {@link ExceptionHandlerExceptionResolver}.
     * <p>This method cannot be overridden; use {@link #configureMessageConverters} instead.
     * Also see {@link #addDefaultHttpMessageConverters} for adding default message converters.
     */
    protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
    /**
     * Override this method to add custom {@link HttpMessageConverter HttpMessageConverters}
     * to use with the {@link RequestMappingHandlerAdapter} and the
     * {@link ExceptionHandlerExceptionResolver}.
     * <p>Adding converters to the list turns off the default converters that would
     * otherwise be registered by default. Also see {@link #addDefaultHttpMessageConverters}
     * for adding default message converters.
     * @param converters a list to add message converters to (initially an empty list)
     */
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    /**
     * Override this method to extend or modify the list of converters after it has
     * been configured. This may be useful for example to allow default converters
     * to be registered and then insert a custom converter through this method.
     * @param converters the list of configured converters to extend
     * @since 4.1.3
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

这里的configureMessageConverters我自己已经实现,但这样做的结果是会 turns off the default converters

 

    /**
     * Adds a set of default HttpMessageConverter instances to the given list.
     * Subclasses can call this method from {@link #configureMessageConverters}.
     * @param messageConverters the list to add the default message converters to
     */
    protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter());
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new ResourceRegionHttpMessageConverter());
        if (!shouldIgnoreXml) {
            try {
                messageConverters.add(new SourceHttpMessageConverter<>());
            }
            catch (Throwable ex) {
                // Ignore when no TransformerFactory implementation is available...
            }
        }
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (!shouldIgnoreXml) {
            if (jackson2XmlPresent) {
                Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
                if (this.applicationContext != null) {
                    builder.applicationContext(this.applicationContext);
                }
                messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
            }
            else if (jaxb2Present) {
                messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
            }
        }

        if (jackson2Present) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
        else if (jsonbPresent) {
            messageConverters.add(new JsonbHttpMessageConverter());
        }
        else if (kotlinSerializationJsonPresent) {
            messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
        }

        if (jackson2SmilePresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
        }
        if (jackson2CborPresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
        }
    }

这里可以看到MappingJackson2XmlHttpMessageConverter已经被添加,而且点进去看:

    /**
     * Construct a new {@code MappingJackson2XmlHttpMessageConverter} with a custom {@link ObjectMapper}
     * (must be a {@link XmlMapper} instance).
     * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
     * @see Jackson2ObjectMapperBuilder#xml()
     */
    public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, new MediaType("application", "xml", StandardCharsets.UTF_8),
                new MediaType("text", "xml", StandardCharsets.UTF_8),
                new MediaType("application", "*+xml", StandardCharsets.UTF_8));
        Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required");
    }

原来 default converters已经支持了xml/text这样的请求。是因为跳过了默认设置所以没生效。所以接下来要做的就是去掉configureMessageConverters。那Long型精度问题怎么解决?原来是在configureMessageConverters里设置的。可以

重写extendMessageConverters这个方法,下面是最终代码:

package com.davidhu.shopguide.web.config;

//import javax.annotation.Resource;

//import com.davidhu.shopguide.web.interceptor.CorsInterceptor;

import com.davidhu.shopguide.web.interceptor.CorsInterceptor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.*;
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;


/**
 * @author <a href="hugaoxiang8619@adpanshi.com">hugaoxiang</a>
 * @version V1.0.1
 * @Description
 * @date 2019年5月9日 下午7:16:15
 */
@Configuration
public class WebMvcConfig  extends WebMvcConfigurationSupport {


    @Override
    public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
        // Turn off suffix-based content negotiation
        configurer.favorPathExtension(false);
    }


    //用configureMessageConverters会使默认配置失效,用extendMessageConverters解决Long型精度丢失问题
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for(HttpMessageConverter converter: converters) {
            if(converter instanceof MappingJackson2HttpMessageConverter) {
                ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
                SimpleModule simpleModule = new SimpleModule();
                simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
                simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
                objectMapper.registerModule(simpleModule);
                ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper);
            }
        }
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).allowedHeaders("*").maxAge(3600);
    }
}

=========================
经过这番改动,又发现以前好的上传微信收款码功能返回的是个xml不是json。因为这个请求的Accept:*/*,只能在相应接口加上
produces = {MediaType.APPLICATION_JSON_VALUE}

 

posted @ 2022-06-29 02:13  zjhgx  阅读(562)  评论(0编辑  收藏  举报