Spring面试

目录

1.谈谈Spring IOC的理解,原理与实现?

IOC又名控制反转:原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮助我们管理,避免资源浪费。

总体问题:

  • 依赖注入
  • IOC容器存储对象(三级缓存)
  • bean的生命周期

DI:依赖注入,把对应属性的值注入到具体的对象中。

注解:

 @Autowired@Resurece(jdk1.8之后,被取消了)

xml:

<bean id="userDao" class="UserDao"></bean>
<bean id="userService" class="UserServiceImpl"/>

<!--    setter -->
<bean id="person1" class="framework.spring.Person" scope="singleton" p:name="张三" p:address="广州"
          p:phone="1319412212" />

  <!-- constructor-->
<bean id="person2" class="framework.spring.Person" init-method="myInit"
          destroy-method="myDestory" scope="singleton"  >
        <constructor-arg name="name" value="zhangsan"></constructor-arg>
        <constructor-arg name="address" value="beijing"></constructor-arg>
        <constructor-arg name="phone" value="1319124212"></constructor-arg>
 </bean>

   <!-- static factory-->
<bean id="person3" class="framework.spring.PersonFactory"  scope="singleton"  factory-method="staticPersonFactory">
</bean>

<!-- factory-->
    <bean id="personFactory" class="framework.spring.PersonFactory"></bean>
    <bean id="person4" class="framework.spring.PersonFactory"  scope="singleton"  factory-bean="personFactory" factory-method="personFactory">
 </bean>

由注解或xml的方式注入,在用populateBean方法完成属性值 的 属性填充

引发的问题?

IOC容器创建过程

在这里插入图片描述

创建描述

1、首先通过反射的方式进行Bean的实例化。

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,
或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。 
对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。
容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,
并未进行依赖注入。 
实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,
从而避免了使用反射机制设置属性。
填充Bean的属性。(populateBean()方法)
调用aware接口相关方法:invokeAwareMethod
(完成BeanName,BeanFactory,BeanClassLoader对象的属性设置,
检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean)

2、加载解析bean对象,准备要创建的bean对象的定义对象beanDefinition(xml或者注解的解析过程)

BeanDefinitionReader:读取 Spring 配置文件中的内容,
将其转换为 IOC 容器内部的数据结构==>BeanDefinition。它是一个接口,
通过不同的实现子类去读取不同文件中的信息,可以是xml、properties。
不管外部定义的bean是什么样子的,
最后都需要经过BeanDefinitionReader这个接口去规范为一种统一的格式

3、BeanFactoryPostProcessor的前置处理,此处是扩展点,

PlaceHolderConfigurSupport,ConfigurationClassPostProcessor,ApplicationContext

BeanFactoryPostProcessor :
允许自定义修改应用程序上下文的BeanDefinition,调整上下文的基础BeanFactoryBean属性值,
实际上是对BeanFactory扩展增强。
BeanFactoryPostProcessor可以与BeanDefinition进行交互并对其进行修改,
但不能与bean实例进行交互
   
PlaceholderConfigurerSupport:
是一个抽象基类,
抽象了bean定义属性值中的占位符解析的功能它继承自PropertyResourceConfigurer。
    
基类已经定义了要在bean容器后置处理阶段对容器中所有bean定义属性进行处理,
而PlaceholderConfigurerSupport则进一步约定了要进行的属性值处理是:
解析属性值中的占位符,同时提供了进行处理所需的一些属性(占位符前后缀等),以及一些工具方法

实现方式
1.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="jdbc:${dbname}"/>
</bean>
2.注解    
@Value("${person.age}")
3.配置文件
driver=com.mysql.jdbc.Driver
dbname=mysql:mydb
4.配置文件
subPath=${rootPath}/subdir

    
ConfigurationClassPostProcessor :
完成了 配置类的解析和保存以及@Component 注解、@Import 等注解的解析工作。
将所有需要注入的bean解析成 BeanDefinition保存到 BeanFactory 中。

5、调用init-method方法:invokeInitMethods(),判断是否实现了InitializingBean接口,如果有,调用afterPropertiesSet方法,没有就不调用。

BeanPostProcessor相关⼦类的before⽅法执⾏完,则执⾏init相关的⽅法:
@PostConstruct、实现了InitializingBean接⼝、定义的init-method⽅法

6、调用BeanPostProcessor的后置处理方法,spring的AOP就是在此处实现的

(AnnotationAwareAspectJAutoProxyCreator 继承 AbstractAutoProxyCreator)。

spring的aop在此实现,AbstractAutoProxyCreator注册Destruction相关的回调接口;钩子函数

7、获取到完整的对象,可以通过getBean的方式来进行对象的获取。

8、销毁Bean,判断是否实现了dispoableBean接口,调用destroyMethod方法。

拓展

BeanFactory与ApplicationContext区别

BeanFactory在启动时,不去实例化Bean,只有在容器中获取的时候才会实例化。也就是懒加载,所以当bean没有完全注入时,可能第一次获取就会抛出异常。

