SpringBoot - SpringMVC的默认配置与修改

简介: SpringBoot - SpringMVC的默认配置与修改

【1】SpringBoot对SpringMVC的默认配置


如下图所示,SpringBoot提供了SpringMVC的自动配置帮助进行Web项目开发。


翻译如下:


① 配置了视图解析器bean;

② 静态资源访问支持,包括WebJars;

③ 注册了转换器和格式化器;

④ 请求响应信息转换器;

⑤ 注册信息校验解析器–定义错误代码生成规则;

⑥ 静态页面index.html支持(欢迎页);

⑦ 项目浏览器图标支持;

⑧ 可配置web数据绑定初始化器bean的自动使用。

【2】Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.


ContentNegotiatingViewResolver:组合所有的视图解析器的,源码分析如下。

首先在WebMvcAutoConfiguration配置类中添加组件ContentNegotiatingViewResolver

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(
            beanFactory.getBean(ContentNegotiationManager.class));
    //注意这里
    // ContentNegotiatingViewResolver uses all the other view resolvers to locate
    // a view so it should have a high precedence
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

ContentNegotiatingViewResolver类如下:

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
        implements ViewResolver, Ordered, InitializingBean {
    private ContentNegotiationManager contentNegotiationManager;
    private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
    private boolean useNotAcceptableStatusCode = false;
    private List<View> defaultViews;
    private List<ViewResolver> viewResolvers;
    // 这里有一个视图解析器list
    private int order = Ordered.HIGHEST_PRECEDENCE;
    //...
}

initServletContext方法如下:

@Override
    protected void initServletContext(ServletContext servletContext) {
        Collection<ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
        if (this.viewResolvers == null) {
            this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.size());
            for (ViewResolver viewResolver : matchingBeans) {
                if (this != viewResolver) {
                    this.viewResolvers.add(viewResolver);
                }
            }
        }
        else {
            for (int i = 0; i < viewResolvers.size(); i++) {
                if (matchingBeans.contains(viewResolvers.get(i))) {
                    continue;
                }
                String name = viewResolvers.get(i).getClass().getName() + i;
                getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
            }
        }
        if (this.viewResolvers.isEmpty()) {
            logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
                    "'viewResolvers' property on the ContentNegotiatingViewResolver");
        }
        AnnotationAwareOrderComparator.sort(this.viewResolvers);
        this.cnmFactoryBean.setServletContext(servletContext);
    }

即,拿到系统中所有的视图解析器!

那么如何定制自己的视图解析器呢?

可以自己给容器中添加一个视图解析器,SpringBoot 会自动的将其组合进来。


示例如下(使用@Bean在容器中添加自己的视图解析器):

    @Bean
    public ViewResolver myViewReolver(){
        return new MyViewResolver();
    }

【3】Automatic registration of Converter, GenericConverter, and Formatter beans.


Converter:转换器;如页面表单数据到后台public String hello(User user):类型转换使用Converter。

Formatter 格式化器; 如页面表单中日期2017.12.17到后台Date。

源码如下:

@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
public Formatter<Date> dateFormatter() {
    return new DateFormatter(this.mvcProperties.getDateFormat());
}
@Override
public void addFormatters(FormatterRegistry registry) {
    for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
        registry.addConverter(converter);
    }
    //自己添加的格式化器转换器,我们只需要放在容器中即可
    for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
        registry.addConverter(converter);
    }
    for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
        registry.addFormatter(formatter);
    }
}
private <T> Collection<T> getBeansOfType(Class<T> type) {
    return this.beanFactory.getBeansOfType(type).values();
}

其中默认的日期格式如下:

    /**
     * Date format to use (e.g. dd/MM/yyyy).
     */
    private String dateFormat;

通常需要在配置文件中修改:

spring.mvc.date-format=yyyy-MM-dd//根据需要自定义

【4】HttpMessageConverters

SpringMVC用来转换Http请求和响应的,如将返回值User对象转换为JSON类型。SpringBoot默认从容器中获取所有的HttpMessageConverter。


如果自己给容器中添加HttpMessageConverter,则只需将自己的组件注册到容器中。

官方文档说明如下:


