SpringBoot项目开发技巧一:自动装配

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot项目开发技巧一:自动装配相关的知识,希望对你有一定的参考价值。

参考技术A

SpringBoot自动装配是springboot重要的功能点。Boot系列、其它框架与Boot的整合很多是基于自动装配的。 我们在写SpringBoot项目时,Maven的pom依赖能看到很多的starter 都是基于自动装配的

自动装配的原理 网上已经有很多的讲解了,这里不再冗余了。

ProviderConfigration

@Configuration注解在类上表示此类为自动装配类。@Bean通常与@Configuration 一起使用,将方法的返回值注入到spring的bean容器中

@Bean 注解的方法是将方法名做为beanName。上面ProviderProperties这个Bean 在spring容器中的beanName为providerHpProperties(作者工作中踩过这个坑)

@ConditionalOnProperty 不讲解。SpringBoot有很多的@ConditionalXXX 实现条件式注入bean。感兴趣的可以下去了解一下

@ConfigurationProperties 将配置文件的符合规则的key 对应的映射到文件中。

prefix :前缀

ignoreInvalidFields :配置true时不能被正常解析的属性会忽略,spring正常启动。默认为false,解析失败时报错。简单来说就是类型不匹配。

ignoreUnknownFields:配置true时属性未找到时会忽略,spring正常启动。默认为false,未找到时报错。简单来说就是没找到。

对应的配置文件

此时我们已经通过自动装配的方式向Spring的bean容器中注册了两个bean,分别为:ProviderProperties、ProviderSwitch

使用和其它bean一样,@Autowired即可。

如果你写的自动装配类是用于本项目的。那么需要注意@Configuration注解的类需要在scanBasePackages扫包的路径内,否则spring无法加载到自动装配类。更别谈自动装配了(如果在spring.factories文件中配了,可以不用扫包操作。见下文)。

很多时候我们需要编写像SDK这样供外界系统使用的包。我们又想最小程度上减轻引入方的配置(不想让三方去添加扫包操作),那么此时自动装配就起到了关键性的作用(SpringBoot很多的starter都是这样)。下面来说一下具体的流程

在resources目录下创建META-INF/spring.factories文件。

文件结构key,value的形式。key为EnableAutoConfiguration(因为我们这章将的是自动装配),value为我们编写的Configration类全路径

SPI文件有很多的扩展点(很多的配置项key),不在一一叙述。可以在idea中直接搜索spring.factories,可以看到有很多。

SpringBoot在启动中会找到Path下面的所有spring.factories文件,并使用SpringFactoriesLoader将文件加载解析为K,V的形式。

在按照key分发至对应的扩展点,将配置的value(就是class全路径)加载解析到Spring中,实现SPI。

在我们完成了以上几步后,我们的sdk包中的自动装配就完成啦。三方引入时也不用扫包啦。

偷偷告诉大家:@Configuration 可以和 @ComponentScan一起使用,如下:

这样com.hp.config包及子目录 也会被spring扫描到。作为一个SDK 既不用三方去扫包,又可以自己扫描一些特定包,岂不是美哉!

在引入了以下依赖后

直接mvn package 命令后,在target目录下可以找到以下文件

将spring-configuration-metadata.json 拷贝到resources的META-INF目录下。

之后在编写配置文件的时候就会有提示,如下

作为一个sdk包,三方在使用的时候一定会对你竖起大拇指的。心想:这个人真强!

好的以上就是分享的内容啦。希望大家看了后会有收获,对编码上会有帮助!

11点了,该睡觉了。

对了,今天三妹说我有点小帅(我老婆,我是二哥她是三妹)!哈哈哈!今晚睡觉怕是要笑出声了 QAQ

SpringBoot自动装配流程源码分析

SpringBoot

传统方式的SSM框架因为需要配置大量文件而被开发人员诟病重复性工作,所以SpringBoot的出现在减少开发人员做大量重复性配置的工作,使得开发人员能够快速的开始项目开发、更加专注于业务代码的编写。但SpringBoot跟SSM有什么框架不同呢?为什么SpringBoot可以自动装配呢?SpringBoot自动装配是如何实现的呢?

SpringBoot入口

写过SpringBoot应用的开发者都知道,SpringBoot应用的启动类是被@SpringBootApplication注解的类,如下我写的一个demo.后续的自动装配源码也是从这个demo入口进入的。

package com.chandler.auto_configure.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

1.主入口只有一个main方法,SpringApplication.run()方法。
2.@SpringBootApplication.

SpringBootApplication注解

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

SpringBootApplication是一个组合注解,@SpringBootConfiguration、@EnableAutoConfiguratio、@ComponentScan都在包含咋该注解中。

SpringBootConfiguration注解

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

EnableAutoConfigureation注解

该注解开启了自动装配功能,我们进入看看具体是如何实现的。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see AutoConfigurationPackages
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

EnableAutoConfigureation注解包含了@AutoConfigurationPackage,@AutoConfigurationPackage中在容器中@Import了一个AutoConfigurationPackages.Registrar.class,我们继续看看这个Registrar.class。

