IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springboot原理初探 -> 正文阅读

[Java知识库]springboot原理初探

前言

  • 本文是作者的入门笔记,学习狂射说,其中的主要知识,借鉴自狂神的课堂笔记。
  • 本文章仅供学习和参考使用,没有任何商业的用途。如果对原作者造成损失,希望大家及时通知作者,我一定会及时处理。
  • 虽然,学习的是老的课程,但是本质还是不变的,然后我是用的是比较新的东西,在组织博客,相当于再学习一遍

运行原理探究

起源·pom.xml

父依赖:主要是管理项目的资源过滤及插件

 <!-- 父依赖 -->
    <!-- lookup parent from repository -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/>
    </parent>

点进spring-boot-starter-parent,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.7.3</version>
  </parent>

点进spring-boot-dependencies:真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心

<groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.7.3</version>

在这里插入图片描述

  • 结论
    • 导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了。

启动器 ·spring-boot-starter

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器
  • spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 用什么功能就导入什么样的场景启动器即可

主启动类·SpringbootApplication

默认的主启动类

//@SpringBootApplication 来标注一个主程序类 , 说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
	public static void main(String[] args) {
		//启动了一个方法,其实启动了一个服务
		SpringApplication.run(SpringbootApplication.class, args);
	}
}

注解·@SpringBootApplication

  • 作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就可以运行这个类的main方法来启动SpringBoot应用

点进@SpringBootApplication:可以看到如下

@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 {
		//······
}

注解·@ComponentScan

  • 这个注解在Spring中很重要 ,它对应XML配置中的元素。
  • 作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
  1. @ComponentScan(param)告诉Spring 哪个packages 的用注解标识的类 会被spring自动扫描并且装入bean容器,param即用来指定扫描包的范围。
  2. 此注解一般和@Configuration注解一起使用,指定Spring扫描注解的package。如果没有指定包,那么默认会扫描此配置类所在的package。
  • @Configuration注解申明当前类是一个配置类,相当于xml配置文件。
  • @ComponentScan和@Configuration一起使用的原因就是基于Spring2.0中的注解配合xml配置文件的实现一样,即在xml配置文件配置ComponentScan包扫描属性。

注解·@SpringBootConfiguration

  • 作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类

点进@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
  • @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件
  • @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用

注解·@EnableAutoConfiguration

  • @EnableAutoConfiguration :开启自动配置功能
  • SpringBoot可以自动帮我们配置 ;*** @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,自动配置才能生效***

点进@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	//······
}
  • @AutoConfigurationPackage : 自动配置包
  • @import :Spring底层注解@import , 给容器中导入一个组件
  • Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器

注解·@Import({AutoConfigurationImportSelector.class})

  • 作用:给容器导入组件
  • @Import({AutoConfigurationImportSelector.class}) :给容器导入组件

点进AutoConfigurationImportSelector.class

  • CTRL+F在本源文件中快速查找
  • 可以找到以下代码【181–189行】
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		//这里的getSpringFactoriesLoaderFactoryClass()方法
		//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法【126–1333行】

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		//这里它又调用了 loadSpringFactories 方法
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

点进 loadSpringFactories【135–169行】

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			/*
				去获取一个资源 "META-INF/spring.factories"
				public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
			*/
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			//将读取到的资源遍历,封装成为一个Properties
			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;
	}

全局搜索META-INF下的:spring.factories

  • IDEA全局搜索快捷键:shift+shift
    在这里插入图片描述

  • spring.factories 自动配置根源所在

  • 也可以在外部库中查找
    在这里插入图片描述

  • 有很多很多自动配置的文件

遗憾·疑问spring.factories源码【看不到大量的自动装配源码】

  • 为什么spring boot2.7.3中找到的spring.factories与下图相差甚远?
    在这里插入图片描述

  • 甚至找不到WebMvcAutoConfiguration的自动装配,但却是存在这个WebMvcAutoConfiguration.java文件

  • 本来按照狂神说的,可以在里面找到WebMvcAutoConfiguration,然后找到WebMvcAutoConfiguration.java

在这里插入图片描述

  • 可以看到这些一个个的都是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. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

不平凡方法·SpringApplication

@SpringBootApplication
	public class SpringbootApplication {
		public static void main(String[] args) {
			SpringApplication.run(SpringbootApplication.class, args);
		}
}

SpringApplication.run分析

  • 分析该方法主要分两部分
    • 一部分是SpringApplication的实例化
    • 二是run方法的执行;
  • SpringApplication
      1. 推断应用的类型是普通的项目还是Web项目
      1. 查找并加载所有可用初始化器 , 设置到initializers属性中
      1. 找出所有的应用程序监听器,设置到listeners属性中
      1. 推断并设置main方法的定义类,找到运行的主类
  • 查看构造器
   public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
  • run方法
    -
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 20:42:44  更:2022-09-24 20:44:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:14:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码