0


[Spring] Spring原理(SpringBoot完结)

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

1. Bean的作用域

1.1 概念

在我们学习SpringIOC和DI阶段的时候,我们学习了Spring是如何帮助我们管理对象的.

  1. 首先我们可以通过@Controller ,@Service ,@Repository ,@Component ,@Configuration ,@Bean 来声明Bean对象.
  2. 通过AppliactionContext(Spring上下文)或者BeanFactory来获取对象.
  3. 通过@Autowired,Setter方法或者构造方法来为应用程序注入所依赖的Bean对象. 可以通过https://blog.csdn.net/2301_80050796/article/details/140531072?spm=1001.2014.3001.5501这篇文章来复习. 创建一个类,叫做Dog类.
@Data@GetterpublicclassDog{@SetterpublicInteger age;@SetterpublicString name;}

之后在

DogBeanConfig

类中创建一个对象,之后把对象放入IoC容器中.

@ComponentpublicclassDogBeanConfig{@BeanpublicDogdog(){Dog dog =newDog();
        dog.setAge(2);
        dog.setName("哈士奇");return dog;}}

之后从上下文中两次拿到dog对象.

@TestvoiddogTest(){Dog dog1 = applicationContext.getBean(Dog.class);System.out.println(dog1);Dog dog2 = applicationContext.getBean(Dog.class);System.out.println(dog2);}

在这里插入图片描述

这里我们发现,Bean对象的输出地址是一样的,说明每次从IoC容器中取出来的是同一个对象.
这也就是单例模式,在默认的情况下,Spring中的Bean都是单例的,这种共行为模式,我们就称为Bean的作用域.Bean的作用域指的是Bean在Spring框架中的某种行为模式.
比如单例模式的作用域:表示在整个Spring中,它只有一个,是全局共享的,也就是当有别人修改它的值之后,另一个人读到的就是被修改的值.

@Test@TestvoiddogTest(){Dog dog1 = applicationContext.getBean(Dog.class);
        dog1.setName("金毛");System.out.println(dog1);System.out.println(dog1.getName());Dog dog2 = applicationContext.getBean(Dog.class);System.out.println(dog2);System.out.println(dog2.getName());}

观察运行结果,我们发现修改之后的Bean对象拿到名字的时候是修改之后的名字.
在这里插入图片描述

那么能不能将Bean对象设置为非单例的呢(每次获取Bean对象都是一个新对象)?我们这时候就要提到Bean的作用域了.

1.2 Bean的作用域

在Spring中支持6种作用域,其中后4种在SpringMVC环境中才会生效.

  1. singleton:单例作用域,表示的是在每个IoC容器中,同名称的Bean只有一个
  2. prototype:原型作用域(多例作用域),每次使用该Bean的时候,都会创建一个新实例.
  3. request:请求作用域,每次http请求都会创建一个新的实例
  4. session:会话作用域,每一个http session周期之内,都会创建一个新实例.
  5. Application:全局作用域,每个ServletContext生命周期内,创建新的实例
  6. websocket:HTTPWebSocket作用域,每个WebSocket生命周期内,创建新的实例 指定Bean对象的生命周期,我们可以通过@Scope(翻译:范围)注解来指定.我们来定义几个作用域不同的Bean.
@ComponentpublicclassDogBeanConfig{@Bean@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)publicDogsingleDog(){Dog dog =newDog();
        dog.setAge(1);
        dog.setName("萨摩耶");return dog;}@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicDogprototypeDog(){Dog dog =newDog();
        dog.setAge(3);
        dog.setName("柴犬");return dog;}@Bean@RequestScopepublicDogrequestDog(){Dog dog =newDog();
        dog.setAge(2);
        dog.setName("哈士奇");return dog;}@Bean@SessionScopepublicDogsessionDog(){Dog dog =newDog();
        dog.setAge(4);
        dog.setName("柯基");return dog;}}

测试不同的Bean获取的对象是否一样.