/**
     * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
     * configuration.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            register(registry, new PackageImport(metadata).getPackageName());
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }

    }

Registrar.获取扫描的包路径。获取的就是被@SpringBootApplication注解类所在包以及子包。所以包名一定要注意,在主配置类(@SpringBootApplication注解的类)。现在以及知道从哪里去加载了,但还有一个问题就是加载谁??EnableAutoConfigureation注解中还@Import了一个AutoConfigurationImportSelector.class。
技术图片

有了这些自动装配的功能,就省去了配置的重复性工作,但这些配置又是从哪里读取得呢?默认情况下都会有一套默认配置。这些配置是Spring-boot-autoconfiguration中META-INF下的spring.factories.
技术图片

/**
 * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Sébastien Deleuze
 * @author Eddú Meléndez
 * @author Stephane Nicoll
 * @author Kristine Jetzke
 * @author Bruce Brouwer
 * @author Artsiom Yudovin
 * @since 2.0.0
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    public static final String DEFAULT_PREFIX = "";

    public static final String DEFAULT_SUFFIX = "";

    private static final String[] SERVLET_LOCATIONS = { "/" };

    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean(FormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }

    static String[] getResourceLocations(String[] staticLocations) {
        String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
        System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
        System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
        return locations;
    }

    // Defined as a nested config to ensure WebMvcConfigurer is not read when not
    // on the classpath
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

        private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

        private final ResourceProperties resourceProperties;

        private final WebMvcProperties mvcProperties;

        private final ListableBeanFactory beanFactory;

        private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

        final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

        public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
                ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
        }

        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            this.messageConvertersProvider
                    .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
        }

        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
                Object taskExecutor = this.beanFactory
                        .getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
                if (taskExecutor instanceof AsyncTaskExecutor) {
                    configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor));
                }
            }
            Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
            if (timeout != null) {
                configurer.setDefaultTimeout(timeout.toMillis());
            }
        }

        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
            configurer.setUseRegisteredSuffixPatternMatch(
                    this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
        }

        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
            configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
            configurer.favorParameter(contentnegotiation.isFavorParameter());
            if (contentnegotiation.getParameterName() != null) {
                configurer.parameterName(contentnegotiation.getParameterName());
            }
            Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
            mediaTypes.forEach(configurer::mediaType);
        }

        @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;
        }

        @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) {
            ApplicationConversionService.addBeans(registry, this.beanFactory);
        }

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
            CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            if (!registry.hasMappingForPattern("/webjars/**")) {
                customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                        .addResourceLocations("classpath:/META-INF/resources/webjars/")
                        .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                        .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                        .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }
        }

        private Integer getSeconds(Duration cachePeriod) {
            return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
        }

        private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
            if (this.resourceHandlerRegistrationCustomizer != null) {
                this.resourceHandlerRegistrationCustomizer.customize(registration);
            }
        }

        @Bean
        @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
        @ConditionalOnMissingFilterBean(RequestContextFilter.class)
        public static RequestContextFilter requestContextFilter() {
            return new OrderedRequestContextFilter();
        }

    }

    /**
     * Configuration equivalent to {@code @EnableWebMvc}.
     */
    @Configuration(proxyBeanMethods = false)
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

        private final ResourceProperties resourceProperties;

        private final WebMvcProperties mvcProperties;

        private final ListableBeanFactory beanFactory;

        private final WebMvcRegistrations mvcRegistrations;

        private ResourceLoader resourceLoader;

        public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
                ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
                ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
            this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
            this.beanFactory = beanFactory;
        }

        @Bean
        @Override
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
                @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
                @Qualifier("mvcConversionService") FormattingConversionService conversionService,
                @Qualifier("mvcValidator") Validator validator) {
            RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
                    conversionService, validator);
            adapter.setIgnoreDefaultModelOnRedirect(
                    this.mvcProperties == null || 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(
                @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
                @Qualifier("mvcConversionService") FormattingConversionService conversionService,
                @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
            // Must be @Primary for MvcUriComponentsBuilder to work
            return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
                    resourceUrlProvider);
        }

        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
                    new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            return welcomePageHandlerMapping;
        }

        private Optional<Resource> getWelcomePage() {
            String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
            return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
        }

        private Resource getIndexHtml(String location) {
            return this.resourceLoader.getResource(location + "index.html");
        }

        private boolean isReadable(Resource resource) {
            try {
                return resource.exists() && (resource.getURL() != null);
            }
            catch (Exception ex) {
                return false;
            }
        }

        @Bean
        @Override
        public FormattingConversionService mvcConversionService() {
            WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
            addFormatters(conversionService);
            return conversionService;
        }

        @Bean
        @Override
        public Validator mvcValidator() {
            if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
                return super.mvcValidator();
            }
            return ValidatorAdapter.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(
                FormattingConversionService mvcConversionService, Validator mvcValidator) {
            try {
                return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
            }
            catch (NoSuchBeanDefinitionException ex) {
                return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
            }
        }

        @Override
        protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
            if (this.mvcRegistrations != null && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) {
                return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
            }
            return super.createExceptionHandlerExceptionResolver();
        }

        @Override
        protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
            super.extendHandlerExceptionResolvers(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;
        }

        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnEnabledResourceChain
    static class ResourceChainCustomizerConfiguration {

        @Bean
        ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
            return new ResourceChainResourceHandlerRegistrationCustomizer();
        }

    }

    interface ResourceHandlerRegistrationCustomizer {

        void customize(ResourceHandlerRegistration registration);

    }

    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 (properties.isCompressed()) {
                chain.addResolver(new EncodedResourceResolver());
            }
            if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
                chain.addResolver(getVersionResourceResolver(strategy));
            }
            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;
        }

    }

    /**
     * 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 MEDIA_TYPE_ALL_LIST;
            }
            return this.delegate.resolveMediaTypes(webRequest);
        }

    }

}

这样是不是就很多配置都不需要我们自行配置了,SPringboot已经给我们配置好了。

以上是关于SpringBoot项目开发技巧一:自动装配的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot自动配置/装配(SPI)

SpringBoot的自动装配(一)

深入理解SpringBoot之自动装配

SpringBoot学习笔记—— 自动装配原理

SpringBoot第一特性:自动装配

SpringBoot自动装配流程源码分析