Spring原理

目录

Bean的作用域

Bean的作用域的介绍

Bean有哪些作用域

Bean的生命周期

[Spring boot的自动配置](#Spring boot的自动配置)

原因

解决方案

通过@ComponentScan注解扫描

@Import导入类

@ImportSelector接口实现类

原理分析

@EnableAutoConfiguration

@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage


Bean的作用域

Bean的作用域的介绍

我们先创建一个student类并把它交给Spring管理

java 复制代码
@Component
public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

接着通过直接注入ApplicationContext来获取Spring容器拿到两个student对象

java 复制代码
@SpringBootTest
class DemoApplicationTests {
	@Autowired
	private ApplicationContext context;
	@Test
	void contextLoads() {
		Student student1 = context.getBean(Student.class);
		Student student2 = context.getBean(Student.class);
		System.out.println(student1);
		System.out.println(student2);
	}

}

运行结果

可以看到这两个对象的地址是一样的,说明我们从Spring容器里取出来的对象是同一个

这也是一个单例模式 (确保一个类只有一个实例),在默认情况下,Spring容器中的bean都是单例的,这种行为模式被称之为,bean的作用域,就是指,Bean在Spring框架中的某种行为模式。通俗的讲就是,这个类在一定范围内只能有一个,在这个范围内有人修改了这个类的属性那么另一个人读取到的就是这个被修改的值

如果想要每次或得到的Bean对象都是不同的(也就是非单例),那么此时就是Bean的不同作用域了

Bean有哪些作用域

在Spring中支持6种作用域(后四种在Spring MVC环境才生效)

  • **singleton(默认):**单例作用域,每个Spring Ioc容器内同名称的bean只有一个实例
  • **prototype:**每次使用这个Bean都会创建出新的实例
  • **request:**在web环境中,每次HTTP请求都会会创建新的实例
  • **session:**在web环境中,每个session都会创建新的实例(每个session的实例都不同)
  • **Application:**在web环境中的一个应用程序下,创建的实例都是一样的,每个ServletContext⽣命周期内,创建新的实例
  • **websocket:**每个WebSocket⽣命周期内,创建新的实例

//Application scope就是对于整个web容器(一个web应用程序)来说,bean的作⽤域是ServletContext级别的。这个和 singleton有点类似,区别在于: Application scope是ServletContext的单例,singleton是⼀个 ApplicationContext的单例,在⼀个web容器中ApplicationContext可以有多个,而ServletContext只有一个

Bean的生命周期

生命周期指的是⼀个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做⼀个对象的⽣命周期

Bean的生命周期可以分为以下几个部分:

  1. 实例化bean(分配内存空间)
  2. 给Bean实例属性赋值(注入和装配)
  3. 初始化,执行各种通知比如BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法,接着执行初始化方法(xml定义init-method,使用@PostConstruct,执行初始化后置方法BeanPostProcessor)
  4. 使用Bean
  5. 销毁Bean

实例化和属性赋值对应构造方法和setter方法的注入.初始化和销毁是用户能自定义扩展的两个阶段,可以在实例化之后,类加载完成之前进行自定义"事件"处理

代码展示:

java 复制代码
@Component
public class BeanLifeComponent implements BeanNameAware {
    private Student userComponent;

    public BeanLifeComponent() {
        System.out.println("执⾏构造函数");
    }

    @Autowired
    public void setUserComponent(Student userComponent) {
        System.out.println("设置属性userComponent");
        this.userComponent = userComponent;
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("执⾏了 setBeanName ⽅法:" + s);
    }

    /**
     * 初始化
     */
    @PostConstruct
    public void postConstruct() {
        System.out.println("执⾏ PostConstruct()");
    }

    public void use() {
        System.out.println("执⾏了use⽅法");
    }

    /**
     * 销毁前执⾏⽅法
     */
    @PreDestroy
    public void preDestroy() {
        System.out.println("执⾏:preDestroy()");
    }
}

运行结果

可以看到这个Bean的生命周期就是,先执行构造函数,在设置属性,然后进行初始化,接着进行使用,当程序结束时自动被销毁

Spring boot的自动配置

原因

Spring boot的自动配置就是在启动Spring容器后,Spring会自动帮我们把一些配置类和bean对象等存入IoC容器中,不用我们自己去声明,起到了一个简化开发的作用。那么SpringBoot是如何将第三方依赖的jar包中的配置类以及Bean对象加载到IOC容器中的呢?

我们通过创建不同路径的目录来模拟第三方jar包

java 复制代码
@Configuration
public class Config {
    private String name;
    public void function(){
        System.out.println("执行Config方法");
    }
}

运行结果:

通过错误日志可以知道,Spring并没有找到这个bean对象

Spring想要通过五大注解或者@Bean注解,帮我们把某个Bean对象加载到Spring IoC容器中的话,这个被注解修饰的Bean对象必须和Springboot的启动类在同一个目录下(也就是说对于刚刚的例子,Config对象必须在com.example.principle路径下才能被SpringBoot扫描到)

//@springBootApplication标注的类,就是SpringBoot的启动类

解决方案

通过@ComponentScan注解扫描

通过@ComponentScan注解,直接给Spring制定我们要扫描的路径

java 复制代码
@ComponentScan("com.example.autoConfig")
@SpringBootApplication
public class DemoApplication {

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

}

也可以通过 @ComponentScan({"com.example.autoConfig1","com.example.autoConfig2"})的方式引入多个第三方依赖

运行结果

可以看到这次Spring成功扫描到了我们的第三方jar包,不过如果我们需要引入的第三方依赖过多这种方法显然就太繁琐了

@Import导入类

java 复制代码
@Import(Config.class)
@SpringBootApplication
public class DemoApplication {

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

}

//也可以导入多个类中间用","分开

运行结果

可以看到这种方法也可以但是当要引入的类多了操作起来依然麻烦

@ImportSelector接口实现类

java 复制代码
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //需要导⼊的全限定类名 
        return new String[]
                {"com.example.autoConfig.Config"};
    }
}

