Spring 面试知识点

Spring 源码个人总结

1、spring的bean和beanDefinition。
2、我对Spring有着自己的一套理解的模型,这个模型里,简单来说主要包含了五个东西:

  1. BeanDefinitionMap
  2. singletonObjectMap
  3. ApplicationContext(AnnotationConfigApplicationContext、ClassPathXMLApplicationContext)
  4. BeanDefinition的后置处理器,代表:ConfigurationClassPostProcesser
  5. Bean的后置处理器,代表:AutowiredAnnotationBeanPostProcessor

这五个的关系图,就是之前那个知乎大神画的图:

AnnotationConfigApplicationContext:
	内置scanner和reader,以scanner为例:
		扫描指定包里面的所有类,转成Resource,... ,最后将所有的BeanDefinition存到BeanDefinitionMap中
	同时,会注册7个后置处理器,这里面就包含了我所提到的两个后置处理器。
	接着,就会调用refresh()方法。

refresh()方法中,会先对BeanDefinition使用BeanDefinition的后置处理器,例如ConfigurationClassPostProcesser。

ConfigurationClassPostProcesser:
	AnnotationConfigApplicationContext只能找到满足条件的@Component类,
	但是除此之外还有很多别的注解,例如@ComponentScan,@Import,@PropertySource等,
	这些注解的类的父级并不是@Component类,所以此时并不在BeanDefinitionMap里,
	因此本后置处理器会去将这部分类转成BeanDefinition存到BeanDefinitionMap里。

接着会根据BeanDefinition生成Bean,此时就会用到bean的后置处理器:

AutowiredAnnotationBeanPostProcessor:
	获取bean内部的@Autowired 或@Value注解的属性,根据属性的Class类型,从BeanDefinitionNames(List)中进行匹配,找出对应的beanDefinition,然后再生成bean,开启反射注入到这个类中。

0 学习spring原理对我的帮助(真实场景):

在实际开发中,经常会遇到引入了某个包之后,项目启动不起来了,或者spring容器报错导致项目起不来。因为我对spring容器的底层源码比较熟悉,所以同事都会来找我帮忙快速定位,我举个例子:

编写sdk时,借鉴了Springboot的自动配置。使用spring.factories注入Configuration。
但是在测试的时候,一直在报Configuration里没有引入Bean。在spring源码成了体系后,我可以立马去制定排查方案:

  1. 是Configuration没有注入还是Configuration里的Bean没有注入
  2. ConfigurationClassPostProcesser这个后置处理器会去解析Configuration类,将里面的Bean解析成BeanDefinition
    所以,只需要在refresh上debug一下,看看BeanFactoryPostProcesser处理后的BeanDefinition包不包含我注入的类就能确定是哪出了问题。
    为什么:Configuration不在BeanFactoryPostProcesser的BeanDefinition中?因为配置的ComponentScan里没有覆盖到对应的包名。

例子2:
启动的时候报类XXX找不到,在populateBean的代码里打断点,加上BeanName.equals(报错的类)作为判断条件,进入断点后,直接去容器的BeanDefinitionMap里搜一下对应的bean在不在,这个时候看看是不是有别的相同的BeanName,或者压根没有这个bean

1 Bean的生命周期(重要)

(注:1、2、3步就是createBean()的三部曲)
看这个,这个文章的图很清晰:请别再问Spring Bean的生命周期了!

在这里插入图片描述

几个关键点:

  • 第一大类:作用于多个bean的接口,触发时机如上图。
  • 第二大类中的 aware,分为了两组,各自的触发时机。
  • 第二大类中的 InitializingBean,DisposableBean。
  1. createBeanInstance():实例化bean
    需要注意,这里的实例化仅仅是生成了这么一个bean,还并没有进行初始化,
  2. populateBean():属性的装配
    将bean的一些属性注入到bean中,例如@AutoWired和@Value就需要用到AutowiredAnnotationBeanPostProcessor这个后置处理器来进行注入。
  3. initializeBean():这里实际上分为了三步:
    3.1. 处理Aware接口:调用各种Aware类的各种setXX()方法,设置各种spring的属性到bean中:https://www.jianshu.com/p/c5c61c31080b
    3.2 postProcessBeforeInitialization()
    3.3 如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
    3.4 如果beanDefinition中设置了init-method,则根据method名并利用反射调用自定义的初始化方法。
    3.5 postProcessAfterInitialization()
  4. 如果Bean实现了DisposableBean接口,执行destroy()方法。
  5. 如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。

