springboot初始化

news2024/11/24 17:25:45

一、 SpringBean

1. Spring Bean

1) Bean定义

Bean是什么,Bean是特殊的对象,交由Spring管理的Java对象,这类对象在创建的时候会根据spring的一些注解,和IOC,属性如果使用@Autowired的话,会自动赋值。Bean和对象的区别是:bean是spring拖管的,里面的属性也是有值的,对象的话直接new 里面的属性是空的,bean是特殊的对象,spring会对单例Bean进行缓存。



2) Bean生命周期

Spring在创建Bean的时候,首先根据ComponetScan注解来扫描具体哪个包下的所有类,如果类上带有Controller,Service,Response,Componet等注解就会创建对应的Bean对象,并且如果是单例的就会缓存到Bean对象池中,如果是多例的话,不缓存,使用的时候直接创建。BeanDefinition信息包括:





Spring创建Bean的过程:获取类的class对象,通过BeanFactory获取类的元信息(有哪些注解,属于哪个class,依赖哪个Bean等),为每个Bean的元信息都创建BeanDefinition一个对象,并且缓存到Map中(BeanFactory就是用来创建BeanDefinition对象的),在此过程中,如果需要对创建的BeanDefinition加工处理,则可以实现Spring提供的接口BeanFactoryPostProcessor,实现类交给Spring拖管,然后可以在这里面添加一些加工处理的代码,用于创建BeanDefinition对象前后执行的方法。

创建好BeanDefinition,遍历Map实例化Bean,注意,如果是多例的对象不需要实例化,填充属性,此时需要为其他Bean创建对象,创建对象使用class方式,利用反射实例化对象,实例化过程中,如果类实现Aware接口(一共9个,每个Aware接口对应不同的功能,如:获取Bean名称,获取BeanFactory,初始化执行的回调函数等等),在不同的时期执行该接口相关的回调函数,此时使用了模板模式,然后初始化对象,在这个过程,如果创建了BeanPostProcessor(post过滤器),则执行这里面的方法,这里面的方法是创建Bean的前置执行的方法,类似于切面,对所有Bean都可以在这里拦截进行加工处理。

如果该类使用了AOP,需要使用代理创建该类的动态代理的子类,如果没有使用AOP,则直接把创建好的对象放到单例池中(Map集合,key是bean的名称,value是创建的Bean)。

先实例化,再初始化,如果不开启AOP就是普通的对象,如果是开启了,那就是生成的代理对象。

3) 特殊接口

1. 通过注解方式扫描Bean

启动的时候,可以通过XML方式和注解方式扫描Bean,如果是注解的方式,扫描Bean的时候需要通过@Componet(“路径”)指定一下扫描路径。

public class StartSpring { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); } }
@Configuration @ComponentScan("com.test") public class AppConfig { }

2. 准备BeanDefinition集合

Bean在创建的时候,spring会根据类上是否有Componet等注解,然后生成beandefinition对象,生成好后放到map中,此时还没有生成正真的对象呢,当把BeanDefinition都准备好后放到Map中,遍历Map获取每个BeanDefinition,调用getBean(“bean名称”)才创建Bean,如果是单例就缓存起来,如果是多例就直接创建不缓存。

BeanDefinition的作用就是获取Class信息,获取类模板信息就可以修改类的信息,所以修改类可以在这个过程做操作。



3. Spring创建代理对象

场景:Spring整合Mybatis的时候,需要为Mapper生成代理对象,在BeanFactory创建BeanDefinition的时候,有一些属性依赖Mapper接口,这时候就需要为Mapper创建代理对象,并且缓存到单例池中,这时候使用这种方式创建代理对象即可。