启动类直接用Import导入这个类就可以

运行结果

可以看到这种方法也可以成功扫描到我们的Bean对象

不过上述的方法都存在一个问题就是,我们在使用这些方法的时候需要知道我们该引入那些那些Bean对象,但是其实使用者对这些东西是不了解的,如果错误的引入可能会影响整个程序。所以依赖中需要引入那些bean,那些配置,一般都是由第三方依赖给我们提供一个注解(因为这些东西他们是最熟悉的),这个注解一般都是以@Enable...开头,然后会在这个注解中封装@Import

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)

public @interface EnableConfig {
    
}

运行结果

可以看到这种方法直接由第三方依赖封装了导入Bean的过程,这样可以让我们在使用时更加方便而且让代码更加简洁,Spring boot也是使用的这种方法(下面会详细分析)。

其实启动类上的@SpringBootApplication注解就帮我们封装了相关操作,我们点进去查看源码就可以看到

原理分析

@SpringBootApplication是一个组合注解,其中主要包括几个部分

1.元注解(JDK中提供的用来标识注解类型的注解)

  • @Target:描述注解的使用范围
  • @Retention:描述注解保存的时间范围
  • @Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息
  • @Inherited:让被修饰的注解可以被继承,也就是说这个类的子类也会自动拥有该注解

2.@SpringBootConfiguration

这个注解是对@Configuration进行了一个封装,用来标注这是一个配置类

3.@ComponentScan

这个注解的具体用法在上面已经做过演示,主要用来定义需要要SpringBoot扫描的特定的包,如果没有定义,就会从声明该注解的类所在的包开始扫描,这也是为什么SpringBoot项目声明的注解类(被Spring管理的类)必须在启动类的目录下,如果不在则需要额外引入

4.@EnableAutoConfiguration

这个就是SpringBoot自动装配的核心注解

