SpringBoot自动配置原理入门
Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器。
让我们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<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> 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"
然后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默认的类型
因为其实一个私有的内部类StandardMultipartFile,对外不暴露,所以并不能对其进行额外的操作,如果想要将MultipartFile转换为一个File类型,并没有提供这样的一个操作,而springboot也提供了另一个MultipartFile的子类型CommonsMultipartFile
对于这样的一个类型,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中的自动配置类,查看相应的类,写出对应的操作即可
文章中的资料有时忘记书写来源,如果需求请告知
最后:关注一下呗