@RestControllerpublicclassDogController{@AutowiredprivateDog singleDog;@AutowiredprivateDog prototypeDog;@AutowiredprivateDog requestDog;@AutowiredprivateDog sessionDog;@AutowiredprivateApplicationContext applicationContext;@RequestMapping("/single")publicStringsingle(){Dog contextDog =(Dog)applicationContext.getBean("singleDog");return"dog:"+singleDog.toString()+",contextDog:"+contextDog;}@RequestMapping("/prototype")publicStringprototype(){Dog contextDog =(Dog)applicationContext.getBean("prototypeDog");return"dog:"+prototypeDog.toString()+",contextDog:"+contextDog;}@RequestMapping("/request")publicStringrequest(){Dog contextDog =(Dog)applicationContext.getBean("requestDog");return"dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();}@RequestMapping("/session")publicStringsession(){Dog contextDog =(Dog)applicationContext.getBean("sessionDog");return"dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();}@RequestMapping("/application")publicStringapplication(){Dog contextDog =(Dog)applicationContext.getBean("applicationDog");return"dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();}}

我们每次请求有两个对象,一个是通过

@Autowired

注入的Bean对象,一个是通过上下文获取的Bean对象.
单例作用域:
每一次访问都是同一个对象,并且@Autowired 和applicationContext.getBean() 也是同⼀个对象.
在这里插入图片描述
原型作用域:
从上下文获取的Bean对象每一次请求都不一样,但是通过注解注入的Bean对象没有改变,这是由于在项目运行起来之后就已经注入完成了,所以多次请求也不会发生变化.
在这里插入图片描述
请求作用域:
每一次请求中,

@Autowired

applicationContext.getBean()

也是同⼀个对象.每次请求都会创建一个新对象.
在这里插入图片描述
会话作用域:
在一个session中,多次请求,获取到的对象都是同一个.换一个浏览器访问就会重新创建对象.
在这里插入图片描述
在这里插入图片描述
Appliaction作用域:
在⼀个应用中,多次访问都是同⼀个对象
在这里插入图片描述
这个Appliaction的效果和Singleton有些类似,但是他们有所不同,区别在于:Application scope是ServletContext的单例,singleton是⼀个ApplicationContext的单例.在⼀个web容器中ApplicationContext可以有多个,但是一个web容器中只有一个ServletContext容器.(了解)

2. Bean的生命周期

2.1 概念

生命周期指的是一个对象从诞生到销毁的过程.
Bean的生命周期分为一下5个部分:

  1. 实例化(为Bean分配内存空间) —> 构造方法
  2. 属性赋值(Bean注入和装配,比如@Autowired) —> Setter方法注入
  3. 初始化: 就是执行通知,BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法和执行使用注解@PostConstruct 修饰的初始化方法.
  4. 使用Bean.
  5. 销毁Bean: 就是执行销毁容器DisposableBean 接口方法和执行使用注解@PreDestroy修饰的销毁容器的方法.在这里插入图片描述

这个就好比我们想买一套房子:

  1. 需要先买房(实例化,分配内存空间)
  2. 装修,把毛坯房变为精装房(执行属性赋值,执行@Autowired注入赋值)
  3. 购买家电.(执行初始化方法,包括接口实现和注释修饰)
  4. 拎包入住(使用Bean)
  5. 寿命到期,拆迁(销毁Bean)

2.2 代码演示

@ComponentpublicclassBeanLifeComponentimplementsBeanNameAware{privateDog dog;publicBeanLifeComponent(){System.out.println("实例化Bean...");}@AutowiredpublicvoidsetDog(Dog dog1){//set方法注入法this.dog = dog1;System.out.println("属性赋值Bean");}@OverridepublicvoidsetBeanName(String name){System.out.println("执行了BeanNameAware接口的通知方法(初始化Bean)");}@PostConstructpublicvoidinit(){System.out.println("执行了PostConstruct注解修饰的方法(初始化Bean)");}publicvoiduse(){System.out.println("使用Bean");}@PreDestroypublicvoiddestroy(){System.out.println("销毁Bean");}}

测试代码:

@TestvoidbeanLifeComponent(){BeanLifeComponent beanLifeComponent = applicationContext.getBean(BeanLifeComponent.class);
    beanLifeComponent.use();}

测试结果如下:
在这里插入图片描述
即使类中的这些方法变换了顺序,也不会改变这些这些内容输出的顺序.

3. SpringBoot自动配置

SpringBoot的自动配置就是当Spring容器启动后,⼀些配置类,bean对象等就自动存入到了IoC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作.

3.1 Spring加载Bean

3.1.1 问题描述

现在有一个问题,就是属于Spring本身的类自动导入还可以理解,但是如果是第三方的库想要导入Spring是如何做到的呢?
现在我们使用在项目下创建不同的目录来模拟第三方代码的引入.
数据准备:

  1. com.jrj.springprincipledemo软件包之外新创建一个软件包spring_autoconfig,之后在spring_autoconfig引入第三方代码TestConfig.在这里插入图片描述
@ComponentpublicclassTestConfig{publicvoidprint(){System.out.println("打印...");}}
  1. com.jrj.springprincipledemo目录下的运行类中通过上下文来获取这个Bean对象,我们发现是获取不到的.
@SpringBootApplicationpublicclassSpringPrincipleDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =SpringApplication.run(SpringPrincipleDemoApplication.class, args);TestConfig testConfig = context.getBean(TestConfig.class);System.out.println(testConfig);}}

在这里插入图片描述

3.1.2 原因分析

根据前面学过的知识,我们知道,这是由于启动项只能扫描到他所在的目录以及子目录造成的,而在外面的目录却扫描不到.
当我们引入第三方的jar包的时候,第三方jar代码目录一定不再启动类的目录下,那么如何告诉Spring帮我们管理这些Bean呢?

3.1.3 解决方案

我们需要指定路径或者引入文件,告诉Spring,Spring扫描到.
常见的解决方案有两种:

  1. @ComponentScan注解添加扫描路径. 这种方法我们之前在SpringIoC&DI注入介绍过,不再赘述
  2. @Import注解导入@Import导入形式主要有以下两种: - 导入类
@SpringBootApplication@Import(TestConfig.class)publicclassSpringPrincipleDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =SpringApplication.run(SpringPrincipleDemoApplication.class, args);TestConfig testConfig = context.getBean(TestConfig.class);System.out.println(testConfig);}}

