Spring面试高频

Spring面试高频

Spring如何创建一个Bean对象

  • Spring内部默认是通过无参构造方法来创建的bean对象,但是该bean对象和我们自己调用的无参构造方法创建的普通类对象还不太一样,因为Spring创建的bean对象的属性是有值的 =》原因是应为Spring在调用无参构造方法创建bean对象之后,这个创建bean对象的过程还远远没有结束,在这之后还有一个依赖注入的问题,这个依赖注入的过程就是给之前创建的bean对象设置属性值。

什么叫单例池?作用?

  • 单例bean(一个bean实例id对应一个bean实例),单例池其实就是一个Map集合,它的key表示beanName(bean实例的名称),它的value表示的就是bean实例对象;我们在获取某个bean实例对象时,首先从单例池Map中寻找,如果找到直接拿来即用,如果找不到则需要创建对应bean对象,并把创建的bean对象放入到单例池中供以后调用(问题:单例池什么时候清理一次?或者说它是根据什么算法来清理的?)

Bean对象和普通对象之间的区别

  • Bean对象和普通对象其实本质是一样的,只是经过特定阶段它的称呼不同

@PostConstruct注解如何工作?(初始化前)

  • Spring底层默认调用类的无参构造方法来创建类对象,但是如何把这个普通的类对象变成我们容器所需要的bean对象还有经过一些过程,调用类的无参构造-》普通类对象-》依赖注入-》初始化前(@Post Controller)-》初始化-》初始化后-》放入Map单例池-》Bean对象 ,那么这个@PostController 注解的作用是什么呢?该注解作用就是在把普通对象转为bean对象的过程中,Spring底层会通过反射机制获取这个对象的类,然后遍历这个类中的方法(找到添加@PostController注解的方法)然后执行添加该注解的方法。

Bean的初始化如何工作

初始化就是判断对象的类是否实现了InitializingBean 接口并是否重写里面的afterPropertiesSet 方法,如果实现了该接口并重写了该方法,那么就要执行该方法(通过看源码AbstructAutowirexxxxBeanFactory 类中的doCreateBean )。

Bean的初始化和实例化的区别

  • bean的实例化就是调用类的无参构造方法来创建类对象
  • bean的初始化就是在普通对象变成bean对象的过程中会经历一个阶段就是初始化阶段,这个初始化阶段会执行一个接口中的方法,如果这个方法执行时抛出异常,那么这个bean对象就会创建失败!

什么是初始化后

  • 初始化后(AOP)-》代理对象

推断构造方法是什么意思?

  • Spring底层默认是通过构造方法来创建对象的(类似于java类在不声明任何构造函数的情况下内部自动生成一个无参的构造函数是一个道理)
  • 那么当一个类即定义了一个无参构造函数和一个有参构造函数,Spring会通过哪个构造函数来创建bean对象呢?默认通过无参的构造函数
  • 那么当一个类中只定义了一个有参的构造函数没有定义无参构造函数,则Spring会通过这个唯一的有参构造函数来创建bean对象
  • 那么当一个类中定义了两个不同的有参构造函数没有定义无参构造函数,Spring会通过哪个构造函数来创建bean对象呢?都不用并且程序报错!因为在没有默认的无参构造函数的情况下Spring不知道到底调用哪个有参构造函数发生“矛盾”就干脆不用了!
  • 怎么解决呢?可以人为指定Spring调用哪个有参构造函数来创建bean对象,那就是在指定的有参构造函数上使用@Autowired 注解

什么是先bytype再byname(接着上面的推断构造方法引出疑问?)

  • 假设我们使用@Autowired 注解来给Spring指定调用哪个有参构造函数来创建bean对象,但是有一个问题就是有参构造函数这个构造方法是由参数的,假设这个参数是另一个类对象那么这个类对象的bean实例也正好存在Spring容器中,那么Spring是怎么拿到这个bean实例呢?
  • 这里就说到了我们了解的bean的自动装配了,一个是byType ,另一个是byName ,那么到底是先byType再byName,还是先byName再byType呢?
  • 分析:如果是先byName,就是根据单例池中bean对象的key(也称为bean的id/beanName-bean对象的标识),那么我们在genBean时就需要实现了解对应bean对象的key(单例bean,bean对象的key不会重复),这样比较麻烦,就是每个人在使用bean对象都要知道bean对象的key
  • 分析:如果是先byType,直接根据bean对象的类型在单例池中寻找,可能会找到多个符合的bean对象(这里有疑问,不符合单例模式),然后在根据所需bean对象的key来获取唯一的bean对象(单例bean),如果根据bean对象的类型只找到一个bean,那么不需要进行后面的byName,直接把这个bean对象拿来用即可!
  • 分析:如果byType和byName都没有获取bean对象,那么说明要获取的bean对象在单例池中不存在,就需要立刻创建并放到单例池中供我们使用
  • 正好@Autowired 注解它获取bean对象的机制就是先byType后byName