import org.springframework.beans.factory.FactoryBean; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { // 创建Object代理对象 需要创建什么代理对象就创建什么代理对象 Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{Object.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return proxy; } }); return null; } @Override public Class<?> getObjectType() { return null; } }

4. 通过各种Aware接口设置想要的值

Aware一共有9个,每种都有不同的用途。以下是两种例子:

import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService implements BeanFactoryAware, BeanNameAware { @Autowired private User user; private BeanFactory beanFactory; private String beanName; /** * 获取当前Bean创建的BeanFactory对象 * @param beanFactory * @throws BeansException */ @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } /** * 获取当前Bean在单例池中的名称 * @param beanName */ @Override public void setBeanName(String beanName) { this.beanName = beanName; } }

5. 初始化完需要执行的方法可使用接口

通过InitializingBean可以对Bean初始化完成后需要执行的方法,继承该方法,可以在初始化完成后执行该操作。

import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService2 implements InitializingBean { @Autowired private User user; @Override public void afterPropertiesSet() throws Exception { // TODO 初始化完成后执行的操作 } }

2. 循环依赖

1) 循环依赖过程

构造方法的循环依赖是无法解决的,因为对象在创建的时候要调用构造函数,调用的时候还要去创建依赖的对象,依赖的对象反过来依赖自己,还没有实例化对象,无法提前暴露对象。



Spring创建循环依赖的对象,并为其赋属性值的过程是:

如A依赖B,B依赖A,A在实例化后,在为属性B赋值的时候,B也会像A一样实例化,然后进行属性赋值,当为A赋值的时候回去缓存中查找,如果A没有进行AOP操作的话,A在实例化后就会把它的原始对象(原始对象是属性还没有填充值的对象)放到单例池中,二级缓存中,提前暴露给别的Bean引用,只是此时的A还不完整,就放到二级缓存中。B从一级缓存中获取没有找到A,就从二级缓存中查找,发现找到了提前暴露的A,然后赋值引用,完成属性的赋值,然后返回A,A继续操作后续过程。

但是如果A发生了AOP的话,就不能把原始对象直接放到二级缓存中了,因为AOP最终要生成代理对象,代理对象和原始对象不是一个的话,为B赋值的是原始对象的引用,为A赋值的是代理对象(放在一级缓存单例池中的对象),这样就违背了单例模式了。

此时A在实例化后把它自己放到三级缓存中,把实例对象传进去,生成一个lambad表达式(() -> getEarlyBeanReference(beanName,mbd,bean原始对象)),不直接生成代理对象,后面谁用到谁再生成,然后赋值B属性的时候,创建B的Bean对象,B引用A的时候,会从一级缓存获取发现没有,从二级缓存获取也发现没有,从三级缓存获取,拿到了lambad表达式,然后执行获取A的代理对象,此时A的代理对象其实是提前生成了,B帮忙生成的,生成后会把A的代理对象放到二级缓存中,然后引用指向了B中的Bean的A属性,然后返回A。



创建代理对象具体代码:



A填充B的属性,执行Aware,init操作后(此时还是针对的是A的原始对象,不是代理对象),执行完后当要开始生成代理对象的时候,还是会从一级缓存获取一下,发现没有,然后从二级缓存获取,发现二级缓存中存在A的代理对象(spring中代理对象里面有一个引用,指向了原始对象),然后获取后直接把该对象放到一级缓存中,完成了初始化过程。

这里有一个细节,为了保证前后对象的唯一性,通过名称找到的对象是不是自己的对象,在B中提前生成的A的代理对象后,会把对象放到earlyProxyReferences map中,当A在执行完第五步属性赋值完后开始生成代理对象前,要到这个map中通过beanName名称获取到对应的对象,然后判断是自己的代理对象,就不生成代理对象了,然后再从一级缓存,二级缓存中查找,如果不是自己的对象,则调用wrapIfNecessary方法进行生成代理对象。



2) 三级缓存

一级缓存是最终存放Bean对象的Map集合,二三级缓存是为解决循环依赖而创建的,在循环依赖过程中,一个对象提前暴露的话会存在二级缓存中,如果这个对象在初始化的时候,进行了AOP操作,就需要生成代理对象,会先在三级缓存中存放一个要生成代理对象的lambad表达式,后面谁依赖的了就执行lambad表达式,生成代理对象,但是由于不是自己生成的,生成完后的代理对象需要放到二级缓存中。