Spring MVC uses the HttpMessageConverter interface to convert HTTP requests and responses. Sensible defaults are included out of the box. For example, objects can be automatically converted to JSON (by using the Jackson library) or XML (by using the Jackson XML extension, if available, or by using JAXB if the Jackson XML extension is not available). By default, strings are encoded in UTF-8.


If you need to add or customize converters, you can use Spring Boot’s HttpMessageConverters class, as shown in the following listing:

import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration
public class MyConfiguration {
    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }
}

SpringBoot中,返回类型为type bean或map时,将会自动转换为JSON,使用的转换器为MappingJackson2HttpMessageConverter,源码如下:

/**
 * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and
 * write JSON using <a href="http://wiki.fasterxml.com/JacksonHome">Jackson 2.x's</a> {@link ObjectMapper}.
 *
 * <p>This converter can be used to bind to typed beans, or untyped {@code HashMap} instances.
 *
 * <p>By default, this converter supports {@code application/json} and {@code application/*+json}
 * with {@code UTF-8} character set. This can be overridden by setting the
 * {@link #setSupportedMediaTypes supportedMediaTypes} property.
 *
 * <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
 *
 * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
 *
 * @author Arjen Poutsma
 * @author Keith Donald
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @author Sebastien Deleuze
 * @since 3.1.2
 */
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
//...
}

【5】修改SpringBoot的默认配置

首先说明一下几点:


1SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;


2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置;


3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置。


其次,官网文档说明如下:


If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.


If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.


If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.


5.1 扩展默认配置

如下,编写一个配置类,是WebMvcConfigurerAdapter类型,不能标注@EnableWebMvc:

@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    //重写方法
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
       // super.addViewControllers(registry);
       //浏览器发送 /success 请求来到 success
        registry.addViewController("/success").setViewName("success");
    }
    //添加组件
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

其中viewController对应以前SpringMVC的xml配置如下:

<mvc:view‐controller path="/success" view‐name="success"/>

原理分析源码如下:

① WebMvcAutoConfiguration是SpringMVC的自动配置类;

② 在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)

@Configuration
@Import(EnableWebMvcConfiguration.class)//这里
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
    private static final Log logger = LogFactory
            .getLog(WebMvcConfigurerAdapter.class);
    private final ResourceProperties resourceProperties;
    private final WebMvcProperties mvcProperties;
    private final ListableBeanFactory beanFactory;
    private final HttpMessageConverters messageConverters;
    final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
    //...
}

EnableWebMvcConfiguration类如下:

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
    //...
}

同样是个配置类,继承DelegatingWebMvcConfiguration ,其类如下:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//拿到系统中所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
    if (!CollectionUtils.isEmpty(configurers)) {
        this.configurers.addWebMvcConfigurers(configurers);
    }
}
//...
}

WebMvcConfigurerAdapter是WebMvcConfigurer的实现类,WebMvcAutoConfigurationAdapter继承自WebMvcConfigurerAdapter。


总结 : 容器中所有的WebMvcConfigurer都会一起起作用,我们的配置类也会被调用。


根据源码分析,还可以在自定义配置类中使用如下方式进行配置扩展:

 //所有的WebMvcConfigurerAdapter组件都会一起起作用
 @Bean //将组件注册在容器
 public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
     WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
         @Override
         public void addViewControllers(ViewControllerRegistry registry) {
             registry.addViewController("/").setViewName("login");
             registry.addViewController("/index.html").setViewName("login");
             registry.addViewController("/main.html").setViewName("dashboard");
         }
         //注册拦截器
         @Override
         public void addInterceptors(InterceptorRegistry registry) {
             //super.addInterceptors(registry);
             //静态资源;  *.css , *.js
             //SpringBoot已经做好了静态资源映射
//                registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
//                        .excludePathPatterns("/index.html","/","/user/login");
         }
     };
     return adapter;
 }

5.2 全面接管SpringMVC

需要在配置类中添加@EnableWebMvc即可,所有的SpringMVC的自动配置都失效了,通常不这么干。

//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    // super.addViewControllers(registry);
    //浏览器发送 /success 请求来到 success
    registry.addViewController("/success").setViewName("success");
    }
}

为什么添加该注解,就会将SpringBoot对SpringMVC的默认自动配置失效呢?

