Spring面试合集

1. Spring的IOC初始化流程

Spring IOC的核心是BeanFactory

其实SpringIOC初始化的过程就是准备好BeanFactory的过程。

(1)定位并获取资源文件
因为对象和对象之间的关系存储在xml或properties等语义化配置文件中,首先要定位到配置文件。用资源加载器ResourceLoader将资源文件路径转换为对应的Resource
(2)解析资源文件
(3)注册
利用解析好的BeanDefinition对象完成最终的注册。将beanName和BeanDefinition作为键值放到了beanFactory的map中

2. Spring Bean的生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

ApplicationContext Bean生命周期:
在这里插入图片描述

  1. 首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化,
  2. 按照Bean定义信息配置信息,注入所有的属性,
  3. 如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,此时该Bean就获得了自己在配置文件中的id,
  4. 如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的BeanFactory,这样该Bean就获得了自己所在的BeanFactory,
  5. 如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext,
  6. 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方法,
  7. 如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法,
  8. 如果Bean配置了init-method方法,则会执行init-method配置的方法,
  9. 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方法,
  10. 经过流程9之后,就可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,其生命周期就交给调用方管理了,不再是Spring容器进行管理了
  11. 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,
  12. 如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的生命周期结束

BeanFactory Bean生命周期
在这里插入图片描述
BeanFactoty容器中, Bean的生命周期如上图所示,与ApplicationContext相比,有如下几点不同:

  1. BeanFactory容器中,不会调用ApplicationContextAware接口的setApplicationContext()方法
  2. BeanPostProcessor接口的postProcessBeforeInitialzation()方法和postProcessAfterInitialization()方法不会自动调用,必须自己通过代码手动注册
  3. BeanFactory容器启动的时候,不会去实例化所有Bean,包括所有scope为singleton且非懒加载的Bean也是一样,而是在调用的时候去实例化。

3. Spring中的循环依赖处理

spring对循环依赖的处理有三种情况:
①构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
②单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
③非单例循环依赖:无法处理。

非单例对象的加载基本分三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法。

构造器循环依赖
将当前正要创建的bean 记录在缓存中
Spring 容器将每一个正在创建的bean 标识符放在一个“当前创建bean 池”中, bean 标识:在创建过程中将一直保持在这个池中,因此如果在创建bean 过程中发现自己已经在“当前创建bean 池” 里时,将抛出BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建
完毕的bean 将从“ 当前创建bean 池”中清除掉。

setter循环依赖
Spring引入了三级缓存解决该问题:
这三级缓存的作用分别是:

  • singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
  • earlySingletonObjects :完成实例化但是尚未初始化的,提前曝光的单例对象的Cache (二级缓存)
  • singletonObjects:完成初始化的单例对象的cache(一级缓存)

在创建bean的时候,会首先从cache中获取这个bean,这个缓存就是sigletonObjects。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

非单例循环依赖
对于“prototype”作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓 存“prototype”作用域的bean ,因此无法提前暴露一个创建中的bean 。需要调用方自行处理。

4. Spring MVC的工作原理(流程)

在这里插入图片描述
用户发送请求由前置控制器DispatcherServlet来决定哪一个页面的控制器进行处理并把请求委托给它,在由HandlerMapping将请求映射为HandlerExecutionChain对象(包含Handler处理器对象(页面控制器),多个HandlerInterceptor对象即拦截器),在返回给DispatcherServlet,DispatcherServlet在次发送请求给HandlerAdapter,HandlerAdapter将处理器包装为适配器,调用处理器相应功能处理方法,Handler返回ModelAnView给HandlerAdapter,HandlerAdapter发送给DispatcherServlet进行视图的解析(ViewResolver),ViewResolver将逻辑视图解析为具体的视图,返回给DispatcherServlet,在进行视图的渲染(View),返回给DispatcherServlet,最后通过DispatcherServlet将视图返回给用户。

5. Spring AOP的理解

AOP就是我们常说的面向切面编程,spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。

术语
  1. 通知(Advice)
    就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。

  2. 连接点(JoinPoint)
    这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

  3. 切入点(Pointcut)
    上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

  4. 切面(Aspect)
    切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

  5. 引入(introduction)
    允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

  6. 目标(target)
    引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

  7. 代理(proxy)
    怎么实现整套aop机制的,都是通过代理。

  8. 织入(weaving)
     把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。
     
    关键就是:切点定义了哪些连接点会得到通知

6. Spring 如何保证 Controller 并发的安全

并发的安全?
原因就在于Controller对象是单例的,那么如果不小心在类中定义了类变量,那么这个类变量是被所有请求共享的,这可能会造成多个请求修改该变量的值,出现与预期结果不符合的异常

那有没有办法让Controller不以单例而以每次请求都重新创建的形式存在呢?
答案是当然可以,只需要在类上添加注解@Scope(“prototype”)即可,这样每次请求调用的类都是重新生成的(每次生成会影响效率)

虽然这样可以解决问题,但增加了时间成本,总让人不爽,还有其他方法么?答案是肯定的!

使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值