Spring Boot 是遵循约定优于配置这个理念产生的,将已有的Spring组件整合起来,对一些常见的应用场景进行了默认的配置。
简化方式包括:基于场景启动器,基于注解开发。
为了阅读方便,本篇主要讲解基于场景。
一、基于场景(starter)
SpringBoot拥有很多方便使用的starter(Spring提供的starter命名规范spring-boot-starter-xxx.jar,第三方提供的starter命名规范xxx-spring-boot-starter.jar),比如spring-boot-starter-log4j、mybatis-spring-boot-starter.jar等,各自都代表了一个相对完整的功能模块。
SpringBoot-starter是一个集成接合器,完成两件事:
- 引入模块所需的相关jar包
- 自动配置各自模块所需的属性
1.1 引入模块所需的相关jar包
其原理是Maven继承依赖关系。
项目 pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
依赖的 spring-boot-starter-web 的 pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.4.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.4.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
注意被依赖的pom文件中的依赖 scope 的值为 compile ,只有依赖范围为compile时可以访问。
1.2 自动配置各自模块所需的属性
starter 除了要整合导入相关的依赖外,还要能够自动配置。
自动配置的思路:
- 在每个 starter 内都有个自动配置类
- 将 starter 内的自动配置类导入到项目的配置类
下面从项目的主入口追踪这一过程:
主程序最大的特点就是其类上放了一个@SpringBootApplication注解,这也正是SpringBoot项目启动的核心;
@SpringBootApplication
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
@SpringBootApplication,可以发现它是一个组合注解;
@SpringBootConfiguration//核心
@EnableAutoConfiguration//核心
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@EnableAutoConfiguration,顾名思义,这个注解一定和自动配置相关;
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class)//自动配置导入选择
AutoConfigurationImportSelector,这个类中存在一个方法可以帮我们获取所有的配置;
/*获取候选的配置*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
实际上它返回了一个List,这个List是由loadFactoryNames()方法返回的,其中传入了一个getSpringFactoriesLoaderFactoryClass(),我们可以看看这个方法的内容;
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
getCandidateConfigurations 方法,其中有一条断言,代表从项目路径中找到所有的(包括依赖中的)META-INF/spring.factories文件然后加载,具体过程如下;
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 即 org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
// 返回 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
// 要读取的资源路径
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
// 将文件转换成 k-v 格式
...
} catch (IOException var14) {
}
}
}
META-INF/spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置都是常会用场景的自动配置类;
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...
其中,WebMvcAutoConfiguration 的内容如下,作用是自动配置 WebMvc:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@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_LOCATION = "/";
public WebMvcAutoConfiguration() {
}
@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();
}
interface ResourceHandlerRegistrationCustomizer {
void customize(ResourceHandlerRegistration registration);
}
...
}
这里涉及到两个问题,一是 META-INF/spring.factories 文件中配置了这么多的自动配置类,为何只有我们依赖的生效了,二是xxxAutoConfiguration是如何自动配置的,或者说 application.properties 中的配置项都有哪些?
第一个问题通过 @Condition 实现
如上文的 @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) 和 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
常用 Condition 注解:
注解 | 解析 |
---|---|
@ConditionalOnBean | 当容器里有指定的Bean的条件下。 |
@ConditionalOnClass | 当类路径下有指定的类的条件下。 |
@ConditionalOnExpression | 基于SpEL表达式作为判断条件。 |
@ConditionalOnJava | 基于JVM版本作为判断条件。 |
@ConditionalOnJndi | 在JNDI存在的条件下查找指定的位置。 |
@ConditionalOnMissingBean | 当容器里没有指定Bean的情况下。 |
@ConditionalOnMissingClass | 当类路径下没有指定的类的条件下。 |
@ConditionalOnNotWebApplication | 当前项目不是Web项目的条件下。 |
@ConditionalOnProperty | 指定的属性是否有指定的值。 |
@ConditionalOnResource | 类路径是否有指定的值。 |
@ConditionalOnSingleCandidate | 当指定Bean在容器中只有一个, 或者虽然有多个但是指定首选的Bean。 |
@ConditionalOnWebApplicatio | 当前项目是Web项目的条件下。 |
第二个问题通过 @EnableConfigurationProperties({xxxProperties.class}) 实现,从 applications.properties 读取相应的配置项
@ConfigurationProperties(
prefix = "spring.mvc"
)
public class WebMvcProperties {
private org.springframework.validation.DefaultMessageCodesResolver.Format messageCodesResolverFormat;
private Locale locale;
private WebMvcProperties.LocaleResolver localeResolver;
private final WebMvcProperties.Format format;
private boolean dispatchTraceRequest;
private boolean dispatchOptionsRequest;
private boolean ignoreDefaultModelOnRedirect;
private boolean publishRequestHandledEvents;
...
@Deprecated
public void setDateFormat(String dateFormat) {
this.format.setDate(dateFormat);
}
public WebMvcProperties.Format getFormat() {
return this.format;
}
public boolean isIgnoreDefaultModelOnRedirect() {
return this.ignoreDefaultModelOnRedirect;
}
...
}