SpringBoot自动配置原理入门

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

SpringBoot自动配置原理入门

让我们j进入@SpringBootApplication

1
2
3
4
5
6
7
8
9
10
11
12
@Target({ElementType.TYPE        })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
  ... ...
)
public @interface SpringBootApplication {
    ... ...
}

进入@EnableAutoConfiguration

1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   ... ...
}

进入AutoConfigurationImportSelector.class找到的selectImports方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()方法
            List<String> configurations = this.getCandidateConfigurations()方法(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> 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()方法:

1
2
3
4
5
6
 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 //进入SpringFactoriesLoader.loadFactoryNames() 方法
        List<String> 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());

1
2
3
4
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

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

1
2
3
4
5
6
7
8
9
10
private static Map<StringList<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<StringString> result = (MultiValueMap)cache.get(classLoader);
        if (result !null) {
            return result;
        } else {
            try {
                Enumeration<URL> 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

2.查看MultipartAutoConfiguration类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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;
    }
}

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

我们可以看到其中的配置

1
2
3
4
5
6
7
8
9
10
 @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

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

而StandardMultipartHttpServletRequest内部封装了StandardMultipartFile

1
2
3
4
5
6
7
8
9
10
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;
   ...

   private final String filename;
...

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

SpringBoot自动配置原理入门

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

SpringBoot自动配置原理入门

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//使用CommonsMultipartResolver前提,需要导入commons-fileupload依赖
        <!--文件上传-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.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})

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

然后就可以强转类型了

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

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

文章中的资料有时忘记书写来源,如果需求请告知

最后:关注一下呗

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们