@EnableAutoConfiguration

首先Ctrl+左键点击注解进行查看

可以看到这个注解除了元注解外主要包含两个注解

@Import(AutoConfigurationImportSelector.class)

这个注解导入了AutoConfigurationImportSelector.class类,我们现在看看这个类的实现

//点进DeferredImportSelector

可以看到和我们刚刚的模拟实现的一样,这里也实现了ImportSelector接口

这个SelectImports的作用就是告诉Spring要扫描的类是那些

这里的SelectImports方法主要调用了getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合

//再点进去getAutoConfigurationEntry() 方法

这里通过调用 getCandidateConfigurations(annotationMetadata, attributes) 方法获取在配置文件中配置的所有自动配置类的集合

//继续点进去

Assert,notEmpty相当于是一个断言,如果configuration是空就会报后面的一行错误,接着我们可以根据这段信息,找一下META-INF/spring/%s.imports的包

可以看到这个包里都是一些自动配置相关的信息

//我们随便点进去一个看看

看以看到这些类已经帮我们声明了一些对象,这也是为什么我们有些代码不用去声明,因为Spring在启动的时候回去寻找这个文件,并加载这些类,这些类里会帮我们声明一些Bean对象。不过还有一点,为什么这里报错却不影响我们正常启动Spring呢?

因为在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断 进行动态加载,@Conditional是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会⽣效

注意博主这里的版本是3.4.1,这部分代码已经进行了一些重构,在2点多版本这里的代码和现在不太一样,2点多版本代码如下

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
AnnotationAttributes attributes) {
 List<String> configurations = new 
ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
 ImportCandidates.load(AutoConfiguration.class, 
this.getBeanClassLoader()).forEach(configurations::add);
 Assert.notEmpty(configurations, "No auto configuration classes found in 
META-INF/spring.factories nor in METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
If you are using a custom packaging, make sure that file is correct.");
 return configurations;
}

从这个版本的代码中可以看到,Spring同时还会加载另一个文件META-INF/spring.factories,那么我们再来看看这个文件里是什么。

这个里面也是一些初始化配置相关的信息,如果有一些第三方依赖也想让Spring加载,就需要提供这样一个文件,Spring在加载的时候就会找到所有的META-INF/spring.factories文件并进行加载。

在当前版本下的源码也是有加载这个文件的信息,不过和2点多版本不一样

@AutoConfigurationPackage

这个注解主要是导入一个配置文件AutoConfigurationPackages.Registrar.class,作用就是就是将启动类所在的包下面所有的组件都扫描注册到spring容器中

思维导图如下:

以上就是博主对Spring知识的分享,在之后的博客中会陆续分享有关Spring的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望可以多多支持博主!!🥰🥰

相关推荐
Michael_lcf9 小时前
Java的UDP通信:DatagramSocket和DatagramPacket
java·开发语言·udp
摇滚侠9 小时前
Spring Boot 3零基础教程,WEB 开发 HttpMessageConverter @ResponseBody 注解实现内容协商源码分析 笔记33
java·spring boot·笔记
计算机毕业设计小帅9 小时前
【2026计算机毕业设计】基于Springboot的校园电动车短租平台
spring boot·后端·课程设计
调试人生的显微镜9 小时前
Web前端开发工具实战指南 从开发到调试的完整提效方案
后端
静心观复9 小时前
drawio画java的uml的类图时,class和interface的区别是什么
java·uml·draw.io
Java水解9 小时前
【SQL】MySQL中空值处理COALESCE函数
后端·mysql
Laplaces Demon9 小时前
Spring 源码学习(十四)—— HandlerMethodArgumentResolver
java·开发语言·学习
guygg889 小时前
Java 无锁方式实现高性能线程
java·开发语言
ss2739 小时前
手写Spring第7弹:Spring IoC容器深度解析:XML配置的完整指南
java·前端·数据库
Python私教9 小时前
DRF:Django REST Framework框架介绍
后端·python·django