• Spring面试(源码手撕)


    目录

    Spring的bean是如何注入的(Bean和new)

     单例Bean的实现(单例池)

    Bean的初始化前-初始化-初始化后

    1.在初始化前执行一个方法获取数据封装给User

    ​编辑

     2.在初始化时期InitializingBean

    初始化后(AOP)

    那么我们怎样在初始化后阶段获得bean对象构造参数值?

     构造方法常见问题

    问:那么我们构造中参数的值是怎么获得的呢?它有值吗?

     Bean的实例化和初始化

     Spring的事务是怎么工作的

    1.问:当一个事务里面再调用一个事务的情况

     2.再次分析事务失效:

    @Configuration注解的作用

    @Component和@Bean的区别+常见注解场景

    Spring为什么要用三级缓存

     问:我们出现循环依赖的本质是什么——>Spring的三级缓存

     问:如何解决的?——>利用Spring三级缓存

     问:如果我们叠加一个AOP,会出现什么样的问题?怎么样去解决?

     Spring二级缓存

    总结earlySignletonObejects的作用:

     三级缓存

     多例prototype循环依赖场景


    Spring的bean是如何注入的(Bean和new)

     众所周知,我们从容器中getBean,Bean是有属性的,而不是只是一个空的对象;所以说,当我们获取一个bean对象时,1.首先通过无参构造得到对象——>2.然后利用反射通过类对象得到类信息,并将这些变量进行注入

    属性赋值:

    依赖注入:

     首先我们分析bean对象和普通的无参构造的实例化对象区别

    众所周知,bean对象是经过初始化等一系列操作,

     虽然我们无参构造方法是得到了UserService的对象,但是里面注入了OrderService,并且OrderService也是一个bean,按道理来说OrderService是应该有值,但是无参构造的话OrderServie肯定是没有值的,所以我们需要进行依赖注入

    解决:

    找到标注@AutoWired注解的变量,然后设置权限,利用反射进行访问,因为OrderService本身也是个bean,所以将其注入我们的一个无参构造和它的bean信息

     单例Bean的实现(单例池)

    问:为什么我们每次getBean(),相同名字都能得到相同的bean 

     实际上是通过Map(CurrentHashMap)的方式,通过相同的key获取对应的bean,而后续几次获取bean相同的原因,就是第一次获取到的bean被放入到了Map集合中,所以后续getBean获取的是同一个Bean对象

     (44条消息) 单例模式的思考_Fairy要carry的博客-CSDN博客


    Bean的初始化前-初始化-初始化后

    问: 如果是@Autowired相当于就是new User()的无参构造,那么我们想要获取到用户权限信息,然后将其封装到User中赋值给admin对象怎么办?

    解决:

    1.在初始化前执行一个方法获取数据封装给User

     我们可以在创建Bean前(在初始化前阶段)——>调用一个方法得到数据库中信息,然后封装给User赋值给admin(那么这个方法怎么样被调用呢?)

    我们可以利用@PostConstruct注解放在方法上,会在初始化前阶段执行,本质也就是通过反射,通过类对象得到方法进行遍历(选择@PostConstrucet的方法),然后通过invoke()方法执行

     初始化前的伪代码:

     2.在初始化时期InitializingBean

     我们可以实现InitializingBean这个接口重写afterPropertiesSet()方法——>然后获取数据库中的权限信息封装到User中这样我们的Bean就直接有信息了

    那么怎么调用这个方法呢?

    通过bean的强转InitializingBean然后进行调用

    初始化后(AOP)

    思考:我们AOP的时候不是动态代理吗,会生成一个代理对象,那放到单例池中的对象是我们之前生成的普通对象还是初始化后生成的代理对象?——>代理对象(毕竟我们增强之前的对象,那传肯定传增强了的)

    (44条消息) 动态代理回顾_Fairy要carry的博客-CSDN博客

     例子:

     进行测试

    发现我们的UserService这个Bean中的属性OrderService并没有赋值——>这是因为我们的Bean初始化流程,这个UserService的bean对象实际已经是一个proxy对象了,后续它并没有依赖注入,所以属性是没有值的

    那么我们怎样在初始化后阶段获得bean对象构造参数值?

     实际上是利用的是:基本对象赋值给代理对象target,所以我们这个代理的UserService bean对象中里面的OrderService是有值的(因为我们原来的基本对象是经历了依赖注入的,那么构造的orderService属性就有值)——>然后再将这个代理的对象放在单例池中

     那么调用方法实际上直接想到动态代理,执行我们父类的方法,然后进行增强

    (44条消息) 动态代理回顾_Fairy要carry的博客-CSDN博客

     构造方法常见问题

     Spring在没有声明采用哪个构造方法时——>会优先采用无参的构造方法,如果只有一个的话,就用了

     如果一定要采用某个设定的构造,我们可以使用@Autowired注解

    问:那么我们构造中参数的值是怎么获得的呢?它有值吗?

    有的,像这里的OrderService会在单例池map中进行寻找这个bean(先根据类型OrderService再根据名字orderService进匹配),如果没找到就会创建这个bean,放在单例池中,并且传给这个构造(注意循环依赖的问题,可能OrderService中又注入了UserService)——>一般我们就将一个不归Spring管理,自己手动管理

     

     Bean的实例化和初始化

    实例化就是:通过构造方法得到对象

    初始化:调用我们定义的初始化方法,实现我们实例化对象的方法(在创建bean的过程中实现的,比如实现InitializingBean接口重写的方法过程)

     Spring的事务是怎么工作的

    1.记得连接的autocommit自动提交需要关闭,不然你执行一个sql就提交,后面咋地回滚

    2.然后要记得既然要回滚,那么我们sql所处的连接必须得是同一个连接

    3.@Transaction生效判断对象的调用是否是代理对象

    (44条消息) (深入+面试)Spring异常失效的场景_Fairy要carry的博客-CSDN博客

    1.问:当一个事务里面再调用一个事务的情况

    像这里的a方法执行,是谁调用的?是我们的一个UserService对象调用的对吧,那这个对象其实就是我们实例化的这种new UserService()的对象,而我们事务生效它是不是得代理对象执行才对,所以a()方法事务是不生效的

     

     2.再次分析事务失效:

    1.首先是UserServiceBase中的a方法@Transactional进行事务声明——>再结合这里的@Component可知->所以说这里就是我们的一个代理对象

     2.UserService中调用a()方法,通过UserServiceBase的注入调用a方法,而这里调用的对象其实也就是代理对象

     3.我们也可以自己注入自己

    ps: @Autowired的对象是通过接口的话,spring就默认会去使用jdk动态代理,jdk动态代理只能对实现了接口的类生成代理,而不是针对实现类——>所以我们@Autowired以此生成代理对象来调用这些方法

    (44条消息) 一文看懂Springboot的@Autowired和@Resource区别_黑人月的博客-CSDN博客_springboot中autowired和resource


    @Configuration注解的作用

    在这里如果不+@Configuration注解,那么前两个bean所获取的数据源dataSource都是不相同的,毕竟你执行了两次方法 ,那么你业务里面jdbcTemplate执行的sql就会创建一个新的数据源(因为你这里的数据源并没有注册到单例池中),那么就会自动提交,事务失效

    而我们+@Configuration,@Configuration本身也是基于动态代理实现,作用替换我们的xml文件,相当于配置了我们的容器,而我们这些bean在放到我们的单例池中都是单例的,所以说数据源就会是相同的,然后我们这个数据源配置不自动提交——>业务调用执行sql的时候就会用的

     (44条消息) @Configuration的使用 和作用_千羽公子的博客-CSDN博客_@configuration

    (44条消息) @Configuration的作用_专抢傻瓜棒棒糖的博客-CSDN博客_@configuration的作用


    @Component和@Bean的区别+常见注解场景

    (44条消息) Spring注解比较,@bean和@component的区别_Java笔记虾的博客-CSDN博客


    Spring为什么要用三级缓存

    首先我们引入一个问题,AService中注入BService,然后BService中注入AService,出现一个循环依赖问题,然后我们测试看是否报错 

    结果是没有报错的,成功输出bService

     问:我们出现循环依赖的本质是什么——>Spring的三级缓存

    单例池SingletonObjects——>earlySingletonObjects——>第三级缓存

    1.首先我们AService创建基本对象后,进行第二步依赖注入时,发现BService在单例池中并没有,所以会创建一个BService——>2.来到BService流程,因为我们的AService它是还没有初始化完成的,也就是没有进入单例池中的,所以说BService又寻找AService,这样就是循环锁住了,类似于死锁

     问:如何解决的?——>利用Spring三级缓存

    利用二级缓存——>1.创建一个Map,将我们创建的AService普通对象放入——>2.然后在BService中第二步依赖注入时,填充AService会先从单例池中找(肯定没有),然后从我自定义的Map中寻找,然后添入,这样就解决了循环依赖的问题

     问:如果我们叠加一个AOP,会出现什么样的问题?怎么样去解决?

    因为我们的AOP它是在初始化之后这个时期执行的,而我们之前AService中依赖注入,是将普通对象放到自定义Map中,所以导致我们BService中注入的AService是一个普通对象,那么就会导致一个问题,在后面流程不能执行我们的AOP切面方法,因为那是需要代理对象执行的

     解决:

    我们可以提前执行AOP,将我们的代理对象AService提取存入自定义的map中——>在BService中,获取AService对象,首先会从单例池中寻找,肯定是没有的——>然后再creatingSet中(AService创建中)寻找发现没有,就会自定义生成一个AService(这样就会出现循环依赖,因为这样又进了AService,以此循环)——>如果定义了切面就会去执行我们的切面提前AOP,获取我们的AService代理对象注入到我们的BService中,这样BService就可以创建完毕

     Spring二级缓存

    目的:当又多个依赖注入时,比如AService中需要注入BService和CService,然后在BService和CService中又需要AService对象,如果两个都进行AOP,就两次AOP了,并且代理对象都不一致

     解决:

    利用earlySingletonObjects,将我们AOP生成的AService代理对象放到里面,其他B/CService需要AService对象注入时,从earlySingletonObjects中获取即可

    注:当然这些对象此时都是属性是没有完全赋值的,因为AOP是提前执行的嘛——>为了获取代理对象,后续的填充其他属性都还没有完成——>所以此时不能放到单例池中,不然被调用的时候就会出问题;

    总结earlySignletonObejects的作用:

    帮助那些没有经历完整生命周期的单例bean对象进行存储,防止创建多个bean,创建多个不完整的bean,在后期会继续我们的bean生命周期,放到单例池中

     三级缓存

    作用:让我们解决循环依赖问题的方式更加方便一点,利用lambda表达式,判断我们的AService是否需要AOP,如果需要就得到代理对象,并将代理对象存储起来,如果不需要就是普通对象,将普通对象存储起来——>我们的lambda(考虑到aop的lambda)放到自定义map中,这个map打破了我们这个循环依赖的作用

    然后在BService中,它不是需要AService填充属性吗,在单例池中寻找后——>判断AService中是否是创建中(creatingSet)——>出现循环依赖——>在二级缓存earlySingletionObjects中寻找发现也没有——>最终在第三级缓存,也就是我们自定义map中的lambda表达式中得到AService对象,并放入二级缓存(保证了bean的一致性,此时bean生命周期是不完整的,后续的属性填充等操作还没有完成)

     

    后面lambda表达式中的bean对象会被移除掉,在此之前放到了二级缓存earlySingletonObjects中 


    @Async问题

    @Async异步注解实际上是基于动态代理实现,也就是说会生成代理对象,那么在bean生命周期的初始化后阶段中也有aop操作生成代理对象,这样就会起冲突,从而报错

    解决:

    我们可以用@Lazy注解,标记到BService上,不会去判断单例池中有没有,而是直接产生BService代理对象

     像在我们AService和BService两种相互注入时,实例化两个对象但是都实例化不出来,因为A等B对象,B等A对象,我们可以采用@Lazy注解直接产生代理对象

     多例prototype循环依赖场景

    如果两个A/BService相互注入,然后采用多例,那么它们每次寻找彼此的Service都会是新的,这样就会出现循环依赖异常 

     

  • 相关阅读:
    防火墙基础之H3C防火墙和三层交换机链路聚合的配置
    第4集丨做一个内心强大的人
    第3章业务功能开发(实现显示线索主页面,并查询表单各个下拉框数据)
    ETLCloud助力富勒TMS实现物流数仓同步
    采访 Footprint Analytics CEO Navy:AI 与 Web3 的融合之道
    Redis的内存淘汰策略
    感统失调长大就好了?错,长大就晚了!
    Android 11 AudioPolicyService 启动流程
    基于SSM的农业信息管理系统的设计与实现(有报告)。Javaee项目。ssm项目。
    【网络爬虫】2 初探网络爬虫
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/126241668