ApplicationContext是在启动的时候就实例化所有单例的Bean,它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

BeanFactory启动占用资源少,但是对资源要求高。

ApplicationContext:在启动时就加载,运行速度快,可以尽早发现系统中的配置问题。

在这里插入图片描述

DefaultListableBeanFactory是整个bean加载的核心,是spring注册及加载bean的默认实现

2.谈一下spring IOC的底层实现

1.先初始化BeanFactory对象,执行 refreshBeanFactory () 判断是否已有beanFactory 。

有,关闭先前的 bean 工厂

通过createBeanFactory创建出一个Bean工厂(DefaultListableBeanFactory)

@Override
protected final void refreshBeanFactory() throws BeansException {
   //判断是否已有beanFactory
   if (hasBeanFactory()) {
      //销毁beans
      destroyBeans();
      //关闭beanFactory
      closeBeanFactory();
   }
   try {
      //实例化DefaultListableBeanFactory
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      //设置序列化ID
      beanFactory.setSerializationId(getId());
      //自定义bean工厂的一些属性(是否覆盖,是否循环依赖)
      customizeBeanFactory(beanFactory);
      //加载应用中的BeanDefinitions
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         //赋值当前bean工厂
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}

2、开始循环创建对象,因为容器中的bean默认都是单例的,所以优先通过getBean,doGetBean从容器中查找,找不到的话。

3.通过doCreateBean(继承abstract createBean)方法,以反射的方式创建对象,一般情况下使用的是无参的构造方法(getDeclaredConstructor,newInstance)

4.进行对象的属性填充populateBean

5、进行其他的初始化操作(initializingBean)

3. Spring是如何解决循环依赖的问题的?

三级缓存,提前暴露对象。我们的对象是单例的,有可能A对象依赖的B对象是有AOP的(B对象需要代理)

总:什么是循环依赖问题,A依赖B,B依赖A

出现在对象属性的注⼊在对象实例化之后(填充属性)

1.先创建A对象,实例化A对象,此时A对象中的b属性为空,填充属性b

2.从容器中查找B对象,如果找到了,直接赋值不存在循环依赖问题(存在循环依赖,肯定找不到),

找不到直接创建B对象

3.实例化B对象,此时B对象中的a属性为空,填充属性a

4.从容器中查找A对象,找不到,直接创建

形成闭环

此时,如果仔细琢磨的话,会发现A对象是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键

当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几个状态,完成实例化=但未完成初始化,完整状态,因为都在容器中,所以要使用不同的map结构来进行存储(key是BeanName,Value是ObjectFactory),此时就有了一级缓存和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1,2,3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象

spring内部有三级缓存:

singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

场景

在这里插入图片描述

生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用@Lazy注解,延迟加载
  2. 使用@DependsOn注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

为什么需要三级缓存?

三级缓存的value类型是ObjectFactory,是一个函数式接口,存在的意义是保证在整个容器的运行过程中同名的bean对象只能有一个。

如果一个对象需要被代理,或者说需要生成代理对象,那么要不要优先生成一个普通对象?要

普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际的调用过程中,是没有办法确定什么时候对象被使用,所以就要求当某个对象被调用的时候,优先判断此对象是否需要被代理,类似于一种回调机制的实现,因此传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程,getEarlyBeanReference()

因此,所有的bean对象在创建的时候都要优先放到三级缓存中,在后续的使用过程中,如果需要被代理则返回代理对象,如果不需要被代理,则直接返回普通对象

拓展

第三级缓存中为什么要添加 ObjectFactory 对象,直接保存实例对象不行吗?

答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的

针对这种场景spring是怎么做的呢?

答案就在 AbstractAutowireCapableBeanFactory doCreateBean 方法的这段代码中:

它定义了一个匿名内部类,通过 getEarlyBeanReference()方法获取代理对象,其实底层是通过AbstractAutoProxyCreator 类的 getEarlyBeanReference 生成代理对象。

//即使被 BeanFactoryAware 等生命周期接口触发,也急切地缓存单例以解析循环引用。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
	logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");
			}
addSingletonFactory(beanName,()-> getEarlyBeanReference(beanName,mbd,bean));
        }

了解更多:

https://blog.csdn.net/java13992394428/article/details/124455127

4.缓存的放置时间和删除时间

三级缓存:在doCreateBean方法,使用createBeanInstance实例化之后,addSingletonFactory放入缓存

二级缓存:第一次从三级缓存确定对象是代理对象还是普通对象的时候,同时删除三级缓存 getSingleton

一级缓存:生成完整对象之后放到一级缓存,删除二三级缓存:addSingleton

5.Bean Factory与FactoryBean有什么区别?

相同点:都是用来创建bean对象的

不同点:使用BeanFactory创建对象的时候,必须要遵循严格的生命周期流程,太复杂了,,如果想要简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactroyBean接口了

  1. isSingleton:是否是单例对象
  2. getObjectType:获取返回对象的类型
  3. getObject:自定义创建对象的过程(new,反射,动态代理)

