Spring常问的------真实⼤⼚⾯试题汇总(含答案)
⾯试题1. Spring中bean的循环依赖怎么解决?
(⼀). ⾸先说⼀下什么是Spring的循环依赖:
其实就是在进⾏getBean的时候,A对象中去依赖B对象,⽽B对象⼜依赖C对象,但是对象C⼜去依赖A对象,结果就造成A、B、C三个对象都不能完成实例化,出现了循环依赖。就会出现死循环,最终导致内存溢出的错误。
(⼆).如何去解决Spring的循环依赖呢?
1.先知道什么是Spring的“三级缓存”:就是下⾯的三个⼤的Map对象,因为Spring中的循环依赖的理论基础其实是基于java中的值传递的,然后其实Spring中的单例对象的创建是分为三个步骤的:
createBeanInstance,其实第⼀步就是通过构造⽅法去进⾏实例化对象。但是这⼀步只是实例对象⽽已,并没有把对象的属性也给注⼊进去
然后这⼀步就是进⾏注⼊实例对象的属性,也就是从这步对spring xml中指定的property进⾏populate
最后⼀步其实是初始化XML中的init⽅法,来进⾏最终完成实例对象的创建。但是AfterPropertiesSet⽅法会发⽣循环依赖的步骤集中在第⼀步和第⼆步。
singletonObjects指单例对象的cache (⼀级缓存)
private final Map<String, Object> singletonObjects =new ConcurrentHashMap<String, Object>(256);
singletonFactories指单例对象⼯⼚的cache(三级缓存)
private final Map<String, ObjectFactory<?>> singletonFactories =new HashMap<String, ObjectFactory<?>>(16);
earlySingletonObjects指提前曝光的单例对象的cache(⼆级缓存)
private final Map<String, Object> earlySingletonObjects =new HashMap<String, Object>(16);
2. 然后是怎么具体使⽤到这个三级缓存的呢,或者说三级缓存的思路?
⾸先第⼀步是在Spring中会先去调⽤getSingleton(String beanName, boolean allowEarlyReference)来获取想要的单例对象。
然后第⼀步会先进⾏通过singletonObjects这个⼀级缓存的集合中去获取对象,如果没有获取成功的话并且使⽤
isSingletonCurrentlyInCreation(beanName)去判断对应的单例对象是否正在创建中(也就是说当单例对象没有被初始化完全,⾛到初始化的第⼀步或者第⼆的时候),如果是正在创建中的话,会继续⾛到下⼀步
然后会去从earlySingletonObjects中继续获取这个对象,如果⼜没有获取到这个单例对象的话,并且通过参数传进来的
allowEarlyReference标志,看是不是允许singletonFactories(三级缓存集合)去拿到该实例对象,如果allowEarlyReference为Ture的话,那么继续下⼀步
此时上⼀步中并没有从earlySingletonObjects⼆级缓存集合中拿到想要的实例对象,最后只能从三级缓存singletonFactories (单例⼯⼚集合中)去获取实例对象,
然后把获取的对象通过Put(beanName, singletonObject)放到earlySingletonObjects(⼆级缓存中),然后在再从
singletonFactories(三级缓存)对象中的集合中把该对象给remove(beanName)出去。
附上核⼼代码
protected Object getSingleton(String beanName,boolean allowEarlyReference){
从⼀级缓存获取
Object singletonObject =(beanName);
if(singletonObject ==null&&isSingletonCurrentlyInCreation(beanName)){
synchronized(this.singletonObjects){
从⼆级缓存获取
singletonObject =(beanName);
if(singletonObject ==null&& allowEarlyReference){
从三级缓存获取
ObjectFactory<?> singletonFactory =(beanName);
if(singletonFactory !=null){
singletonObject = Object();
this.earlySingletonObjects.put(beanName, singletonObject);
ve(beanName);
}
}
}
}
return(singletonObject != NULL_OBJECT ? singletonObject :null);}
3. 总结⼀下为什么这么做就能解决Spring中的循环依赖问题。
其实在没有真正创建出来⼀个实例对象的时候,这个对象已经被⽣产出来了,虽然还不完美(还没有
进⾏初始化的第⼆步和第三步),但是已经能被⼈认出来了(根据对象引⽤能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让⼤家认识,让⼤家使⽤A⾸先完成了初始化的第⼀步,并且将⾃⼰提前曝光到singletonFactories中,此时进⾏初始化的第⼆步,发现⾃⼰依赖对象B,此时就尝试去get(B),发现B还没有被create,所以⾛create流程,B在初始化第⼀步的时候发现⾃⼰依赖了对象A,于是尝试get(A),尝试⼀级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试⼆级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将⾃⼰提前曝光了,所以B能够通过Object拿到A对象(虽然A还没有初始化完全,但是总⽐没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将⾃⼰放⼊到⼀级缓存
singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成⾃⼰的初始化阶段2、3,最终A也完成了初始化,长⼤成⼈,进去了⼀级缓存singletonObjects中,⽽且更加幸运的是,由于B拿到了A的对象引⽤,所以B现在hold住的A对象也蜕变完美了!⼀切都是这么神奇!!
总结:Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引⽤原理,⽐较完美地解决了某些情况下的循环依赖问题!
⾯试题2. Spring中bean的加载过程?
⾸先从⼤的⼏个核⼼步骤来去说明,因为Spring中的具体加载过程和⽤到的类实在是太多了。
(1)、⾸先是先从AbstractBeanFactory中去调⽤doGetBean(name, requiredType, final Object[] args, boolean
typeCheckOnly【这个是判断进⾏创建bean还是仅仅⽤来做类型检查】)⽅法,然后第⼀步要做的就是先去对传⼊的参数name进⾏做转换,因为有可能传进来的name=“&XXX”之类,需要去除&符号
(2)、然后接着是去调⽤getSingleton()⽅法,其实在上⼀个⾯试题中已经提到了这个⽅法,这个⽅法就是利⽤“三级缓存” 来去避免循环依赖问题的出现的。【这⾥补充⼀下,只有在是单例的情况下才会去解决循环依赖问题】
(3)、对从缓存中拿到的bean其实是最原始的bean,还未长⼤,所以这⾥还需要调⽤getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)⽅法去进⾏实例化。
(4)、然后会解决单例情况下尝试去解决循环依赖,如果isPrototypeCurrentlyInCreation(beanName)返回为true的话,会继续下⼀步,否则throw new BeanCurrentlyInCreationException(beanName);
(5)、因为第三步中缓存中如果没有数据的话,就直接去parentBeanFactory中去获取bean,然后判断
containsBeanDefinition(beanName)中去检查已加载的XML⽂件中是否包含有这样的bean存在,不存在的话递归去getBean()获取,如果没有继续下⼀步
(6)、这⼀步是吧存储在XML配置⽂件中的GernericBeanDifinition转换为RootBeanDifinition对象。这⾥主要进⾏⼀个转换,如果⽗类的bean不为空的话,会⼀并合并⽗类的属性
(7)、这⼀步核⼼就是需要跟这个Bean有关的所有依赖的bean都要被加载进来,通过刚刚的那个RootBeanDifinition对象去拿到所有的beanName,然后通过registerDependentBean(dependsOnBean, beanName)注册bean的依赖
(8)、然后这⼀步就是会根据我们在定义bean的作⽤域的时候定义的作⽤域是什么,然后进⾏判断在进⾏不同的策略进⾏创建(⽐如isSingleton、isPrototype)
(9)、这个是最后⼀步的类型装换,会去检查根据需要的类型是否符合bean的实际类型去做⼀个类型转换。Spring中提供了许多的类型转换器
⾯试题3. Spring中bean的⽣命周期?
1. ⾸先会先进⾏实例化bean对象
2. 然后是进⾏对bean的⼀个属性进⾏设置
3. 接着是对BeanNameAware(其实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调⽤
容器的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调⽤Spring容器的服务)
4. 然后是实现BeanPostProcessor 这个接⼝中的两个⽅法,主要是对调⽤接⼝的前置初始化postProcessBeforeInitialization
5. 这⾥是主要是对xml中⾃⼰定义的初始化⽅法 init-method = “xxxx”进⾏调⽤
6. 然后是继续对BeanPostProcessor 这个接⼝中的后置初始化⽅法进⾏⼀个调⽤postProcessAfterInitialization()
7. 其实到这⼀步,基本上这个bean的初始化基本已经完成,就处于就绪状态
8. 然后就是当Spring容器中如果使⽤完毕的话,就会调⽤destory()⽅法
9. 最后会去执⾏我们⾃⼰定义的销毁⽅法来进⾏销毁,然后结束⽣命周期
补充:当在执⾏第六步之后,如果Spring初始的这个bean的作⽤域是Prototype的话,之后的过程就不归Spring来去管理了,直接就交给⽤户就可以了。
⾯试题4. 说⼀下Spring中的IOC核⼼思想和DI?
⾯试题5. 说说Spring中事务实现原理?
1. 事务的实现原理⾸先第⼀步是需要先打开 @EnableTransactionManagement 是开启注解式事务的事务
2. 然后通过注解中套娃的TransactionManagementConfigurationSelector对象源码
spring ioc注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public@interface EnableTransactionManagement {
/**
* ⽤来表⽰默认使⽤JDK Dynamic Proxy还是CGLIB Proxy
*/
boolean proxyTargetClass()default false;
/**
* 表⽰以Proxy-based⽅式实现AOP还是以Weaving-based⽅式实现AOP
*/
AdviceMode mode()default AdviceMode.PROXY;
/**
* 顺序
*/
int order()default Ordered.LOWEST_PRECEDENCE;
}
3. 然后通过TransactionManangementConfigurationSelector这个对象去选择⼀些Bean,向容器注册BeanDefinition的(严格意义上
Selector仅时选择过程,注册的具体过程是在 ConfigurationClasspathPostProcessor 解析时,调⽤ ConfigurationClassParser 触发)。
主要的逻辑就是根据代理模式,注册不同的BeanDefinition。
对Proxy的模式⽽⾔,注⼊的有两个
AutoProxyRegistrar
ProxyTransactionManagementConfiguration
4. AutoProxyRegistrar
Registrar同样也是⽤来向容器注册Bean的,在Proxy的模式下,它会调⽤
AbstractAdvisorAutoProxyCreator 的⼦类。
从⽽,我们完成了我们的第⼀个条件——AOP代理。
ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration 是⼀个配置类,如果算上其继承的⽗类,⼀共是声明了四个类:
TransactionalEventListenerFactory
BeanFactoryTransactionAttributeSourceAdvisor
TransactionAttributeSource
TransactionInterceptor
后三个类相对⽐较重要,我们⼀⼀分析
5. 最关键的是这个 TransactionInterceptor
@Nullable
protected Object invokeWithinTransaction(Method method,@Nullable Class<?> targetClass,
final InvocationCallback invocation)throws Throwable {
TransactionAttributeSource tas =getTransactionAttributeSource();
final TransactionAttribute txAttr =(tas !=null? TransactionAttribute(method, targetClass):null);
final TransactionManager tm =determineTransactionManager(txAttr);
//省略部分代码
//获取事物管理器
PlatformTransactionManager ptm =asPlatformTransactionManager(tm);
final String joinpointIdentification =methodIdentification(method, targetClass, txAttr);
if(txAttr ==null||!(ptm instanceof CallbackPreferringPlatformTransactionManager)){
// 打开事务(内部就是getTransactionStatus的过程)
TransactionInfo txInfo =createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try{
// 执⾏业务逻辑 invocation.proceedWithInvocation();
}
catch(Throwable ex){
// 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally{
cleanupTransactionInfo(txInfo);
}
/
/省略部分代码
//提交事物
commitTransactionAfterReturning(txInfo);
return retVal;
}
⾯试题6. 说⼀下SpringMVC中的和Servlet中的filter有什么区别?
⾸先最核⼼的⼀点他们的拦截侧重点是不同的,SpringMVC中的是依赖JDK的反射实现的,SpringMVC的主要是进⾏拦截请求,通过对Handler进⾏处理的时候进⾏拦截,先声明的中的preHandle⽅法会先执⾏,然⽽它的postHandle⽅法(他是介于处理完业务之后和返回结果之前)和afterCompletion⽅法却会后执⾏。并且Spring的是按照配置的先后顺序进⾏拦截的。
⽽Servlet的filter是基于函数回调实现的过滤器,Filter主要是针对URL地址做⼀个编码的事情、过滤掉没⽤的参数、安全校验(⽐较泛的,⽐如登录不登录之类)
⾯试题7. spring容器的bean什么时候被实例化?
(1)如果你使⽤BeanFactory作为Spring Bean的⼯⼚类,则所有的bean都是在第⼀次使⽤该Bean的时候实例化
(2)如果你使⽤ApplicationContext作为Spring Bean的⼯⼚类,则⼜分为以下⼏种情况:
如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不⽤设置),则 ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在⼀个map结构的缓存中,下次再使 ⽤该 Bean的时候,直接从这个缓存中取
如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第⼀次使⽤该Bean的时候进 ⾏实例化
如果bean的scope是prototype的,则该Bean的实例化是在第⼀次使⽤该Bean的时候进⾏实例化
⾯试题8.说⼀下Spring中AOP的底层是怎么实现的?,再说说⼀下动态代理和cglib区别?

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。