SpringBoot自动配置原理入门

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringBoot自动配置原理入门

author:阿风/Alan

公众号:阿风的JAVA

Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器。

SpringBoot自动配置原理入门

让我们j进入@SpringBootApplication

123456789101112
@Target({ElementType.TYPE        })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(  ... ...)public @interface SpringBootApplication {    ... ...}

@Target({ElementType.TYPE        })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
  … …
)
public @interface SpringBootApplication {
    … …
}

进入@EnableAutoConfiguration

123456789
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})public @interface EnableAutoConfiguration {   ... ...}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   … …
}

进入AutoConfigurationImportSelector.class找到的selectImports方法

123456789101112131415161718
public class AutoConfigurationImportSelector{    public String[] selectImports(AnnotationMetadata annotationMetadata) {        if (!this.isEnabled(annotationMetadata)) {            return NO_IMPORTS;        } else {            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);            //这里的调用的getCandidateConfigurations()方法            ListString configurations = this.getCandidateConfigurations()方法(annotationMetadata, attributes);            configurations = this.removeDuplicates(configurations);            SetString exclusions = this.getExclusions(annotationMetadata, attributes);            this.checkExcludedClasses(configurations, exclusions);            configurations.removeAll(exclusions);            configurations = this.filter(configurations, autoConfigurationMetadata);            this.fireAutoConfigurationImportEvents(configurations, exclusions);            return StringUtils.toStringArray(configurations);        }    }

public class AutoConfigurationImportSelector{
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //这里的调用的getCandidateConfigurations()方法
            ListString configurations = this.getCandidateConfigurations()方法(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            SetString exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

找到getCandidateConfigurations()方法:

123456
 protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //进入SpringFactoriesLoader.loadFactoryNames() 方法        ListString configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");        return configurations;    }

 protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 //进入SpringFactoriesLoader.loadFactoryNames() 方法
        ListString configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, “No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.”);
        return configurations;
    }

进入SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

1234
public static ListString loadFactoryNames(Class? factoryClass, @Nullable ClassLoader classLoader) {        String factoryClassName = factoryClass.getName();        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());    }

public static ListString loadFactoryNames(Class? factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

该方法又调用了loadSpringFactories()方法

12345678910
private static MapString, ListString loadSpringFactories(@Nullable ClassLoader classLoader) {        MultiValueMapString, String result = (MultiValueMap)cache.get(classLoader);        if (result != null) {            return result;        } else {            try {                EnumerationURL urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");          ... ...          }    }

private static MapString, ListString loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMapString, String result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                EnumerationURL urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);
          … …
          }
    }

由此我们可以看到自动配置加载的文件:”META-INF/spring.factories”

SpringBoot自动配置原理入门

然后spring boot会根据对应的jar文件进行相应的自动配置

举例说明,文件上传:

1.我们从spring.factories找到文件上传的部分,搜索Multipart可以查看到下面的全类名

1
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration

org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration

2.查看MultipartAutoConfiguration类

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
package org.springframework.boot.autoconfigure.web.servlet;import javax.servlet.MultipartConfigElement;import javax.servlet.Servlet;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.multipart.MultipartResolver;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.multipart.support.StandardServletMultipartResolver; @Configuration@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})@ConditionalOnProperty(    prefix = "spring.servlet.multipart",    name = {"enabled"},    matchIfMissing = true)@ConditionalOnWebApplication(    type = Type.SERVLET)@EnableConfigurationProperties({MultipartProperties.class})public class MultipartAutoConfiguration {    private final MultipartProperties multipartProperties;     public MultipartAutoConfiguration(MultipartProperties multipartProperties) {        this.multipartProperties = multipartProperties;    }     @Bean    @ConditionalOnMissingBean({MultipartConfigElement.class, CommonsMultipartResolver.class})    public MultipartConfigElement multipartConfigElement() {        return this.multipartProperties.createMultipartConfig();    }     @Bean(        name = {"multipartResolver"}    )    @ConditionalOnMissingBean({MultipartResolver.class})    public StandardServletMultipartResolver multipartResolver() {        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());        return multipartResolver;    }}

package org.springframework.boot.autoconfigure.web.servlet;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

@Configuration
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(
    prefix = “spring.servlet.multipart”,
    name = {“enabled”},
    matchIfMissing = true
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({MultipartProperties.class})
public class MultipartAutoConfiguration {
    private final MultipartProperties multipartProperties;

    public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
        this.multipartProperties = multipartProperties;
    }

    @Bean
    @ConditionalOnMissingBean({MultipartConfigElement.class, CommonsMultipartResolver.class})
    public MultipartConfigElement multipartConfigElement() {
        return this.multipartProperties.createMultipartConfig();
    }

    @Bean(
        name = {“multipartResolver”}
    )
    @ConditionalOnMissingBean({MultipartResolver.class})
    public StandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
        return multipartResolver;
    }
}

我们可以看到其中的配置