我们看到了启动类成功拿到了IoC容器中的Bean对象.
在这里插入图片描述
也可以使用大括号导入多个类:

@Import({TestConfig1.class,TestConfig2.class})
  • 导入
    ImportSelector
    
    接口实现类.
publicclassMyImportSelectorimplementsImportSelector{@OverridepublicString[]selectImports(AnnotationMetadata importingClassMetadata){returnnewString[]{"spring_autoconfig.TestConfig"};}}

需要注意的一点是,给返回值导入类的时候,需要导入类的全限定名称.
在启动类的上面直接导入实现

ImportSelector

的类.

@SpringBootApplication@Import(MyImportSelector.class)publicclassSpringPrincipleDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =SpringApplication.run(SpringPrincipleDemoApplication.class, args);TestConfig testConfig = context.getBean(TestConfig.class);System.out.println(testConfig);}}

依然可以获取到对象:
在这里插入图片描述
问题:但是他们都有⼀个明显的问题,就是使用者需要知道第三方依赖中有哪些Bean对象或配置类.依赖中有哪些Bean,使用的时候需要配置哪些Bean,只有第三方最清楚,能否让第三方来做这件事情呢?

  • 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都是以@EnableXxxx开头,其中封装的就是@Import注解. 1. 第三方提供注解
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(TestConfig.class)public@interfaceEnableTestConfig{}

使用

@Target

标签定义注解在哪里标记,使用