Spring AOP底层怎么工作的?

  • UserService -->构造方法 -->普通对象 -->依赖注入 -->初始化前(@PostController) -->初始化(InitializingBean) -->初始化后(AOP) -->代理对象 -->放入单例池 -->Bean对象

  • 通过测试来获取userService对象,此时获取的bean对象是普通对象还是代理对象?是代理对象;那么获取的这个bean对象(代理对象)它里面的属性orderService里面是否有值?没有值,为空;

  • @Transactional,被该注解修饰的方法表示被Spring事务管理,事务管理器的作用就是创建数据库的链接,并且最重要的就是把自动提交autocommit改为false,这样的话就可以对执行抛异常的sql进行回滚,不回自动提交到数据库;

  • 为什么会有事务失效的问题呢?假如说有一个test方法和a方法,这两个方法都被Spring事务管理(通过@Transactional注解修饰),注意a方法在被事务管理时有一个特定的修饰,就是当准备执行这个a事务时,如果之前存在事务,那么此时在执行a事务就会抛异常,但是当test方法中在执行完自己的sql后,再调用a方法,为什么控制台没有抛异常?本来按照场景分析执行a时控制台应该抛出异常,分析思路:分析到底什么对象调用了a方法,因为代理对象调用a方法在这种情景下一定抛出一次,这里分析就知道一定是普通对象调用的a方法(为什么是普通对象,看图片,虽然test方法事务是有效的但是里面的a方法由于是普通对象执行的所以a方法的事务是无效的),所以a方法的注解就没有生效,因为只有代理对象调用该方法注解才会生效!

  • 怎么解决:1,将a方法抽取出来新建一个类注入spring容器通过这个类的bean对象(代理对象)来调用;2,不用将a方法抽取出来,可以这自己这个类中“自己注入自己”,获取自己的bean实例(代理对象)来调用a方法

@Configuration注解

  • 声明事务管理器之后,事务管理器会创建一个数据库连接并存放到ThreadLocal中(本质就是一个Map),我们拿Spring Boot封装的JdbcTemplate来说,使用JdbcTemplate来执行sql时,JdbcTemplate会先获取数据库的连接(在ThreadLocal中get),如果连接不存在JdbcTemplate会自己创建对应的数据库连接来执行sql,如果存在对应的数据库连接就直接拿来用,而@Configuration注解就是给加入该注解的类生成一个代理对象来执行上述的判断,保证单例!

Spring为什么要使用三级缓存来解决循环依赖问题

  • 场景假设:假设自定义了两个类(这里叫A类、B类)并且需要对这两个类进行bean实例的创建并注入到容器中,但是这两个类彼此之间互相依赖(每个类通过先通过有参构造来创建普通对象,但是有参构造函数的入参是对方的类对象或者说每个类彼此都有对方的类的属性),那么我们如果要获取A类的bean实例就需要先获取B类的bean实例,查看Spring容器是否存在B类的bean实例,如果没有存在那么要先创建B类的bean实例,而B类的bean实例创建需要先获取A实例,这样又需要先创建A类的bean实例…循环依赖
  • 怎么解决?先了解一下,Spring自带三级缓存,第一级缓存是单例池();第二级缓存是();第三级缓存是()
  • 分析:要解决这里的循环依赖问题,我们先引入一个Map1(用来存放类的代理对象)和Map2(用来存放类的普通对象及相关信息),在我们创建bean实例的时候会先进行判断是否存在循环依赖,如果存在循环依赖,那么A类会把它的普通对象放到Map2中(目的时为了在需要时生成指定的代理对象);等到B类创建bean对象时,同样它依赖A类,于是从单例池中来寻找A类的bean对象,单例池中一开始不存在,再去Map1中寻找A类的代理对象,如果Map1中没有A类的代理对象,那就在Map2中获取A类的普通对象来创建代理对象,并把生成的代理对象存放到Map1中,这样B类会拿到A类的代理对象,然后就可以去生成B类的bean实例,B类的bean实例生成之后也就打破了循环。
  • 总结:其实这里的Map1就是Spring的二级缓存,Map2就是Spring的三级缓存;本质上二级缓存其实就是来存放代理对象的,三级缓存才是用来解决循环以来的(调用三级缓存的普通对象来创建代理对象)