12345678910
 @Bean(        name = {"multipartResolver"}    )    @ConditionalOnMissingBean({MultipartResolver.class})    public StandardServletMultipartResolver multipartResolver() {        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());        return multipartResolver;    }    //默认的multipartResolver配置是StandardServletMultipartResolver

 @Bean(
        name = {“multipartResolver”}
    )
    @ConditionalOnMissingBean({MultipartResolver.class})
    public StandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
        return multipartResolver;
    }
    //默认的multipartResolver配置是StandardServletMultipartResolver

我们再来看StandardServletMultipartResolver这个对文件上传的的解决方案

将普通的request转换为StandardMultipartHttpServletRequest

1234
@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {   return new StandardMultipartHttpServletRequest(request, this.resolveLazily);}

@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
   return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

而StandardMultipartHttpServletRequest内部封装了StandardMultipartFile

12345678910
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {/** * Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object. */@SuppressWarnings("serial")private static class StandardMultipartFile implements MultipartFile, Serializable {   private final Part part;    private final String filename;   ...

public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
/**
 * Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object.
 */
@SuppressWarnings(“serial”)
private static class StandardMultipartFile implements MultipartFile, Serializable {
   private final Part part;

   private final String filename;
   …

这个就是默认的springboot的文件上传的解决方案,也就是其MultipartFile默认的类型

SpringBoot自动配置原理入门

因为其实一个私有的内部类StandardMultipartFile,对外不暴露,所以并不能对其进行额外的操作,如果想要将MultipartFile转换为一个File类型,并没有提供这样的一个操作,而springboot也提供了另一个MultipartFile的子类型CommonsMultipartFile

SpringBoot自动配置原理入门

对于这样的一个类型,springboot也提供了解决方案CommonsMultipartResolver,因为之前配置的MultipartResolver的出了@Bean注解还有一个@ConditionalOnMissingBean({MultipartResolver.class})意思是当MultipartResolver.class不存在才会加载这个默认的类型,所以我们只需要配置一个自己的MultipartResolve就可以使用了

1234567891011121314151617181920
//使用CommonsMultipartResolver前提,需要导入commons-fileupload依赖        !--文件上传--        dependency            groupIdcommons-fileupload/groupId            artifactIdcommons-fileupload/artifactId            version1.3.1/version        /dependency@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)public CommonsMultipartResolver multipartResolver() {    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();    multipartResolver.setDefaultEncoding("UTF-8");    multipartResolver.setMaxUploadSize(20971520);    multipartResolver.setMaxInMemorySize(1048576);    multipartResolver.setResolveLazily(true);    return multipartResolver;} //需要注意的问题://因为其走的是自动配置的类,所以我们在启动springboot的时候需要排除相应的自动配置//@SpringBootApplication(exclude = { MultipartAutoConfiguration.class})

//使用CommonsMultipartResolver前提,需要导入commons-fileupload依赖
        !–文件上传–
        dependency
            groupIdcommons-fileupload/groupId
            artifactIdcommons-fileupload/artifactId
            version1.3.1/version
        /dependency
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setDefaultEncoding(“UTF-8”);
    multipartResolver.setMaxUploadSize(20971520);
    multipartResolver.setMaxInMemorySize(1048576);
    multipartResolver.setResolveLazily(true);
    return multipartResolver;
}

//需要注意的问题:
//因为其走的是自动配置的类,所以我们在启动springboot的时候需要排除相应的自动配置
//@SpringBootApplication(exclude = { MultipartAutoConfiguration.class})

然后就可以强转类型了

1234
MultipartFile file = xxx; CommonsMultipartFile cf= (CommonsMultipartFile)file; DiskFileItem fi = (DiskFileItem)cf.getFileItem();File f = fi.getStoreLocation();

MultipartFile file = xxx; 
CommonsMultipartFile cf= (CommonsMultipartFile)file; 
DiskFileItem fi = (DiskFileItem)cf.getFileItem();
File f = fi.getStoreLocation();

每个自动配置都有其不同的解决步骤,如果将默认的自动配置改为自定义的配置,只需要搜索spring.factories中的自动配置类,查看相应的类,写出对应的操作即可

文章如有错误,请您一定指出,感谢之至!
如果你有不同的见解,欢迎留言
图片可能来源于网络,如有侵权请告知。
文章中的资料有时忘记书写来源,如果需求请告知
最后:关注一下呗

SpringBoot自动配置原理入门
SpringBoot自动配置原理入门

长按二维码识别关注

 

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringBoot自动配置原理入门


 上一篇
SpringBoot快速入门 SpringBoot快速入门
SpringBoot入门 Author:Alan 公众号:阿风的JAVA Spirng boot 入门引导部分 (如果您直接书写案例,您可以跳过此部分) 关于Spring Boot 百度百科是这么介绍的: Spring Boot是由P
下一篇 
SpringBoot配置原理快速入门 SpringBoot配置原理快速入门
Author:Alan 公众号:阿风的JAVA 本篇文章需要对spirng有所了解 对于Spring Boot其并不是一个框架。其整合原理是Sping对技术的整合。不过与传统xml不同的是它推荐使用的 java配置文件的整合形式。那么