SpringBoot源码分析

一、预热

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二、源码分析

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方法的定义类,找到运行的主类

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值