一、预热
二、源码分析
2.1、初始化阶段
思考:springboot是如何把bean放入到IOC容器中
- 1、核心方法
SpringApplication.run(ManagementCenterGateWayApplication.class, args);
- 2、调入方法如下
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
- 3、调入方法如下(
超重点,核心
)
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
- 3.1、初始化阶段(
把项目中所有的符合SpringBoot机制的bean全部收集起来 map
)
new SpringApplication(primarySources)
- 3.1.1、调用构造函数方法
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
- 3.1.2、调用具体的初始化方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 3.1.3、调用
getSpringFactoriesInstances
- 核心方法一、
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
核心方法二、
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
1:为什么要传递类
触发类加载器
2:传递类的作用是干啥
触发类加载器,通过双亲委派模型,把项目中所有的类,包括jdk,jdkext,spring.jar,mybatis.jar全部把他们中编译好的字节文件class找到,放入map中
3:怎么找到项目所需要的bean
上边两个核心方法都调用了getSpringFactoriesInstances
方法,触发类加载器
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
- 验证阶段、我们在
ClassLoader classLoader = getClassLoader();
打断点,启动项目
结果发现,我们在启动项目的时候,类加载器加载了项目中所需要的所有jar包
思考?
项目中引入jdk是怎么加载进去的,项目中引入的spring.jar里面的类为什么可以使用,项目中的String,List为什么就可以使用呢
答案:在项目启动的时候,项目会调用类加载器通过双亲委派模型把项目中所有的class全部找到,然后放入到jvm中去,只不过springboot把这些加载的classes进行过滤匹配把符合条件的bean放入到ioc容器中的过程
双亲委派模型:一句话,找项目所有的class字节文件,不论jdk,ext,项目写的classes,spring.jar通通全部找到
- 3.1.4、开始对类加载的加载类进行过滤
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
SpringFactoriesLoader点进去
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//这里就把spring.factories 中的对应起来了
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
type:开始进行匹配过滤,作用其实就是一个加载缓存的目的,作用就是,他会把类加载中所有的jar文件存在:META-INF/spring.factories
文件中的内容全部找到,然后把这个META-INF/spring.factories
中的bean全部放入到map中
2.2、springboot是如何把需要的bean加载出来的呢
我们知道类加载,把项目所有的classes文件通通都加载到jvm中,n那么springboot怎么是如何把需要管理和初始化放入到ioc容器的classes找出来呢?springboot借鉴了java9的新特性:spi加载机制,这种加载机制就是把需要加载的类放入一个配置中,而springboot的命名规则就是META-INF/spring.factories
,而这个文件中就定义了springboot所需要的bean都放入到这个文件中
2.3、运行阶段
.run(args);
三、主程序【自动装配原理】
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组合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //springboot的配置
@EnableAutoConfiguration //自动装配
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@SpringBootConfiguration
@EnableAutoConfiguration
- AutoConfigurationImportSelector :自动配置导入选择器
1、获得候选的配置
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;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
3、我们继续点击查看 loadSpringFactories 方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
,
4、发现一个多次出现的文件:spring.factories,全局搜索它,
spring.factories 看到了很多自动配置的文件;这就是自动配置根源所在!
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
-
结论:
1、SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
2、将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
3、整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
4、它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
5、有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
3.4、spring.factories中的配置文件是如何选择性的加载的
- 以HttpEncodingAutoConfiguration为例子
//表示这是一个配置类
@Configuration(proxyBeanMethods = false)
//自动配置属性
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解(判断是否满足当前指定条件)
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//@Conditional(OnWebApplicationCondition.class)这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
1、一但这个配置类生效;这个配置类就会给容器中添加各种组件;
2、这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
3、所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
4、配置文件能配置什么就可以参照某个功能对应的这个属性类
- 精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
- @Conditional注解的作用
自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效
;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
3.5、SpringApplication.run方法流程分析
一部分是SpringApplication的实例化,二是run方法的执行;
- SpringApplication类的作用
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类