@Retention

注解定义注解的生命周期.之后使用

@Import

注解导入第三方的类对象.
2. 在启动类上提供第三方注解

@SpringBootApplication@EnableTestConfigpublicclassSpringPrincipleDemoApplication{publicstaticvoidmain(String[] args){ApplicationContext context =SpringApplication.run(SpringPrincipleDemoApplication.class, args);TestConfig testConfig = context.getBean(TestConfig.class);System.out.println(testConfig);}}

Bea对象依然可以拿到:
在这里插入图片描述

3.2 SpringBoot原理分析

3.2.1 源码

那么Spring究竟是如何实现自动导入的呢?接下来我们就来查看Spring的源码,我们从

@SpringBootApplication 

开始看起.
这个直接也是Spring实现自动配置的核心

@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@interfaceSpringBootApplication{....}
@SpringBootAppliaction

是一个组合注解,注解中包含了:

  1. 元注解: JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为meta-annotation(元注解),他们分别是: • @Target描述注解的使用范围(即被修饰的注解可以用在什么地方) • @Retention描述注解保留的时间范围 • @Documented描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息(了解) • @Inherited使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,其子类自动具有该注解)
  2. @SpringBootConfiguration:
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configuration@Indexedpublic@interfaceSpringBootConfiguration{@AliasFor(
        annotation =Configuration.class)booleanproxyBeanMethods()defaulttrue;}

里面其实就是一个

@Configuration

,只不过就是封装了一层而已.
3. @EnableAutoConfiguratio
这是Spring自动装配的核心机制,下面详细解释.
4. @ComponentScan
excludeFilter是自定义过滤器,通常用于排除一些类,注解等.

3.2.2 @EnableAutoConfiguratio详解

我们来观察

@EnableAutoConfiguration

注解的实现:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public@interfaceEnableAutoConfiguration{/**
     * Environment property that can be used to override when auto-configuration is
     * enabled.
     */StringENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";/**
     * 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{};}

这个注解主要包含两部分:

  1. @Import(AutoConfigurationImportSelector.class) 使用@Import注解,导入实现ImportSelector的类
publicclassAutoConfigurationImportSelectorimplementsDeferredImportSelector,BeanClassLoaderAware,ResourceLoaderAware,BeanFactoryAware,EnvironmentAware,Ordered{@OverridepublicString[]selectImports(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnNO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry =getAutoConfigurationEntry(annotationMetadata);returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
selectImports

方法有调用了

getAutoConfigurationEntry

方法,获取可以自动配置的配置类信息集合:

protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnEMPTY_ENTRY;}AnnotationAttributes attributes =getAttributes(annotationMetadata);List<String> configurations =getCandidateConfigurations(annotationMetadata, attributes);
        configurations =removeDuplicates(configurations);Set<String> exclusions =getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations =getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);returnnewAutoConfigurationEntry(configurations, exclusions);}

其中

getCandidateConfigurations

方法获取在配置文件中所有自动配置类的集合.

protectedList<String>getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes){List<String> configurations =ImportCandidates.load(AutoConfiguration.class,getBeanClassLoader()).getCandidates();Assert.notEmpty(configurations,"No auto configuration classes found 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;}

获取的是所有基于

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

配置文件和

META-INF/spring.factories

的配置文件.里面包含了很多第三方依赖的配置文件.
在这里插入图片描述
[注意]

  • 加载的时候并不是把所有的配置全部加载进来,他是根据不同的条件来导入对应的配置的,这和@Conditional注解有关系,这个注解是Spring底层的一个注解,就是根据不同的条件来进行自己不同条件的判断,如果满足指定的条件,配置才会生效.
  • META-INF/spring.factories文件是Spring内部提供的⼀个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件中配置即可, Spring就会把相应的实现类注入到Spring容器中.
  1. @AutoConfigurationPackage 源码如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import(AutoConfigurationPackages.Registrar.class)public@interfaceAutoConfigurationPackage{String[]basePackages()default{};Class<?>[]basePackageClasses()default{};}

这个注解主要是导入了配置文件

AutoConfigurationPackages.Registrar.class
staticclassRegistrarimplementsImportBeanDefinitionRegistrar,DeterminableImports{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry){register(registry,newPackageImports(metadata).getPackageNames().toArray(newString[0]));}@OverridepublicSet<Object>determineImports(AnnotationMetadata metadata){returnCollections.singleton(newPackageImports(metadata));}}

Registrar中实现了

ImportBeanDefinitionRegistrar

类,就可以被注解

@Import

导入到Spring容器中.其中

PackageImports(metadata).getPackageNames().toArray(new String[0]))

