Spring的一些常见面试题

Spring八股文

源码解析
反射
1、谈谈spring ioc的理解,原理和实现?
总:两层意思:控制反转和容器。
控制反转:他是一种思想理论,原来的对象是由我们使用者自己来进行控制的,而有了spring之后就把对象交给了spring来给我们进行管理;
容器:存放具体存储对象的。
分:
2、谈一下spring ioc的底层实现

1、xml 解析——XML有三种解析方式:DOM4J SAX STAX DOM
2、工厂模式——把对类的创建初始化全都交给一个工厂来执行,而用户不需要去关心创建的过程是什么样的
3、反射 ——反射可以在运行时根据指定的类名获得类的信息
可以参考一下这里关于IOC的底层实现

3、描述一下bean的生命周期?就是bean从创建到销毁的全过程
1.实例化
a.通过反射去推断构造函数进行实例化
b.实例工厂、静态工厂
2.属性赋值
a.解析自动装配(byname bytype constractor none @Autowired) DI的体现 b.循环依赖
3.初始化
a.调用XXXAware回调方法
b.调用初始化生命周期回调(三种)
c.如果bean实现aop 创建动态代理
4.销毁
a.在spring容器关闭的时候进行调用
b.调用销毁生命周期回调

4、spring 是如何解决循环依赖问题的?
5、spring中用到的设计模式
6、spring的AOP底层实现原理?
7、spring 的事务是如何回滚的?
8、谈一下spring 的事务传播?

spring 是如何解决循环依赖问题的?

可以使用 @Lazy 注解进行修饰
具体可以看 @Lazy注解的作用
什么时候用二级缓存什么时候用三级缓存解决
spring一级缓存二级缓存和三级缓存的区别

去看源码:DefaultSingletonBeanRegistry类中

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    //一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    //二级缓存
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    //三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
//三级缓存中map储存的value
@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

解决循环依赖问题
答:Spring通过三级缓存解决了循环依赖(一二三级缓存分别时什么),其中一级缓存为单例池(singletonObjects)(已经初始化的bean),二级缓存为早期曝光对象earlySingletonObjects(已经实例化但未初始化),三级缓存为早期曝光对象工厂(创建bean对象对应的bean工厂)(singletonFactories)。

一二级缓存使用的是ConcurrentHashMap(为了安全,bean默认是单例)
三级缓存用的是HashMap
三级缓存中,有一个方法用了synchronized修饰

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

❝第一步:先获取到三级缓存中的工厂;
第二步:调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
第三步:当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

面试官:为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
❝答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

Spring涉及的设计模式以及应用场景

【Spirng】8.spring框架中使用了哪些设计模式及应用场景_哔哩哔哩_bilibili

  1. 工厂模式,在各种BeanFactory以及ApplicationContext创建中都用到了
  2. 模版模式,在各种BeanFactory以及ApplicationContext实现中也都用到了
  3. 代理模式,Spring AOP利用了AspectJ AOP实现的! AspectJ AOP的底层用了动态代理
  4. 策略模式,加载资源文件的方式,使用了不同的方法,比如: ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理
  5. 单例模式,比如在创建bean的时候。
  6. 观察者模式,spring中的ApplicationEvent,ApplicationListener,ApplicationEventPublisher
  7. 适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter
  8. 装饰者模式,源码中类型带Wrapper或者Decorator的都是

SpringBean的生命周期

1、解析xml配置或注解配置的类,得到BeanDefinition;
2、通过BeanDefinition反射创建Bean对象;
3、对Bean对象进行属性填充;
4、回调实现了Aware接口的方法,如BeanNameAware;
5、调用BeanPostProcessor的初始化前方法;
6、调用init初始化方法;
7、调用BeanPostProcessor的初始化后方法,此处会进行AOP;
8、将创建的Bean对象放入一个Map中;
9、业务使用Bean对象;
10、Spring容器关闭时调用DisposableBean的destory()方法;

Spring的核心是什么?

Spring是一个开源框架,他是为了简化企业开发而生的,更加优雅和简洁.

Spring是整个Spring生态中的基石,其他的框架都是在spring的基础上进行开发的

Spring 两大核心:IOC和AOP

IOC:

容器的一个创建过程.需要进行了解一下,拓展点,反射进行实例化....

​ IOC:是一种编程思想,原本对象的创建以及管理,都是由我们程序员自己来进行的,然后有了Spring之后呢,bean就交由Spring来替我们控制和进行管理,我们只需要专注于业务的处理就可以了,

IOC 是一种思想,如果问你DI是什么,他才是Spring的一个具体实现方式

容器

AOP:

AOP的使用
面向切面编程 , 为 解耦 而生 , 底层是 动态代理 , 动态代理有 jdk动态代理(代理接口的) 还有 cglib动态代理(代理类的) 两种实现方式

当我们项目需要用到跟业务不相关的公共功能的时候,例如日志,事务配置(@Transactional),异常处理,可以用到AOP,把一些关键的配置,插入到我们的业务代码中

定义一个切面类:
(首先需要导入两个包 : spring-aop 和 spring-aspects)

  1. 加上@Component注解 @Aspect注解,他就是一个切面类
  2. 定义一个@Pointcut , 再来一个 @After @Before 就可以执行了
  3. 他在spring底层会转换成对应个数的advisor

Spring的事务隔离级别

Mysql隔离级别|特性|传播行为
基本跟Mysql的事务隔离级别差不多,5种 ,多了一个默认ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应。
Spring的5种事务隔离级别
例如A类用了事务,A类里面用到了B类,B类里面也有事务
若此时事务级别是required 因为A类有事务,所以用A的事务

