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 = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
       从二级缓存获取
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
          从三级缓存获取
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(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能够通过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

面试题9. Spring的几种注入方式说一下?

(1)构造方法注入:

<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
	<constructor-arg ref="userDaoJdbc"></constructor-arg>
</bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>

注意的点:

  • 如果有多个构造参数,那么与构造方法参数列表参数的顺序无关
  • 如果有多个构造方法且参数顺序不同,那么会按第一个构造方法进行注入

(2)set方法注入:

<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
	<!-- 写法一 -->
	<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
	<!-- 写法二 -->
	<property name="userDao" ref="userDaoMyBatis"></property>
</bean>

<!-- 注册mybatis实现的dao -->
<bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>

注意的点:

  • 其实set方法的注入原理就是,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入
  • 如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。

(3)注解的方式注入:

  • @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean
  • @Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个bean。determineAutowireCandidate 方法的内容如下:
// candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
    //  requiredType 为匹配到的接口的类型
   Class<?> requiredType = descriptor.getDependencyType();
   // 1. 先找 Bean 上有@Primary 注解的,有则直接返回
   String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
   if (primaryCandidate != null) {
       return primaryCandidate;
   } else {
       // 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
       String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
       if (priorityCandidate != null) {
           return priorityCandidate;
       } else {
           Iterator var6 = candidateBeans.entrySet().iterator();

           String candidateBeanName;
           Object beanInstance;
           do {
               if (!var6.hasNext()) {
                   return null;
               }

               // 3. 再找 bean 的名称匹配的
               Entry<String, Object> entry = (Entry)var6.next();
               candidateBeanName = (String)entry.getKey();
               beanInstance = entry.getValue();
           } while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));

           return candidateBeanName;
       }
   }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章