Spring常问的------真实大厂面试题汇总(含答案)

面试专题 · 2019-05-29 · 197 人浏览

面试题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);12345678

2. 然后是怎么具体使用到这个三级缓存的呢,或者说三级缓存的思路?

  • 首先第一步是在Spring中会先去调用getSingleton(String beanName, boolean allowEarlyReference)来获取想要的单例对象。

  • 然后第一步会先进行通过singletonObjects这个一级缓存的集合中去获取对象,如果没有获取成功的话并且使用isSingletonCurrentlyInCreation(beanName)去判断对应的单例对象是否正在创建中(也就是说当单例对象没有被初始化完全,走到初始化的第一步或者第二的时候),如果是正在创建中的话,会继续走到下一步

  • 然后会去从earlySingletonObjects中继续获取这个对象,如果又没有获取到这个单例对象的话,并且通过参数传进来的allowEarlyReference标志,看是不是允许singletonFactories(三级缓存集合)去拿到该实例对象,如果allowEarlyReference为Ture的话,那么继续下一步

  • 此时上一步中并没有从earlySingletonObjects二级缓存集合中拿到想要的实例对象,最后只能从三级缓存singletonFactories (单例工厂集合中)去获取实例对象,

  • 然后把获取的对象通过Put(beanName, singletonObject)放到earlySingletonObjects(二级缓存中),然后在再从singletonFactories(三级缓存)对象中的集合中把该对象给remove(beanName)出去。

  • 附上核心代码

么这么做就能解决Spring中的循环依赖问题。
  • 其实在没有真正创建出来一个实例对象的时候,这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用

  • 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对象也蜕变完美了!一切都是这么神奇!!

总结: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?

之前自己有总结过的一篇:https://blog.csdn.net/qq_36520235/article/details/79383238

面试题5. 说说Spring中的几种事务和隔离级别?

面试题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区别?

Spring中AOP底层的实现其实是基于JDK的动态代理和cglib动态创建类进行动态代理来实现的:

1. 第一种基于JDK的动态代理的原理是:

需要用到的几个关键成员

  • InvocationHandler (你想要通过动态代理生成的对象都必须实现这个接口)

  • 真实的需要代理的对象(帮你代理的对象)

  • Proxy对象(是JDK中java.lang.reflect包下的)

下面是具体如何动态利用这三个组件生成代理对象

  • (1)首先你的真是要代理的对象必须要实现InvocationHandler 这个接口,并且覆盖这个接口的invoke(Object proxyObject, Method method, Object[] args)方法,这个Invoker中方法的参数的proxyObject就是你要代理的真实目标对象,方法调用会被转发到该类的invoke()方法, method是真实对象中调用方法的Method类,Object[] args是真实对象中调用方法的参数

  • (2)然后通过Proxy类去调用newProxyInstance(classLoader, interfaces, handler)方法,classLoader是指真实代理对象的类加载器,interfaces是指真实代理对象需要实现的接口,还可以同时指定多个接口,handler方法调用的实际处理者(其实就是帮你代理的那个对象),代理对象的方法调用都会转发到这里,然后直接就能生成你想要的对象类了。

下面是Proxy调用newProxyInstance的方法源码


public static Object newProxyInstance(ClassLoader loader,
                                     Class<?>[] interfaces,
                                     InvocationHandler h) throws IllegalArgumentException {
   //验证传入的InvocationHandler不能为空
   Objects.requireNonNull(h);
   //复制代理类实现的所有接口

   final Class<?>[] intfs = interfaces.clone();
   //获取安全管理器
   final SecurityManager sm = System.getSecurityManager();
   //进行一些权限检验
   if (sm != null) {
       checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
   }
   //该方法先从缓存获取代理类, 如果没有再去生成一个代理类
   Class<?> cl = getProxyClass0(loader, intfs);
   try {
       //进行一些权限检验
       if (sm != null) {
           checkNewProxyPermission(Reflection.getCallerClass(), cl);
       }
       //获取参数类型是InvocationHandler.class的代理类构造器
       final Constructor<?> cons = cl.getConstructor(constructorParams);
       final InvocationHandler ih = h;
       //如果代理类是不可访问的, 就使用特权将它的构造器设置为可访问
       if (!Modifier.isPublic(cl.getModifiers())) {
           AccessController.doPrivileged(new PrivilegedAction<Void>() {
               public Void run() {
                   cons.setAccessible(true);
                   return null;
               }
           });
       }
       //传入InvocationHandler实例去构造一个代理类的实例
       //所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
       return cons.newInstance(new Object[]{h});
   } catch (Exception e) {
       //为了节省篇幅, 笔者统一用Exception捕获了所有异常
       throw new InternalError(e.toString(), e);
   }
}

大佬的博客借鉴一下; https://www.cnblogs.com/liuyun1995/p/8157098.html