@ComponentScan注解的扫描机制是怎样的?

  • 该注解是扫描指定包下的bean对象,而被该注解修饰的类为配置bean,@ComponentScan注解扫描指定包下的带有@Component注解的类,判断该注解下类的bean对象是否是单例的,是否是懒加载并生成BeanDefinition(后续会根据BeanDefinition信息来生成对应的bean对象);注意这里的@ComponentScan注解可以是多个,也就是说一个配置bean中可以配置多个扫描包的路径(通过@ComponentScan注解的源码可以看出它可以在一个类中使用多次)。

@ComponentScan底层什么机制能够扫描到添加@Component注解的类?

  • 如果我们自己想里面实现的逻辑可能会想到反射,通过反射api来获取一个类的详细信息(包括注解,属性,方法等),但是Spring底层扫描机制却使用的是ASM工具,通过使用ASM工具来解析编译后的字节码文件(也就是.class文件)。

什么叫配置类?

  • 可能说是类中被@Configuration注解修饰的类是配置类。但是这样说不完全对。配置类的定义绝对不仅限于被@Configuration注解修饰,根据源码分析可得,如果一个类被@ComponentScan、@Component、@Import、@Import Resources(四个注解)任意来修饰一个类,那么这个类就是配置类。

对于添加@Component注解的类来说?

  • 如果该类不是独立类,存在两种情况:
    • 该类中含有普通内部类,但是这个普通内部类不能被加载为bean对象(会被过滤掉)?非独立类中内部类怎么实例化?
    • 如果类中含有静态内部类,这可以

一个添加@Component注解类一定能够生成bean实例吗?

  • Spring底层在给添加@Component注解的类生成bean实例之前需要判断该类是否为独立类,假如该类是独立类还需要判断该类(该类不能为接口类、不能是抽象类、或者可以是抽象类,但是抽象类中的方法要有@Lookup注解修饰,该注解的作用就是在调用该方法之后返回一个bean对象)。

@ComponentScan注解在扫描指定包下的带有@Component注解的类时,为符合条件的类生成BeanDefinition,但是两个类之间的BeanDefinition可能会重复?

  • BeanDefinition如果重复Spring底层会对其做两种方式的处理,如果两个类之间的beanName相同并且都属于同一个类(getSource),那么Spring底层自己返回false,并不会将重复的bean注入到Spring容器中;如果两个类之间仅仅是命名重复但不属于同一个类,那么Spring底层直接报冲突异常。

mapper层在被业务层调用时,需要一个xxMapper接口对应的对象来执行sql,那么这个对象是哪个框架封装生成的?

  • 在service层执行业务时需要调用mapper层的接口对象来执行sql,但是这个接口对象有两种,一个是接口实现类的对象,一个是接口的代理对象?答案是接口的代理对象 那么这个代理对象是Mybatis框架生成的还是Spring框架?答案是Mybatis框架,Mybatis框架通过jdk的动态代理来生成接口的代理对象。问题:mybatis生成的代理对象怎么变成一个bean对象?通过实现一个接口,重写里面的两个方法。(引出@Mapperscan注解)。

理解Spring框架定义bean对象的方式?

  • Spring框架定义事务的方式有两种:编程式(配置文件transamanger),声明式(注解),那么Spring框架定义bean的方式其实也分为两种:声明式(@Bean注解(配置类),配置文件<bean> 标签,@Component注解),编程式(通过BeanDefiniton)。

单例模式和单例bean?

  • 单例模式和单例bean还不太一样,单例bean就是Spring容器中一个类可以定义为多个bean对象,只是同一类型的bean的beanName要唯一,相当于map集合中的key不能重复。

自己理解

  • 控制反转(IOC):由Spring 容器”控制“进而达到“反转”的目的
  • IOC创建对象的方式:默认使用无参构造创建(java类没有声明构造函数默认存在一个无参构造,一旦生命一个有参构造函数那么默认生成的无参构造会销毁,要想使用无参构造创建实例化对象就需要在类中显示声明!);也可以使用有参构造函数创建bean实例
  • 依赖注入(DI):bean实例的创建依赖于Spring容器(IOC容器),而bean实例的属性也需要有容器注入
  • 总结:bean实例的创建以来构造函数,bean实例的属性注入以来Set方法
  • bean的作用域:默认单例模式(?单例bean)
  • 设计模式:单例模式?原型模式
  • 父子类动态代理
    • 要想生成代理对象先生成代理类
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉水不好喝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值