二级缓存是为了提前暴露对象而产生的,三级缓存是为了AOP对象需要生成代理对象产生的。如果A没有循环依赖,但是进行了AOP操作,也是会生成一个lambad表达式放到三级缓存中,但是没有其他Bean依赖A,所以三级缓存中的lambad表达式始终都不会执行到,执行不到就不会放到二级缓存中,当A执行到最后的时候,从二级缓存中获取代理对象是获取不到的,所以自己生成。

三级缓存包括:

Ø singletonObjects:一级缓存,用于存放最终的Bean对象,一级缓存对于循环依赖没有什么帮助;

Ø earlySingletonObjects:二级缓存,存放不完整的对象,创建提前暴露的对象放在里面,提前暴露的对象可能还没有进行完全的初始化,属性还没有赋值完成或者是完全;

Ø singletonFactories:三级缓存,用于存放对原始对象的包装的一个lambad表达式,里面存储的是() -> getEarlyBeanReference(beanName,mbd,bean原始对象),还没有进行创建代理对象,只是存一个表达式,value是ObjectFactory;

Ø earlyProxyreferences:其实还包括一个缓存,他用来记录某个原始队形是否进行了AOP了。

3) 为什么需要三级缓存

在构建原始对象的时候,不知道是否会发生循环依赖,如A依赖B,B没有依赖A,二级缓存存的是对象,三级缓存存的是ObjectFactory,如果只利用二级缓存省去三级缓存,那么二级缓存应该存什么,一个Bean在初始化的时候,在执行到BeanPostProcessor的时候才知道自己要不要生成代理对象,存原始对象还是存代理对象,如果没有发生AOP,则需要存原始对象,如果发生了AOP则需要存储代理对象,这样就矛盾了,所以放到三级缓存中一个表达式,在使用的时候再创建是最好的选择,如果没有AOP则可以省去二级缓存和三级缓存其实也是可以的。

二、 SpringBoot

1. Refresh方法

Spring容器创建之后,会调用它的refresh方法,refresh的时候会做很多事情:比如完成配置类的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造等等。

以web为例,对应的Spring容器为AnnotationConfigEmbeddedWebApplicationContext。它的refresh方法调用了父类AbstractApplicationContext的refresh方法:



1) prepareRefresh方法

表示在真正做refresh操作之前需要准备做的事情:

l 设置Spring容器的启动时间,撤销关闭状态,开启活跃状态;

l 初始化属性源信息(Property);

l 验证环境信息里一些必须存在的属性。

2) prepareBeanFactory方法

从Spring容器获取BeanFactory(Spring Bean容器)并进行相关的设置为后续的使用做准备:

l 设置classloader(用于加载bean),设置表达式解析器(解析bean定义中的一些表达式),添加属性编辑注册器(注册属性编辑器);

l 添加ApplicationContextAwareProcessor这个BeanPostProcessor。取消ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationContextAware、EnvironmentAware这5个接口的自动注入。因为ApplicationContextAwareProcessor把这5个接口的实现工作做了;

l 设置特殊的类型对应的bean。BeanFactory对应刚刚获取的BeanFactory;ResourceLoader、ApplicationEventPublisher、ApplicationContext这3个接口对应的bean都设置为当前的Spring容器;

l 注入一些其它信息的bean,比如environment、systemProperties等。

注意:https://blog.csdn.net/z69183787/article/details/104364846

2. Springboot如何加载一个starter依赖组件并完成初始化

引入maven starter依赖,基本就满足了日常的web接口开发,而不使用springboot时,需要引入spring-web、spring-webmvc、spring-aop等等来支持项目开发。

实际上那些必要的依赖在spring-boot-starter-web中已经被引入了,说白了,starter可以当成是一个maven依赖组,引入这个组名就引入了所有的依赖。