2 beanFactory和factoryBean(重要)

BeanFactory:就是生产bean的工厂,用于生成任意bean。
FactoryBean:特殊bean,用于生成特定的bean。FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,这样便可以在getObject()方法中对要生成的bean进行灵活配置,比方说生成bean的前后都可以加入一些代码。
在spring配置文件中配置了名字 myFcBean 的一个类型,该类型是 FactoryBean 的实现类。那么通过
BeanFactory.getBean(“myFcBean”) 返回的并不是这个类型本身的对象,而是调用这个对象的getObject方法的返回值。

需要注意的是:getBean(“factoryBean”); 返回的不是这个工厂bean,而是它生产的具体bean。
使用:比方说在生成前加一段话。

public class ProductFactory implements FactoryBean<Object> {
	...
	    @Override
    public Object getObject() throws Exception {
        logger.debug("getObject......");
        return new Product();
    }

    @Override
    public Class<?> getObjectType() {
        return Product.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
	...
}

配置:

 <bean id="ProductFactory" class="com.xxx.ProductFactory"/> 

3 spring的循环依赖,三级缓存

这一篇讲的比较清晰:https://blog.csdn.net/u010013573/article/details/90573901

3.1 个人总结

循环依赖指的是:
A的属性有@Autowired了B,B的属性有@Autowired了A。
在创建A的时候,因为注入属性时会先去创建属性,所会去创建B,然后创建B的时候,同样会去创建A,导致无限循环。

解决这个问题的方法:找一块地方来暂存A或者B的半成品。

我们来看看spring是怎么解决的:
spring解决循环依赖的方法是“三级缓存”,也就是三个map。

/** Cache of singleton objects: bean name --> bean instance */
//singletonObjects,存放初始化后的单例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
//存放已经实例化,但未注入属性及未完成初始化的单例对象
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

关于第三级缓存为什么存放着的是beanFactory。会在后面"为什么是三级而不是二级缓存"中进行讲解。

创建bean有三部曲:createBeanInstance()、 populateBean()、initializeBean()

循环依赖发生在populateBean()上。但在createBeanInstance()之后,populateBean()之前会有一个addSingletonFactory()方法,这个方法会将bean的半成品存到第三级缓存中:
在这里插入图片描述

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

具体流程:

我们以A为例,A的半成品存入第三级缓存中,然后调用populateBean,这时遇到A的属性B时,会转去调用createBean(B)创建B。

同样的,B也会发现自己有属性A,然后获取A的时候在三级缓存中发现了半成品的A,这样就能继续完成B自己的创建工作。

B完成后,会将自己放到一级缓存中,然后回到了A,因为A能拿到对象B,所以便可以顺利的完成创建工作。

在这里插入图片描述

3.2 为什么在构造方法中设置属性无法解决循环依赖?

这是因为调用构造函数是在第一步createBeanInstance()中,这一步是在放入三级缓存前,所以无法解决。

3.3 为什么要用三级缓存,而不是用二级?

假设我是spring的设计者,最开始我和你们想的一样,所以设计了一个二级缓存用于存储bean的半成品,解决了前面提到的AB循环依赖问题。
但是测试的时候发现这么一个case:如果A或者B是需要进行AOP代理的,那么会导致A注入的是B本身,实际上预期的结果应该是注入B的代理对象

针对这个case,最初的解决方案是:

  • 不管三七二十一,所有 Bean 在实例化后就要完成 AOP 代理再存入二级缓存中。
    这种方法的好处是简单,但是它会直接破坏了 对Spring 设计的原则,Spring 在设计原则是在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。

所以,要做的应该只是将需要提前暴露出来(循环依赖)的bean生成代理对象,其他的还是放在最后再统一进行AOP代理。所以设计者采用了方法二:

  • 在二级缓存的基础上,再加一层缓存,用于存放beanFactory,之所以存beanFactory是因为beanFactory可以自定义生成bean的方法。所以,我可以只对需要提前暴露的beanFactory中提供代理方法,这样就能实现小部分的提前代理。

具体实现为:

protected Object doCreateBean( ... ){
 ...
 
 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 需要提前暴露(支持循环依赖),就注册一个ObjectFactory到三级缓存
 if (earlySingletonExposure) { 
  // 添加BeanFactory,并注入方法getEarlyBeanReference
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 }

 //注入属性
 populateBean(beanName, mbd, instanceWrapper);

getEarlyBeanReference方法会判断是否需要进行AOP代理:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
        	//如果有SmartInstantiationAwareBeanPostProcessor,则需要进行对bean进行AOP代理
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   // 对bean进行提前Spring AOP代理
   return wrapIfNecessary(bean, beanName, cacheKey);
}

详见:烂了大街的 Spring 循环依赖问题,你以为自己就真会了吗

4 springboot自动装配

详见:https://blog.csdn.net/bintoYu/article/details/105497852

因为在网上看到了比较好的介绍文章,因此就不重复劳动了,传送门:
springboot 源码 - 自动配置

也可以看看视频:https://www.bilibili.com/video/BV1Et411Y7tQ?p=18

总结:
顺序:@EnableAutoConfiguration -> spring.factories -> ConfigurationClassPostProcessor ->@EnableConfigurationProperties -> @Conditional

Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的对应的所有自动配置类,并解析存放到spring容器中,这些自动配置类会根据条件配置大量的组件,同时,因为在配置组件时会从Properties类中获取属性(@EnableConfigurationProperties),所以我们可以在配置文件中指定这些属性的值,另外,需要注意不是所有的自动配置类都会生效,需要满足特定条件才会生效(@Conditional)。

5 Spring AOP和AspectJ的区别:

  1. spring aop使用的是JDK和CGLIB动态代理,因此其用于代码运行时织入,而ApsectJ是在编译时和class文件加载时织入。
  2. Spring aop是基于代理,而AspectJ是基于字节码操作。
  3. 速度的话,AspectJ比Spring AOP要快得多,但Spring AOP简单,因此切面少的时候使用Spring aop,切面多的时候就要开始考虑使用AspectJ了。

JDK 动态代理 —— Spring AOP 的首选方式。只要目标对象实现甚至一个接口, 就会使用 JDK 动态代理;
CGLIB 代理 —— 如果目标对象没有实现接口, 则可以使用 CGLIB 代理。

6 Spring的单例模式和Java的单例的区别

java的单例指的是jvm中单例,而Spring的单例模式指的是spring容器中的单例。

7 spring的bean是线程安全的吗?

这个问题实际上是有一定的误导性,实际上,spring的bean是否是线程安全,与spring容器无关。spring仅仅是负责托管这些bean。

我们知道,spring的bean分为了单例和多例。多例下每次从容器里取出(getBean)的都是新的bean实例,不存在被共享的问题,所以是线程安全的。
spring的单例模式还分为了两种情况:

  • 无状态的bean:只对bean的成员变量进行查询操作。 线程安全
  • 有状态的bean:会对bean的成员变量进行更新操作。存在线程安全问题

此时该如何处理线程安全问题?

  1. 将单例改成多例。
  2. 将可变的成员变量存到ThreadLocal中。接下来面试官就可能会问ThreadLocal相关的问题。

8 Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

9 Spring 框架中都用到了哪些设计模式?

https://blog.csdn.net/a745233700/article/details/80959716

10 spring的多例模式

注:多例会从BeanDefinitionMap里取出BeanDefinition,然后每一次getBean()都会创建新的bean实例(每一次都不同),需要注意的是,这些实例不会存入singletonObjectMap中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值