面試還不懂這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

    相關文章
    相關標籤/搜索