6. Spring中用到的设计模式?

1、简单工厂(BeanFactory)

实现方式:BeanFactory。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

2、单例模式

Spring依赖注入Bean实例默认是单例的

3、适配器模式

实现方式:SpringMVC中的适配器HandlerAdatper

4、装饰器模式

实现方式:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。

5、代理模式

实现方式:AOP底层,就是动态代理模式的实现

6、观察者模式

实现方式:Spring的事件驱动模型使用的是观察者模式 ,Spring中Observer模式常用的地方是listener的实现。

7、策略模式

实现方式:Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring框架本身大量使用了Resource接口来访问底层资源。

8、模版方法模式

Spring模板方法模式实质:是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。

9、工厂方法

实现方式:FactoryBean接口

7.Spring的AOP的底层实现原理?

动态代理

aop是ioc的一个扩展功能,先有的ioc,再有的aop,只是在ioc的整个流程中新增的一个扩展点而已:BeanPostProcessor

总:aop概念,应用场景,动态代理

分:

​ bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法中来进行实现

​ 1、代理对象的创建过程(advice,切面,切点)

​ 2、通过jdk或者cglib的方式来生成代理对象

​ 3、在执行方法调用的时候,会调用到生成的字节码文件中,直接回找到DynamicAdvisoredInterceptor类中的intercept方法,从此方法开始执行

​ 4、根据之前定义好的通知来生成拦截器链

​ 5、从拦截器链中依次获取每一个通知开始进行执行,在执行过程中,为了方便找到下一个通知是哪个,会有一个CglibMethodInvocation的对象,找的时候是从-1的位置一次开始查找并且执行的。

8. Spring的事务是如何回滚的?

spring的事务管理是如何实现的?

总:spring的事务是由aop来实现,首先要生成具体的代理对象,然后按照aop的流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个TransactionInterceptor来实现的,然后调用invoke来实现具体的逻辑。

Service加了@Transactional注解的类创建生成代理对象

Controller在调用Service方法时,由于Service是代理对象,便会进入到DynamicAdvisedInterceptor类的intercept方法中

1.解析各个方法上事务相关的属性,根据具体的属性来判断开始新事务

2.当需要开启的时候,获取数据库连接,关闭自动提交功能,开启事务

3.执行具体的sql逻辑操作

4.在操作过程中,如果执行失败了,那么会通过completeTransactionAfterThorwing来完成事务的回滚操作,回滚的具体逻辑是通过doRollBack方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来回滚

5.如果执行过程中,没有任何意外情况的发生,那么通过commitTransactionAfterReturning来完成事务的提交操作,提交的具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象提交

	if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}

			if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				// Set rollback-only in case of Vavr failure matching our rollback rules...
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}

			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

6.当事务执行完毕之后需要清除相关的事务信息cleanupTransactionInfo

9. Spring事务传播行为

  • REQUIRED:默认的事务传播行为;需要事务:存在事务则使用已存在事务,否则创建新的事务;
  • SUPPORTS:支持已存在事务:存在事务则使用已存在事务,否则以非事务方式运行;
  • MANDATORY:强制使用事务:存在事务则使用已存在事务,否则抛出异常;
  • REQUIRES_NEW:需要新事务:存在事务则挂起已存在事务,否则创建新事务;
  • NOT_SUPPORTED:不支持事务:存在事务则挂起已存在事务,否则以非事务方式运行;
  • NEVER:从不使用事务:存在事务则抛出异常,否则以非事务方式运行;
  • NESTED:嵌套事务:存在事务则使创建保存点使用嵌套的事务,否则创建新的事务。

Spring事务的传播行为在Propagation枚举类中定义了如下几种选择

支持当前事务

  • REQUIRED :如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务
  • SUPPORTS:如果当前存在事务,则加入该事务 。如果当前没有事务, 则以非事务的方式继续运行
  • MANDATORY :如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常

不支持当前事务

  • REQUIRES_NEW :创建一个新事务,如果当前存在事务,则把当前事务挂起
  • NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • NEVER :以非事务方式运行,如果当前存在事务,则抛出异常

其他情况

  • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行 。如果当前没有事务,则该取值等价于REQUIRED

以NESTED启动的事务内嵌于外部事务中 (如果存在外部事务的话),此时内嵌事务并不是一个独立的事务,它依赖于外部事务。只有通过外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交

spring中不同方法的嵌套调用过程中,事务应该如何进行处理?

是用同一个事务还是不同的事务,当出现异常的时候会回滚还是提交,两个方法之间的相关影响,在日常工作中,使用比较多的是required,Requires_new,nested

​ 分:1、先说事务的不同分类,可以分为三类:支持当前事务,不支持当前事务,嵌套事务

​ 2、如果外层方法是required,内层方法是,required,requires_new,nested

​ 3、如果外层方法是requires_new,内层方法是,required,requires_new,nested

​ 4、如果外层方法是nested,内层方法是,required,requires_new,nested

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值