对于其他一些starter,比如要使用redis、jpa等等,就不仅仅是引入依赖了,还需要实现一些初始的配置,比如常使用@Configuration,这个注解就会在springboot启动时去实例化被其修饰的类,前提是springboot配置了@EnableAutoConfiguration,而springboot启动类默认的@SpringBootApplication中默认包含了该注解,所以不用再显示引入,最后需要在starter项目中META-INF/spring.factories中添加:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.xxx.Xxx

这样在springboot启动时,才能正确加载自定义starter的配置。所以说,starter中简单来讲就是引入了一些相关依赖和一些初始化的配置。

为什么加了@Configuration注解还是要配置META-INF/spring.factories呢?因为springboot项目默认只会扫描本项目下的带@Configuration注解的类,如果自定义starter,不在本工程中,是无法加载的,所以要配置META-INF/spring.factories配置文件。

为什么配置了META-INF/spring.factories配置文件就可以加载?这里才是springboot实现starter的关键点,springboot的这种配置加载方式是一种类SPI(Service Provider Interface)的方式,SPI可以在META-INF/services配置接口扩展的实现类,springboot中原理类似,只是名称换成了spring.factories而已。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1178348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]

场景 在使用Android Studio的虚拟设备运行App时&#xff0c;需要创建很大镜像文件。这些镜像文件一般都在系统盘&#xff0c;导致系统盘占用增大。怎么把这些镜像的存放路径设置在其他盘&#xff1f; 说明 虚拟设备的和它的镜像默认是放在用户目录\.android\avd位置。如果是在…

同样是PM,产品经理和项目经理有啥不一样?

大家好&#xff0c;我是老原。身边有很多人都问&#xff1a; “干几年的技术可以做到项目经理&#xff1f;” “我要从项目经理转型到产品经理吗&#xff1f;” “产品经理和项目经理&#xff0c;哪个发展前&#xff08;钱&#xff09;景更好” …… 不难发现&#xff0c;…

Python中日志异步发送到远程服务器

背景 在Python中使用日志最常用的方式就是在控制台和文件中输出日志了,logging模块也很好的提供的相应 的类,使用起来也非常方便,但是有时我们可能会有一些需求,如还需要将日志发送到远端,或者直接写入数 据库,这种需求该如何实现呢? StreamHandler和FileHandler # -*- cod…

java泛型的深入 泛型还可以在很多地方进行定义 泛型类 泛型方法 泛型接口 泛型的继承和通配符 泛型类练习