就是当前启动所在的包名.
所以:

@AutoConfigurationPackage

就是把启动类所在的包下面所有的组件全部都扫描注册操Spring容器中.

3.2.3 总结

SpringBoot自动装配原理大致如下:
在这里插入图片描述
当Spring项目启动的时候,就会自动把这些配置文件中的配置类通过

@Import

注解全部加载到SpringIoC容器中.

4. Spring三级缓存

这个问题其实可以换一种问法,Spring是如何解决循环依赖的?答案就是Spring使用了三级缓存.

4.1 什么是循环依赖

简单来说,就是A对象依赖B对象,B对象又依赖A对象,代码如下:

@ComponentpublicclassA{@AutowiredpublicB b;}@ComponentpublicclassB{@AutowiredpublicA a;}

我们运行之后会发现,程序出现了报错,这是由于在早些版本的时候,Spring会默认自动处理循环依赖,但是在新版本之后,Spring默认不会处理循环依赖.
在这里插入图片描述

4.2 循环依赖出现问题的原因

Spring容器保存Bean的方式是采用缓存的方式,使用Map的结构,其中key为Bean的名称,value为Bean对象,使用的时候直接从缓存中获取.假如A和B相互依赖:

  1. 容器中没有A对象,实例化A对象
  2. 在属性赋值A中的B对象的时候,发现B在容器中不存在,所以要先实例化B.
  3. 在属性赋值B中的A对象的时候,发现A在容器中不存在,所以要先实例化A.
  4. 循环上述过程.

如此一来就会出现死循环.

4.3 Spring解决循环依赖

Spring使用的是三级缓存的方式来解决循环依赖的问题.首先我们需要看一看什么叫做三级缓存.
在这里插入图片描述
三级缓存解决A,B循环依赖的过程如下:

  1. 实例化A对象,A对象被放入了三级缓存区.
  2. 赋值A对象的属性B,此时1,2,3级缓存都获取不到B,就要先初始化B.
  3. 实例化B对象,B对象被放入三级缓存.
  4. 赋值B对象的属性A,在三级缓存中找到了A对象,调用获取Bean之后,A对象就会被保存到二级缓存,之后在三级缓存中删除A对象.
  5. B对象继续后续的处理,注入其他依赖,初始化.完成之后,B对象就是完整的Bean,就会把B放入一级缓存,并在三级缓存中删除.
  6. A对象继续后续的处理,处理完成之后,A对象就是一个完整的Bean了,A被放入一级缓存,之后从二级缓存中删除.

4.4 Spring不支持的循环依赖

Spring框架在处理循环依赖的时候存在一些限制,一下三种情况循环依赖不可以被默认解决.

  1. 构造器依赖: 当两个Bean在它们的构造器中相互依赖时(在构造器上添加@Autowired注解),Spring无法解决这种循环依赖.
  2. 原型Bean循环依赖: 对于Bean是原型的,无法解决循环依赖.
  3. 复杂的AOP环境下的依赖.
标签: spring spring boot java

本文转载自: https://blog.csdn.net/2301_80050796/article/details/141392470
版权归原作者 LileSily 所有, 如有侵权,请联系我们删除。

“[Spring] Spring原理(SpringBoot完结)”的评论:

还没有评论