Springboot⾃动加载的原理
⼀.概括
我们⽤Springboot很⽅便的能将⼀个框架给搭建起来,是因为它将以前我们需要⼿动配置的地⽅都利⽤⾃动配置来代替,利⽤约定⼤于配置的思想简化了我们开发⼯作量。例如:在没有springboot之前,我们要在⼯程⾥⾯连接数据库的时候,我们需要在l⽂件⾥⾯配置:
<bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"  value="sql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
但引⼊springboot后不需要声明DriverManagerDataSource,只需要在applicationContext.properties⾥⾯
配置连接数据库的所需要的URL,⽤户名和密码就可以了,然后在pom⽂件⾥⾯引⼊相应的jar包就可以了,⾄于为什么可以这样,下⾯会讲解到
spring.datasource.druid.url=jdbc:mysql://127.0.01:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.sql.jdbc.Driver
所以⾃动加载对于springboot来说很重要。
springboot启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication注解,@SpringBootApplication是⼀个复合型注解,⾥⾯包含了三个很重要的注解:
@SpringBootConfiguration:说明DemoApplication这个类是⼀个ioc容器配置类,可以在DemoApplication类中声明⼀些bean,然后通过@ComponentScan加载到ioc容器中,相当于我们配置⽂件⾥⾯的
<beans>
<bean id="**" class="******">
</bean>
.......
</beans>
@ComponentScan:扫描当前包下的所有类和当前包下⾯⼦包所包含的类,将带有注解的类加载到ioc容器,@ComponentScan在springboot中的作⽤就是将代码中带有@Controller,@Service,@Repority等这些注解的类加载到ioc容器中。相当于我们在配置⽂件中写的这样⼀段代码
<context:component-scan base-package="***.***.***"/>
@EnableAutoConfiguration:基于你配置的依赖项,也就是引⼊的jar包,扫描所有jar包下⾯的META-
INF/spring.factories,spring.factories中都是这个jar的配置类,配置类⾥⾯就有我们所需要的⼯具类。将所有复合⾃动配置条件的bean 定义加载到ioc容器中,记住@EnableAutoConfiguration⾃动加载的是⼀些不需要我们⾃⼰去定义但是需要⽤到的“⼯具类”,例如上⾯提到的DriverManagerDataSource类。
@EnableAutoConfiguration能⾃动加载我们项⽬中所需要的“⼯具类”是⽤到了SpringFactoriesLoader,SpringFactoriesLoader能将指定的配置⽂件META-INF/spring.factories加载配置。
@Target({ElementType.TYPE})// 注解的适⽤范围,其中TYPE⽤于描述类、接⼝(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME)// 注解的⽣命周期,保留到class⽂件中
@Documented// 表明这个注解应该被javadoc记录
@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 {};
}
⼆.@EnableAutoConfiguration的基本原理
来看看@EnableAutoConfiguration这个注解是如何实现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "ableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
在@EnableAutoConfiguration中有两个⽐较重要的注解,⼀个是@AutoConfigurationPackage,另外⼀个是
@Import(EnableAutoConfigurationImportSelector.class)
@AutoConfigurationPackage的作⽤就是将当前启动类所在的包以bean的形式注册到ioc容器中,⾄于在后⾯起什么作⽤,我在⽹上查了半天,也没有⼀个说清楚的,有谁知道的,请在下⾯留⾔告诉我。
@Import(EnableAutoConfigurationImportSelector.class):借助EnableAutoConfigurationImportSelector⽗类AutoConfigurationImportSelector的selectImports⽅法来读取所有依赖的jar包下⾯META-INF/spring.factories⽂件,并且根据加载条件来加载项⽬所需要的类,这样就完成了springboot的⾃动加载。
EnableAutoConfigurationImportSelector继承了AutoConfigurationImportSelector,看看AutoConfigurationImportSelector的selectImports⽅法
/**
* 最主要的⽅法
* annotationMetadata
* [@org.springframework.boot.autoconfigure.SpringBootApplication
* (scanBasePackageClasses=[], excludeName=[], exclude=[], scanBasePackages=[])]
* @param annotationMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/**
*
*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
/
**
* 得到注解中的所有属性信息{excludeName=[], exclude=[]}
*/
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/**
*加载META-INF/spring-autoconfigure-metadata.properties,获取所有⽀持⾃动配置的信息
* 获取所有⽀持EnableAutoConfiguration的组件信息,这部分信息配置在spring-boot-autoconfig包下的spring.factories下
*
*
*  使⽤了内部⼯具使⽤SpringFactoriesLoader,查classpath上所有jar包中的
*  META-INF\spring.factories,出其中key为
*  org.springframework.boot.autoconfigure.EnableAutoConfiguration
*  的属性定义的⼯⼚类名称。
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
/**
* 去除不需要的
* @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, })
*/
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
/**
* 然后使⽤AutoConfigurationImportFilter进⾏过滤,过滤的⽅式基本上是判断现有系统是否引⼊了某个组件,(系统是否使⽤哪个组件是在pom定义的时候就              * ,如果有的话则进⾏相关配置。⽐如ServletWebServerFactoryAutoConfiguration
* ,会在ServletRequest.class等条件存在的情况下进⾏配置,
* ⽽EmbeddedTomcat会在Servlet.class, Tomcat.class存在的情况下创建TomcatServletWebServerFactory
*
* org.springframework.dition.OnClassCondition
* 总⽽⾔之,此过滤器会检查候选配置类的注解@ConditionalOnClass,如果要求的类在classpath 中不存在,则这个候选配置类会被排除掉
*/
configurations = filter(configurations, autoConfigurationMetadata);
/**
* 现在已经到所有需要被应⽤的候选配置类
* ⼴播事件AutoConfigurationImportEvent
*/
fireAutoConfigurationImportEvents(configurations, exclusions);
StringArray(configurations);
}
private void fireAutoConfigurationImportEvents(List<String> configurations,
Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
}
}
}
getCandidateConfigurations⽅法会读取到所有依赖jar包下⾯的META-INF/spring.factories,并将spring.factories中的配置类的全名
称获取到。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.SpringFactoriesLoaderFactoryClass(), BeanClassLoader());
}
重点来了,springboot之所以能拿到spring.factories就是通过SpringFactoriesLoader来读取的,SpringFactoriesLoader会将依赖包
所有的spring.factories读取出来,并⽤⼀个map来封装读取出来的vaule。SpringFactoriesLoader是spring提供的⼀种扩张⽅案,其主
要功能就是从指定的配置⽂件META-INF/spring.factories加载配置。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = Name();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, ptyList());
}
/**
*在springboot启动的时候会将依赖包所有的spring.factories读取出来,spring.factories
*中不仅包括配置类,还有,初始化器等
*/
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//第⼀次就加载了所有的,后⾯每次需要的时候都是从缓存⾥⾯取的
MultiValueMap<String, String> result = (classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = Element();
UrlResource resource = new UrlResource(url);
//将⽂件路径转变成Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : Set()) {
String factoryClassName = ((String) Key()).trim();
for (String factoryName : StringUtilsmaDelimitedListToStringArray((String) Value())) {spring ioc注解
result.add(factoryClassName, im());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
拿例如引⼊的mybatis举例,引⼊baomidou的mybaitis包后,springboot启动会去读取这个jar包下⾯的配置⽂件
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。