SpringBoot作为目前市面上的主流框架,大小公司都会基于SpringBoot进行开发,因此在面试中出现频率也是很高的,从本文开始,和大家一起深入理解SpringBoot工作原理。
1. SpringBoot 特点
首先先看下笔者总结的几点SpringBoot优点。
- SpringBoot 并未提供Spring框架外的功能,它是一个快速集成Spring项目的工具
- SpringBoot提供内置的tomcat, jetty等web容器,因此不需要将项目打包成war包,以jar包方式即可运行
- SpringBoot 减少了繁琐的xml配置,通过自动配置大幅度提高了开发效率
本文将理解自动配置为目标,带大家深入浅出自动配置原理是怎么一回事。
2. 预备知识
在看源码之前,我们先了解下几个注解。
2.1 @Configuration注解
@Configuration注解应该是最常见的注解了,被@Configuration注解修饰的类会被作为一个配置类,@Bean注解修改的方法会返回一个对象,这个对象将会以Java Config的形式被Spring IOC容器管理。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
2.2 @Import注解
@Import注解 的作用就是导入一些配置类,或者直接创建导入类的实例,被Spring 容器进行管理。
@import 注解支持三种类型的导入:
- 导入普通类,这个类的实例将会被生成
- 指定实现了ImportSelector接口的类,将以自定义的方式导入类
- 指定实现了ImportBeanDefinitionRegistrar的接口的类,用于个性化加载,但是如果想要修改bean的属性和作用范围,就需要通过这种方式
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
@Import注解的详细介绍请参考大佬的文章 Spring全解系列 - @Import注解
2.3 @Conditional注解
@Conditional注解是一类注解,这类注解定义被修饰的类什么时候被加载,如果定义的条件满足,将会进行加载。比如SpringBoot提供的@ConditionalOnBean 这个注解会定义当容器中存在某个bean时才进行加载。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
如果我们需要实现自定义的条件,可以实现Condition接口,并且覆盖match方法
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
以下时SpringBoot中提供的注解
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化
2.4 @AliasFor注解
@AliasFor注解 主要的作用是别名,在同一个注解中使用,@AliasFor可以定义两个字段含有相同的含义。比如@RequestMapping()中我们可以直接写@RequestMapping("/api") 也可以写@RequestMapping(path = "/api")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
Class<? extends Annotation> annotation() default Annotation.class;
}
2.5 @Inherited注解
@Inherited注解 根据名称就可以得知,表示子类可以继承父类的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
3. 自动配置原理
有了上面的基础知识的理解后,我们对后续的自动配置原理就会比较轻松了。
3.1 @SpringBootApplication注解
每个SpringBoot项目需要在主启动类上加上@SpringBootApplication注解,这个注解时自动配置的核心,它是一个复合注解,点进去看到,下面还有比较重要的三个注解,@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。其中最重要的是@EnableAutoConfiguration注解
@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 = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
3.2 @SpringBootConfiguration
@SpringBootConfiguration 是个复合注解,主要是@Configuration注解,表示主启动类也是个配置类,也可以配置Bean的相关信息。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
3.3 @ComponentScan
@ComponentScan 可以指定包路径,然后Spring会去扫描指定路径下的类,将其实例化交由Spring容器进行管理。其中还有个Filter子注解,可以进行过滤操作。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
3.4 @EnableAutoConfiguration
@EnableAutoConfiguration是SpringBoot自动配置的核心注解,其中@Import({AutoConfigurationImportSelector.class}) 就是自动配置的核心入口,在基础知识部分我们讲过,@Import({AutoConfigurationImportSelector.class})可以自定义导入规则,主要就是AutoConfigurationImportSelector的selectImports来选择需要自动配置的类进行配置。
@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 {};
}
selectImports 方法中主要是getCandidateConfigurations这个方法,这个方法又会调用SpringFactoriesLoader.loadFactoryNames主要作用是读取spring-boot-autoconfigure.jar包下的META-INF/spring.factories中配置可以进行自动配置的类,这些类都是以JavaConfig形式进行导入的,在满足了@Conditional中定义的加载条件后,Spring会讲这些类加载到IOC容器中。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 获取注解的元数据信息
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 扫描spring.factories配置的自动配置类进行读取
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);
}
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
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 {
// 扫描META-INF/spring.factories配置的自动配置类
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result.addAll((String)entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
}
}
}
接下来看看,Spring.factories内容是什么,其中定义了Initializers,Listeners,以及Auto Configure。其中关于自动配置的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,value就是需要自动配置的类,这些类是个集合,以,\进行拼接,并且都是以Configuration结尾的,说明是这些类配置类,在启动中会根据类的配置信息进行加载。Spring.factories定义了哪些类可以被自动配置,每个配置类定了在什么条件下可以被自动配置,并将这些类实例化被Spring容器管理。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# 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.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
.... 省略了很多
这里以AopAutoConfiguration为例子,结合基础知识中的注解可知,当类路径下存在EnableAspectJAutoProxy.class,Aspect.class,Advice.class等类时才会注入AopAutoConfiguration,并且默认是创建CglibAutoProxyConfiguration配置。
// 申明这是个配置类
@Configuration
// 定义加载类的条件
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
// 定义加载类的配置需要spring.aop.auto=true,缺省值是true
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"auto"},
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = true
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "true",
matchIfMissing = true
)
public static class CglibAutoProxyConfiguration {
public CglibAutoProxyConfiguration() {
}
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "false",
matchIfMissing = false
)
public static class JdkDynamicAutoProxyConfiguration {
public JdkDynamicAutoProxyConfiguration() {
}
}
}
4. 总结
要是面试官问你:Spring Boot的自动配置原理是什么,你可以这样回答哦:
- SpringBoot主启动类有个注解是SpringBootApplication,它是个复合注解,主要包含@SpringConfiguration、@ComponentScan、@EnableAutoConfiguration注解,其中@EnableAutoConfiguration注解是自动配置的主要注解。
- @EnableAutoConfiguration注解也是个复合注解,它主要是导入了AutoConfigurationImportSelecor类,该类的的selectImports方法会把满足条件的类加载到容器中。
- selectImports方法会调用Spring 核心的loadSpringFactories方法,这个方法会扫描META-INF/spring.factories中配置的自动配置类,然后根据每个配置类的加载条件来判断是否需要加载对应的实例,交由Spring 容器管理。
网友评论