Spring是如何简化开发的

  • 基于POJO的轻量级和最小侵入性编程
  • 通过依赖注入和面向接口实现松耦合
    • 他有很多自带的可拓展的接口,你可以进行实现重写其中的方法即可使用
  • 基于切面和惯例进行声明式编程,只需要在方法上加上@Transactional注解即可
  • 通过切面和模板减少样板式代码,
    • 例如:你要在一些方法中切入一些日志,你只需要写好一个切面,告诉他在哪些方法中切入即可

Spring bean中的id和name是否可以重复

同名bean:多个bean 有相同的 name 或者 id,称之为同名bean

bean 的id 和 name的区别

id和name都是spring 容器中bean 的唯一标识符。 name和id都不是必须的,可以没有

  1. id: 一个bean的唯一标识 , 命名格式必须符合XML ID属性的命名规范
  2. name: 可以用特殊字符,并且一个bean可以用多个名称:name=“bean1,bean2,bean3”
    ,用逗号或者分号或者空格隔开。如果没有id,则name的第一个名称默认是id

spring 容器如何处理同名bean?

  • 同一个spring配置文件中,bean的 id、name是不能够重复的,否则spring容器启动时会报错。
    如果一个spring容器从多个配置文件中加载配置信息,则多个配置文件中是允许有同名bean的,并且后面加载的配置文件的中的bean定义会覆盖前面加载的同名bean。
    1、在spring同一个配置文件中,不能存在id相同的两个bean,否则会报错。
    2、在两个不同的spring配置文件中,可以存在id相同的两个bean,启动时,不会报错。这是因为spring
    ioc容器在加载bean的过程中,类DefaultListableBeanFactory会对id相同的bean进行处理:后加载的配置文件的bean,覆盖先加载的配置文件的bean。DefaultListableBeanFactory类中,有个属性allowBeanDefinitionOverriding,默认值为true,该值就是用来指定出现两个bean的id相同的情况下,如何进行处理。如果该值为false,则不会进行覆盖,而是抛出异常。

spring 容器如何处理没有指定id、name属性的bean?

如果 一个 bean 标签未指定 id、name 属性,则 spring容器会给其一个默认的id,值为其类全名。
如果有多个bean标签未指定 id、name 属性,则spring容器会按照其出现的次序,分别给其指定 id 值为 “类全名#1”, “类全名#2”

如下:
配置文件:

<bean class="com.xxx.UserInfo">
 <property name="accountName" value="no-id-no-name0"></property>
</bean>

<bean class="com.xxx.UserInfo">
 <property name="accountName" value="no-id-no-name1"></property>
</bean>

<bean class="com.xxx.UserInfo">
 <property name="accountName" value="no-id-no-name2"></property>
</bean>

获取bean的方式:

UserInfo u4 = (UserInfo)ctx.getBean("com.xxx.UserInfo");
UserInfo u5 = (UserInfo)ctx.getBean("com.xxx.UserInfo#1");
UserInfo u6 = (UserInfo)ctx.getBean("com.xxx.UserInfo#2");

SpringBoot的自动装配原理

这个可以衍生到 SpringBean的一个生命周期

主要是启动类上的@SpringBootApplication注解
里面主要是三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

大致流程

  1. 启动类
  2. SpringApplication的构造方法
  3. SpringApplication.run方法
  4. 启动过程中主要有两个方法:prepareContext()refreshContext()
  5. prepareContext方法中的 load()方法 ,将当前启动类作为一个BeanDefinition注册到BeanDefinitionMap中
  6. refreshContext()方法中的 refresh()方法,对整个容器进行refresh操作,执行BeanFactory的后置处理器(就是一些PostProcessors) 。。。
  7. @Configuration是Spring的一个注解,其修饰的类会加入Spring容器。这就说明SpringBoot的启动类会加入Spring容器。
  8. @ComponentScan注解: 从声明这个注解的类所在的包开始,扫描包及子包,把符合扫描规则的类装配到spring容器中。而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中
  9. @EnableAutoConfiguration注解,(看下面对@Import注解的一个讲解)调用了Spring容器中SpringFactoriesLoader类的loadSpringFactories方法,他是基于SPI机制,扫描当前ClassPath下所有的配置类,调用所有导入的依赖包下的 MATE-INF包下面的Spring.factories文件中key为EnableAutoConfiguration的所有value。
    里面有@Import注解,解析@Import注解的时候比较特别,会有一个collectImports()方法,会把所有包含@Import的注解都解析到BeanDefinitionMap中。
  10. 至此,springboot的自动装配就完成了

参考 Spring自动装配概述
也可以看看这篇文章:关于@SpringBootApplication注解的一些讲解

@Impot注解中的AutoConfigurationImportSelector

调用AutoConfigurationImportSelector类(相当于一个处理器)中的process()方法进而触发getCandidateConfigurations()方法获取Spring.factories文件下的key为EnableAutoConfiguration的所有value,所以这就是为什么很多人的文章中都说Springboot的自动装配就是调用@EnableAutoConfiguration注解下的@Import中的AutoConfigurationImportSelector类,主要就是通过这种不断解析注解的方法去调用的

@EnableAutoConfiguration下的@AutoConfigurationPackage注解

basePackages是个set集合,但是容器当中始终只有一个BasePackagesBeanDefinition对象,也就是只要代码当中添加@AutoConfigurationPackage注解,就会将注解所在的包名添加到basePackages集合当中。

然后@Import注解是如何获取到类的?

可以看出来AnnotationMetadata其实就是@Import所在的类当中的元数据。这个元数据就是@Import所在的类信息,例如这个类的全类名以及所用到了哪些注解等等…
所以他是通过传入 Metadata 参数来拿到当前传入的类的
关于@Import注解的一些讲解

posted @ 2022-11-21 21:32  没有烦恼的猫猫  阅读(86)  评论(0编辑  收藏  举报