分析源码如下 :


① @EnableWebMvc的核心

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
    //..
}

② DelegatingWebMvcConfiguration

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
    //...
}

很眼熟对不对,这货继承自WebMvcConfigurationSupport 。

③ 查看WebMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
        WebMvcConfigurerAdapter.class })
//注意这里!!!
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

只有在WebMvcConfigurationSupport不存在的情况下,WebMvcAutoConfiguration 才生效!


而@EnableWebMvc将WebMvcConfigurationSupport组件导入进来,但是WebMvcConfigurationSupport只有SpringMVC最基本的功能。


【6】DispatcherServletAutoConfiguration与WebMvcAutoConfiguration

在这两个类中,SpringBoot为我们默认配置了SpringMVC的诸多组件,包括DispatcherServlet等,可以追踪源码进行查看。


DispatcherServletAutoConfiguration 源码如下:

 */
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    /*
     * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {
        private final WebMvcProperties webMvcProperties;
        public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
            this.webMvcProperties = webMvcProperties;
        }
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(
                    this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(
                    this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                    this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            return dispatcherServlet;
        }
        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }
    }
    @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
        private final ServerProperties serverProperties;
        private final WebMvcProperties webMvcProperties;
        private final MultipartConfigElement multipartConfig;
        public DispatcherServletRegistrationConfiguration(
                ServerProperties serverProperties, WebMvcProperties webMvcProperties,
                ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
            this.serverProperties = serverProperties;
            this.webMvcProperties = webMvcProperties;
            this.multipartConfig = multipartConfigProvider.getIfAvailable();
        }
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet) {
            ServletRegistrationBean registration = new ServletRegistrationBean(
                    dispatcherServlet, this.serverProperties.getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }
    }
    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DefaultDispatcherServletCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage
                    .forCondition("Default DispatcherServlet");
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            List<String> dispatchServletBeans = Arrays.asList(beanFactory
                    .getBeanNamesForType(DispatcherServlet.class, false, false));
            if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome.noMatch(message.found("dispatcher servlet bean")
                        .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome
                        .noMatch(message.found("non dispatcher servlet bean")
                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            if (dispatchServletBeans.isEmpty()) {
                return ConditionOutcome
                        .match(message.didNotFind("dispatcher servlet beans").atAll());
            }
            return ConditionOutcome.match(message
                    .found("dispatcher servlet bean", "dispatcher servlet beans")
                    .items(Style.QUOTE, dispatchServletBeans)
                    .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }
    }
    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DispatcherServletRegistrationCondition
            extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
            if (!outcome.isMatch()) {
                return outcome;
            }
            return checkServletRegistration(beanFactory);
        }
        private ConditionOutcome checkDefaultDispatcherName(
                ConfigurableListableBeanFactory beanFactory) {
            List<String> servlets = Arrays.asList(beanFactory
                    .getBeanNamesForType(DispatcherServlet.class, false, false));
            boolean containsDispatcherBean = beanFactory
                    .containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            if (containsDispatcherBean
                    && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome
                        .noMatch(startMessage().found("non dispatcher servlet")
                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            return ConditionOutcome.match();
        }
        private ConditionOutcome checkServletRegistration(
                ConfigurableListableBeanFactory beanFactory) {
            ConditionMessage.Builder message = startMessage();
            List<String> registrations = Arrays.asList(beanFactory
                    .getBeanNamesForType(ServletRegistrationBean.class, false, false));
            boolean containsDispatcherRegistrationBean = beanFactory
                    .containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
            if (registrations.isEmpty()) {
                if (containsDispatcherRegistrationBean) {
                    return ConditionOutcome
                            .noMatch(message.found("non servlet registration bean").items(
                                    DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
                }
                return ConditionOutcome
                        .match(message.didNotFind("servlet registration bean").atAll());
            }
            if (registrations
                    .contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
                return ConditionOutcome.noMatch(message.found("servlet registration bean")
                        .items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
            }
            if (containsDispatcherRegistrationBean) {
                return ConditionOutcome
                        .noMatch(message.found("non servlet registration bean").items(
                                DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
            }
            return ConditionOutcome.match(message.found("servlet registration beans")
                    .items(Style.QUOTE, registrations).append("and none is named "
                            + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
        }
        private ConditionMessage.Builder startMessage() {
            return ConditionMessage.forCondition("DispatcherServlet Registration");
        }
    }
}

WebMvcAutoConfiguration 源码如下:

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
        WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    public static final String DEFAULT_PREFIX = "";
    public static final String DEFAULT_SUFFIX = "";
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    @Bean
    @ConditionalOnMissingBean(HttpPutFormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
    public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
        return new OrderedHttpPutFormContentFilter();
    }
    // Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when not
    // on the classpath
    @Configuration
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
        private static final Log logger = LogFactory
                .getLog(WebMvcConfigurerAdapter.class);
        private final ResourceProperties resourceProperties;
        private final WebMvcProperties mvcProperties;
        private final ListableBeanFactory beanFactory;
        private final HttpMessageConverters messageConverters;
        final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
        public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
                WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
                @Lazy HttpMessageConverters messageConverters,
                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConverters = messageConverters;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
                    .getIfAvailable();
        }
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.addAll(this.messageConverters.getConverters());
        }
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            Long timeout = this.mvcProperties.getAsync().getRequestTimeout();
            if (timeout != null) {
                configurer.setDefaultTimeout(timeout);
            }
        }
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            Map<String, MediaType> mediaTypes = this.mvcProperties.getMediaTypes();
            for (Entry<String, MediaType> mediaType : mediaTypes.entrySet()) {
                configurer.mediaType(mediaType.getKey(), mediaType.getValue());
            }
        }
        @Bean
        @ConditionalOnMissingBean
        public InternalResourceViewResolver defaultViewResolver() {
            InternalResourceViewResolver resolver = new InternalResourceViewResolver();
            resolver.setPrefix(this.mvcProperties.getView().getPrefix());
            resolver.setSuffix(this.mvcProperties.getView().getSuffix());
            return resolver;
        }
        @Bean
        @ConditionalOnBean(View.class)
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
            return resolver;
        }
        @Bean
        @ConditionalOnBean(ViewResolver.class)
        @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
        public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
            ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
            resolver.setContentNegotiationManager(
                    beanFactory.getBean(ContentNegotiationManager.class));
            // ContentNegotiatingViewResolver uses all the other view resolvers to locate
            // a view so it should have a high precedence
            resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return resolver;
        }
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
        public LocaleResolver localeResolver() {
            if (this.mvcProperties
                    .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            }
            AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
            localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
            return localeResolver;
        }
        @Bean
        @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
        public Formatter<Date> dateFormatter() {
            return new DateFormatter(this.mvcProperties.getDateFormat());
        }
        @Override
        public MessageCodesResolver getMessageCodesResolver() {
            if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
                DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
                resolver.setMessageCodeFormatter(
                        this.mvcProperties.getMessageCodesResolverFormat());
                return resolver;
            }
            return null;
        }
        @Override
        public void addFormatters(FormatterRegistry registry) {
            for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
                registry.addConverter(converter);
            }
            for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
                registry.addConverter(converter);
            }
            for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
                registry.addFormatter(formatter);
            }
        }
        private <T> Collection<T> getBeansOfType(Class<T> type) {
            return this.beanFactory.getBeansOfType(type).values();
        }
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            Integer cachePeriod = this.resourceProperties.getCachePeriod();
            if (!registry.hasMappingForPattern("/webjars/**")) {
                customizeResourceHandlerRegistration(
                        registry.addResourceHandler("/webjars/**")
                                .addResourceLocations(
                                        "classpath:/META-INF/resources/webjars/")
                        .setCachePeriod(cachePeriod));
            }
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                customizeResourceHandlerRegistration(
                        registry.addResourceHandler(staticPathPattern)
                                .addResourceLocations(
                                        this.resourceProperties.getStaticLocations())
                        .setCachePeriod(cachePeriod));
            }
        }
        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(
                ResourceProperties resourceProperties) {
            return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
        }
        private void customizeResourceHandlerRegistration(
                ResourceHandlerRegistration registration) {
            if (this.resourceHandlerRegistrationCustomizer != null) {
                this.resourceHandlerRegistrationCustomizer.customize(registration);
            }
        }
        @Bean
        @ConditionalOnMissingBean({ RequestContextListener.class,
                RequestContextFilter.class })
        public static RequestContextFilter requestContextFilter() {
            return new OrderedRequestContextFilter();
        }
        @Configuration
        @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
        public static class FaviconConfiguration {
            private final ResourceProperties resourceProperties;
            public FaviconConfiguration(ResourceProperties resourceProperties) {
                this.resourceProperties = resourceProperties;
            }
            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
                        faviconRequestHandler()));
                return mapping;
            }
            @Bean
            public ResourceHttpRequestHandler faviconRequestHandler() {
                ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
                requestHandler
                        .setLocations(this.resourceProperties.getFaviconLocations());
                return requestHandler;
            }
        }
    }
    /**
     * Configuration equivalent to {@code @EnableWebMvc}.
     */
    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
        private final WebMvcProperties mvcProperties;
        private final ListableBeanFactory beanFactory;
        private final WebMvcRegistrations mvcRegistrations;
        public EnableWebMvcConfiguration(
                ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
                ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
                ListableBeanFactory beanFactory) {
            this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
            this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
            this.beanFactory = beanFactory;
        }
        @Bean
        @Override
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
            RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
            adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null ? true
                    : this.mvcProperties.isIgnoreDefaultModelOnRedirect());
            return adapter;
        }
        @Override
        protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
            if (this.mvcRegistrations != null
                    && this.mvcRegistrations.getRequestMappingHandlerAdapter() != null) {
                return this.mvcRegistrations.getRequestMappingHandlerAdapter();
            }
            return super.createRequestMappingHandlerAdapter();
        }
        @Bean
        @Primary
        @Override
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            // Must be @Primary for MvcUriComponentsBuilder to work
            return super.requestMappingHandlerMapping();
        }
        @Bean
        @Override
        public Validator mvcValidator() {
            if (!ClassUtils.isPresent("javax.validation.Validator",
                    getClass().getClassLoader())) {
                return super.mvcValidator();
            }
            return WebMvcValidator.get(getApplicationContext(), getValidator());
        }
        @Override
        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
            if (this.mvcRegistrations != null
                    && this.mvcRegistrations.getRequestMappingHandlerMapping() != null) {
                return this.mvcRegistrations.getRequestMappingHandlerMapping();
            }
            return super.createRequestMappingHandlerMapping();
        }
        @Override
        protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
            try {
                return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
            }
            catch (NoSuchBeanDefinitionException ex) {
                return super.getConfigurableWebBindingInitializer();
            }
        }
        @Override
        protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
            if (this.mvcRegistrations != null && this.mvcRegistrations
                    .getExceptionHandlerExceptionResolver() != null) {
                return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
            }
            return super.createExceptionHandlerExceptionResolver();
        }
        @Override
        protected void configureHandlerExceptionResolvers(
                List<HandlerExceptionResolver> exceptionResolvers) {
            super.configureHandlerExceptionResolvers(exceptionResolvers);
            if (exceptionResolvers.isEmpty()) {
                addDefaultHandlerExceptionResolvers(exceptionResolvers);
            }
            if (this.mvcProperties.isLogResolvedException()) {
                for (HandlerExceptionResolver resolver : exceptionResolvers) {
                    if (resolver instanceof AbstractHandlerExceptionResolver) {
                        ((AbstractHandlerExceptionResolver) resolver)
                                .setWarnLogCategory(resolver.getClass().getName());
                    }
                }
            }
        }
        @Bean
        @Override
        public ContentNegotiationManager mvcContentNegotiationManager() {
            ContentNegotiationManager manager = super.mvcContentNegotiationManager();
            List<ContentNegotiationStrategy> strategies = manager.getStrategies();
            ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator();
            while (iterator.hasNext()) {
                ContentNegotiationStrategy strategy = iterator.next();
                if (strategy instanceof PathExtensionContentNegotiationStrategy) {
                    iterator.set(new OptionalPathExtensionContentNegotiationStrategy(
                            strategy));
                }
            }
            return manager;
        }
    }
    @Configuration
    @ConditionalOnEnabledResourceChain
    static class ResourceChainCustomizerConfiguration {
        @Bean
        public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
            return new ResourceChainResourceHandlerRegistrationCustomizer();
        }
    }
    interface ResourceHandlerRegistrationCustomizer {
        void customize(ResourceHandlerRegistration registration);
    }
    private static class ResourceChainResourceHandlerRegistrationCustomizer
            implements ResourceHandlerRegistrationCustomizer {
        @Autowired
        private ResourceProperties resourceProperties = new ResourceProperties();
        @Override
        public void customize(ResourceHandlerRegistration registration) {
            ResourceProperties.Chain properties = this.resourceProperties.getChain();
            configureResourceChain(properties,
                    registration.resourceChain(properties.isCache()));
        }
        private void configureResourceChain(ResourceProperties.Chain properties,
                ResourceChainRegistration chain) {
            Strategy strategy = properties.getStrategy();
            if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
                chain.addResolver(getVersionResourceResolver(strategy));
            }
            if (properties.isGzipped()) {
                chain.addResolver(new GzipResourceResolver());
            }
            if (properties.isHtmlApplicationCache()) {
                chain.addTransformer(new AppCacheManifestTransformer());
            }
        }
        private ResourceResolver getVersionResourceResolver(
                ResourceProperties.Strategy properties) {
            VersionResourceResolver resolver = new VersionResourceResolver();
            if (properties.getFixed().isEnabled()) {
                String version = properties.getFixed().getVersion();
                String[] paths = properties.getFixed().getPaths();
                resolver.addFixedVersionStrategy(version, paths);
            }
            if (properties.getContent().isEnabled()) {
                String[] paths = properties.getContent().getPaths();
                resolver.addContentVersionStrategy(paths);
            }
            return resolver;
        }
    }
    static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
        private static final Log logger = LogFactory
                .getLog(WelcomePageHandlerMapping.class);
        private WelcomePageHandlerMapping(Resource welcomePage,
                String staticPathPattern) {
            if (welcomePage != null && "/**".equals(staticPathPattern)) {
                logger.info("Adding welcome page: " + welcomePage);
                ParameterizableViewController controller = new ParameterizableViewController();
                controller.setViewName("forward:index.html");
                setRootHandler(controller);
                setOrder(0);
            }
        }
        @Override
        public Object getHandlerInternal(HttpServletRequest request) throws Exception {
            for (MediaType mediaType : getAcceptedMediaTypes(request)) {
                if (mediaType.includes(MediaType.TEXT_HTML)) {
                    return super.getHandlerInternal(request);
                }
            }
            return null;
        }
        private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
            String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
            return MediaType.parseMediaTypes(
                    StringUtils.hasText(acceptHeader) ? acceptHeader : "*/*");
        }
    }
    /**
     * Decorator to make {@link PathExtensionContentNegotiationStrategy} optional
     * depending on a request attribute.
     */
    static class OptionalPathExtensionContentNegotiationStrategy
            implements ContentNegotiationStrategy {
        private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class
                .getName() + ".SKIP";
        private final ContentNegotiationStrategy delegate;
        OptionalPathExtensionContentNegotiationStrategy(
                ContentNegotiationStrategy delegate) {
            this.delegate = delegate;
        }
        @Override
        public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
                throws HttpMediaTypeNotAcceptableException {
            Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE,
                    RequestAttributes.SCOPE_REQUEST);
            if (skip != null && Boolean.parseBoolean(skip.toString())) {
                return Collections.emptyList();
            }
            return this.delegate.resolveMediaTypes(webRequest);
        }
    }
}
目录
相关文章
|
1月前
|
缓存 Java API
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
本文介绍了在Spring Boot中配置Swagger2的方法。通过创建一个配置类,添加`@Configuration`和`@EnableSwagger2`注解,使用Docket对象定义API文档的详细信息,包括标题、描述、版本和包路径等。配置完成后,访问`localhost:8080/swagger-ui.html`即可查看接口文档。文中还提示了可能因浏览器缓存导致的问题及解决方法。
82 0
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
|
1月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
85 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
1月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
100 0
|
1月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
103 0
|
1月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
77 0
|
1月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
84 0
|
1月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
63 0
|
1月前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
35 0
|
1月前
|
SQL Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
132 0
|
2月前
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
579 14

热门文章

最新文章

下一篇
oss创建bucket