面试还不懂这10道Spring问题,回去等通知了

最近有一位朋友和我说,他作了开发 3 年了,最近去面试时,Spring 被面试官问得哑口无言,他总结了下面几道被问到的关于 Spring 的面试题,能够参考下。前端

参考问题

  • Spring IoC、AOP 原理
  • Spring Bean 生命周期
  • Spring Bean 注入是如何解决循环依赖问题的
  • 怎样用注解的方式配置 Spring?
  • Spring 事务为什么失效了
  • SpringMVC 的流程?
  • Springmvc 的优势:
  • Spring 通知类型使用场景分别有哪些?
  • IoC 控制反转设计原理?
  • Spring 如何处理线程并发问题?
  • 参考解析

    1.Spring IoC、AOP 原理

    1.1.定义java

    1.1.1.IoC面试

    Inversion of Control,控制反转。是面向对象编程中的一种设计原则,能够用来减低计算机代码之间的耦合度。其中最多见的方式叫作依赖注入(DependencyInjection,简称 DI),这也是 Spring 的实现方式。经过控制反转,对象在被建立的时候,由一个调控系统内全部对象的外界实体将其所依赖的对象的引用传递给它。也能够说,依赖被注入到对象中。

    1.1.2.AOP编程

    Aspect Oriented Programming,面向切面编程。经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。其中,最经常使用的使用场景通常有日志模块、权限模块、事物模块。后端

    1.2.原理缓存

    1.2.1.IoC安全

    IoC 内部核心原理就是反射技术,固然这里面还涉及到 Bean 对象的初始化构建等步骤,这个在后面的生命周期中讲,这里咱们须要了解 Java 中反射是如何作的就好。这里主要说明下主要的相关类和可能面试问题转向,具体的 API 实现须要本身去看。

    还有其余的类不一一列举出来,都在 java.lang.reflect 包下。说到这个模块的时候,那么面试官可能会考察相关的知识,主要是考察你是否真的有去了解过反射的使用。举两个例子: bash

    利用反射获取实例的私有属性值怎么作

    这里其实就是里面的重要考察点就是反射对私有属性的处理。多线程

    /**
     * 经过反射获取私有的成员变量.
     */
    private Object getPrivateValue(Person person, String fieldName)
    {
        try
        {
            Field field = person.getClass().getDeclaredField(fieldName);
            // 主要就是这里,须要将属性的 accessible 设置为 true 
            field.setAccessible(true);
            return field.get(person);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }复制代码

    如何经过反射构建对象实例?

    使用默认构造函数(无参)建立的话: 架构

    Class.newInstance() Constroctor constroctor = clazz.getConstructor(String.class,Integer.class); Object obj = constroctor.newInstance("name", 18);复制代码

    1.2.2.AOP

    AOP 的内部原理其实就是动态代理和反射了。主要涉及到的反射类:


    动态代理相关原理的话,你须要了解什么是代理模式、静态代理的不足、动态代理的实现原理。 Spring 中实现动态代理有两种方式可选,这两种动态代理的实现方式的一个对比也
    是面试中常问的。

    JDK 动态代理

    必须实现 InvocationHandler 接口,而后经过 Proxy.newProxyInstance(ClassLoader
    loader, Class<?>[] interfaces, InvocationHandler h) 得到动态代理对象。
    CGLIB 动态代理

    使用 CGLIB 动态代理,被代理类不须要强制实现接口。CGLIB 不能对声明为 final的方法进行代理,由于 CGLIB 原理是动态生成被代理类的子类。

    OK,AOP 讲了。其实讲到这里,可能会有一个延伸的面试问题。咱们知道,Spring事物也是 通 过 AOP 来 实 现的 , 咱们使用的时候 一 般就是在方法上 加@Tranactional 注解,那么你有没有遇到过事物不生效的状况呢?这是为何?这个问题咱们在后面的面试题中会讲。

    2.Spring Bean 生命周期


    这只是个大致流程,内部的具体行为太多,须要自行去看看代码。

    3.Spring Bean 注入是如何解决循环依赖问题的

    3.1. 什么是循环依赖,有啥问题?

    循环依赖就是 N 个类中循环嵌套引用,这样会致使内存溢出。循环依赖主要分两种:

    • 构造器循环依赖
    • setter 循环依赖
    3.2. Spring 解决循环依赖问题
    • 构造器循环依赖问题
    无解,直接抛出 BeanCurrentlyInCreatingException 异常。
    • setter 循环依赖问题
    单例模式下,经过“三级缓存”来处理。非单例模式的话,问题无解。

    Spring 初始化单例对象大致是分为以下三个步骤的:

    • createBeanInstance:调用构造函数建立对象
    • populateBean:调用类的 setter 方法填充对象属性
    • initializeBean:调用定义的 Bean 初始化 init 方法
    能够看出,循环依赖主要发生在 一、2 步,固然若是发生在第一步的话,Spring 也是没法解决该问题的。那么就剩下第二步 populateBean 中出现的循环依赖问题。经过“三级缓存”来处理,三级缓存以下:

    • singletonObjects:Cache of singleton objects: bean name --> bean instance,完成初始化的单例对象的 cache(一级缓存)
    • earlySingletonObjects:Cache of early singleton objects: bean name--> bean instance ,完成实例化可是还没有初始化的,提早暴光的单例对象的 cache (二级缓存)
    • singletonFactories : Cache of singleton factories: bean name -->ObjectFactory,进入实例化阶段的单例对象工厂的 cache (三级缓存)

    咱们看下获取单例对象的方法:

    protected Object getSingleton(String beanName, boolean allowEarlyReference)
    {
        Object singletonObject = this.singletonObjects.get(beanName);
        // isSingletonCurrentlyInCreation:判断当前单例 bean 是否正在建立中 
        if(singletonObject == null && isSingletonCurrentlyInCreation(beanName))
        {
            synchronized(this.singletonObjects)
            {
                singletonObject = this.earlySingletonObjects.get(beanName);
                // allowEarlyReference:是否容许从 singletonFactories 中经过 getObject 拿到 
                对象
                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);
    }复制代码

    其中解决循环依赖问题的关键点就在 singletonFactory.getObject() 这一步,getObject 这是 ObjectFactory<T> 接口的方法。Spring 经过对该方法的实现,在createBeanInstance 以后,populateBean 以前,经过将建立好但还没完成属性设置和初始化的对象提早曝光,而后再获取 Bean 的时候去看是否有提早曝光的对象实例来判断是否要走建立流程。

    protected void addSingletonFactory(String beanName, ObjectFactory <? > singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects)
        {
            if(!this.singletonObjects.containsKey(beanName))
            {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }复制代码

    4.Spring 事务为什么失效了

    可能的缘由:

    1. MySQL 使用的是 MyISAM 引擎,而 MyISAM 是不支持事务的。须要支持使用可使用 InnoDB 引擎
    2. 若是使用了 Spring MVC ,context:component-scan 重复扫描问题可能会引发事务失败
    3. @Transactional 注解开启配置放到 DispatcherServlet 的配置里了。
    4. @Transactional 注解只能应用到 public 可见度的方法上。 在其余可见类型上声明,事务会失效。
    5. 在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因此若是强制使用了 CGLIB,那么事物会实效。
    6. @Transactional 同一个类中无事务方法 a() 内部调用有事务方法 b(),那么此时事物不生效。
    按 理 说 , 如 果 按 照 Spring 说 的 事 物 传 播 级 别 去 配 置 其 事 物 级 别 为REQUIRES_NEW 的话,那么应该是在调用 b() 的时候会新生成一个事物。实际上却没有。



    NOT_SUPPORTED 老是非事务地执行,并挂起任何存在的事务其实,这是因为 Spring 的事物实现是经过 AOP 来实现的。此时,当这个有注解的方法 b() 被调用的时候,其实是由代理类来调用的,代理类在调用以前就会启动 transaction。然而,若是这个有注解的方法是被同一个类中的其余方法 a() 调用的,那么该方法的调用并无经过代理类,而是直接经过原来的那个 bean,因此就不会启动 transaction,咱们看到的现象就是 @Transactional 注解无效。

    5.怎样用注解的方式配置 Spring?

    Spring 在 2.5 版本之后开始支持用注解的方式来配置依赖注入。能够用注解的方式来替代XML 方式的 bean 描述,能够将 bean 描述转移到组件类的内部,只须要在相关类上、方法上或者字段声明上使用注解便可。注解注入将会被容器在 XML 注入以前被处理,因此后者会覆盖掉前者对于同一个属性的处理结果。注解装配在 Spring 中是默认关闭的。因此须要在 Spring 文件中配置一下才能使用基于注解的装配模式。若是你想要在你的应用程序中使用关于注解的方法的话,请参考以下的配置。

    <beans>
        <context:annotation-config/>
        <!-- bean definitions go here -->
    </beans>复制代码

    在标签配置完成之后,就能够用注解的方式在 Spring 中向属性、方法和构造方法中自动装配变量。

    下面是几种比较重要的注解类型:

    • @Required:该注解应用于设值方法。
    • @Autowired:该注解应用于有值设值方法、非设值方法、构造方法和变量。
    • @Qualifier:该注解和@Autowired 注解搭配使用,用于消除特定 bean 自动装配的歧义。
    • JSR-250 Annotations:Spring 支持基于 JSR-250 注解的如下注解,@Resource、@PostConstruct 和@PreDestroy。

    六、SpringMVC 的流程?

    1. 用户发送请求至前端控制器 DispatcherServlet;
    2. DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取Handle
    3. 处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(若是有则生成)一并返回给 DispatcherServlet;
    4. DispatcherServlet 调用 HandlerAdapter 处理器适配器;
    5. HandlerAdapter 通过适配调用 具体处理器(Handler,也叫后端控制器);
    6. Handler 执行完成返回 ModelAndView;
    7. HandlerAdapter 将 Handler 执 行 结 果 ModelAndView 返 回 给DispatcherServlet ;
    8. DispatcherServlet 将 ModelAndView 传 给ViewResolver 视图解析器进行解析;
    9. ViewResolver 解析后返回具体View;
    10. DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
    11. DispatcherServlet 响应用户。


    七、Springmvc 的优势:

    1. 能够支持各类视图技术,而不只仅局限于 JSP;
    2. 与 Spring 框架集成(如 IoC 容器、AOP 等);
    3. 清 晰 的 角 色 分 配 : 前 端 控 制 器 (dispatcherServlet) , 请 求 处处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
    4. 支持各类请求资源的映射策略。

    8. Spring 通知类型使用场景分别有哪些?

    9.IoC 控制反转设计原理?

    具体设计原理以下图:


    10.Spring 如何处理线程并发问题?

    Spring 使用 ThreadLocal 解决线程安全问题。咱们知道在通常状况下,只有无状态的 Bean 才能够在多线程环境下共享,在Spring 中,绝大部分 Bean 均可以声明为 singleton 做用域。就是由于Spring 对 一 些 Bean ( 如 RequestContextHolder 、
    TransactionSynchronizationManager、LocaleContextHolder 等)中非线程安全状态采用 ThreadLocal 进行处理,让它们也成为线程安全的状态,由于有状态的 Bean 就能够在多线程中共享了。

    ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析何时对变量进行读写,何时须要锁定某个对象,何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而 ThreadLocal 则从另外一个角度来解决多线程的并发访问。 ThreadLocal 会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每个线程都拥有本身的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,能够把不安全的变量封装进 ThreadLocal。

    因为 ThreadLocal 中能够持有任何类型的对象,低版本 JDK 所提供的 get()返回的是 Object 对象,须要强制类型转换。但 JDK 5.0 经过泛型很好的解决了这个问题,在必定程度地简化 ThreadLocal 的使用。归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。

    最后

    欢迎你们一块儿交流,喜欢文章记得关注我点个赞哟,感谢支持!

    欢迎你们关注个人公众号【以Java架构赢天下】,2019年多家公司java面试笔记整理了500多页pdf文档,文章都会在里面更新,整理的资料也会放在里面。


    image.png

    相关文章
    相关标签/搜索