文章目录 泛型的深入泛型还可以在很多地方进行定义泛型类泛型方法泛型接口 泛型的继承和通配符泛型类练习总结 泛型的深入 public static void main(String[] args) {//在没有泛型的时候怎么存储数据ArrayList listnew ArrayList();list.add(1);list.add("abc");//遍…

C语言 用字符串比较函数cmp来做一个门禁:账号密码是否匹配 (干货满满)

#include<stdio.h> #include<string.h> void fun04() {for (int i 0; i < 3; i){char *str01 "hello";char uname[100] ;printf("请输入账号");scanf("%s",uname);char *str02 "123456";char pword[100];printf(&qu…

数字化转型:云表低代码开发助力制造业腾飞

数字化转型已成为制造业不可避免的趋势。为了应对市场快速变化、提高运营效率以及降低成本&#xff0c;制造业企业积极追求更加智能化、敏捷的生产方式。在这个转型过程中&#xff0c;低代码技术作为一种强大的工具&#xff0c;正逐渐崭露头角&#xff0c;有望加速制造业的数字…

QGC 中添加海康威视摄像头记录(Qt For Android 使用 JNI 进行JAVA 与 C++ 的通讯)

文章目录 1. 配置海康威视 SDK 下载库文件移植工程文件添加动态库&#xff08;.so&#xff09;Android xml 配置添加 java 文件 2. JavaQGCActivity.javaHkwsManager.java 3. C头文件添加&#xff1a;C 中调用 Java 静态函数&#xff08;hcnNetSDKInit&#xff09;JNI 传入规则…

【电路笔记】-串联RLC电路分析

串联RLC电路分析 文章目录 串联RLC电路分析1、概述2、瞬态响应3、AC响应4、RCL和CLR配置5、结论 电阻器 、电感器 (L) 和电容器 © 是电子器件中的三个基本无源元件。 它们的属性和行为已在交流电阻、交流电感和交流电容文章中详细介绍。 在本文中&#xff0c;我们将重点讨…

二蛋赠书七期:《云原生数据中台:架构、方法论与实践》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

基于北方苍鹰算法的无人机航迹规划-附代码

基于北方苍鹰算法的无人机航迹规划 文章目录 基于北方苍鹰算法的无人机航迹规划1.北方苍鹰搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用北方苍鹰算法来优化无人机航迹规划。 …

Qt QTableView排序

1.简介 在开发过程中&#xff0c;我们需要通过点击表头来对QTableView或QTreeView等一系列高级视图进行排序操作&#xff0c;以下是进行排序的步骤。 步骤&#xff1a; 首先创建了一个QStandardItemModel对象或者继承QAbstractTableModel类作为数据模型&#xff0c;并设置了…

如何获取HuggingFace的Access Token;如何获取HuggingFace的API Key

Access Token通过编程方式向 HuggingFace 验证您的身份&#xff0c;允许应用程序执行由授予的权限范围&#xff08;读取、写入或管理&#xff09;指定的特定操作。您可以通过以下步骤获取&#xff1a; 1.首先&#xff0c;你需要注册一个 Hugging Face 账号。如果你已经有了账号…

Android修行手册 - 实现POI上万行的大数据量Excel读写操作,解决内存溢出

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

web前端JS基础------制作一个获取验证码

1&#xff0c;需要一个定时器&#xff0c;和一个button&#xff0c;通过点击事件启动获取验证码 2&#xff0c;参考代码如下 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><…

Linux中for循环

for do done 复习知识点&#xff1a;cut命令&#xff0c;id命令&#xff0c;finger命令&#xff0c;for循环 程序如上&#xff0c;-d 接分隔符&#xff0c;-f后的数字表示分隔后的列 从结果可以看出&#xff0c;系统上没有finger这个命令&#xff0c;后面会学到yum安装命令&a…

上手SQL语句调优必须了解的内容——Explain

在做性能测试时&#xff0c;资深的性能测试工程师&#xff0c;都会帮助研发同学优化sql语句&#xff0c;听起来很高深&#xff0c;但是具体操作是比较容易的&#xff0c;使用expain命令就可以了&#xff01;本文我会用最简单有效的方式带大家掌握expain的使用方法&#xff01; …

JVM虚拟机:垃圾回收器之Serial(年轻代)

本文重点 本文将介绍年轻代的Serial回收器,它最主要的特征就是串行化的回收器。 运行方式 Serial是一个单线程的收集器,在进行垃圾收集的时候,必须暂停其它所有的工作线程(java程序找一个安全点safe point然后才停止执行,进行等待)直到垃圾回收结束,下的运行状态图如…

迅为iTOP-i.MX8M开发板使用 make 工具

make 工具是编译辅助工具&#xff0c;用来解决使用命令编译工程非常繁琐的问题。 调用这个命令工具&#xff1a;我们在 windows 上编程使用 ide &#xff0c;我们有图形界面&#xff0c;有相应的按钮&#xff0c;比如说 build 或者 run 来编译。其实 make 这个编译辅助工具使…

Dajngo学习笔记(3)

电话号码管理 查看功能 class PrettyNum(models.Model):mobilemodels.CharField(verbose_name"电话号",max_length11)pricemodels.IntegerField(verbose_name"价格")level_choice((1,"一级"),(2,"二级"),(3,"三级"))level…

RISC-V处理器设计(四)—— Verilog 代码设计

一、前言 从6月底刚开始接触 risc-v 架构&#xff0c;到现在完成了一个 risc-v cpu 的设计&#xff0c;并且成功移植了 rt-thread nano 到本 cpu 上运行&#xff0c;中间经过了 4个多月的时间&#xff0c;遇到了数不清的问题&#xff0c;也想过放弃&#xff0c;但好在最后还是…