Java面試題全集(15)

Java面試題全集(15)

白玉 IT哈哈

141、Hibernate中DetachedCriteria類是做什麼的?

答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法創建的,也就意味着離開創建它的Session,Criteria就無法使用了。DetachedCriteria不需要Session就可以創建(使用DetachedCriteria.forClass()方法創建),所以通常也稱其爲離線的Criteria,在需要進行查詢操作的時候再和Session綁定(調用其getExecutableCriteria(Session)方法),這也就意味着一個DetachedCriteria可以在需要的時候和不同的Session進行綁定。

142、@OneToMany註解的mappedBy屬性有什麼作用?

答:@OneToMany用來配置一對多關聯映射,但通常情況下,一對多關聯映射都由多的一方來維護關聯關係,例如學生和班級,應該在學生類中添加班級屬性來維持學生和班級的關聯關係(在數據庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關係),如果要使用雙向關聯,在班級類中添加一個容器屬性來存放學生,並使用@OneToMany註解進行映射,此時mappedBy屬性就非常重要。如果使用XML進行配置,可以用<set>標籤的inverse="true"設置來達到同樣的效果。

143、MyBatis中使用#和$書寫佔位符有什麼區別?

答:#將傳入的數據都當成一個字符串,會對傳入的數據自動加上引號;$將傳入的數據直接顯示生成在SQL中。注意:使用$佔位符可能會導致SQL注射***,能用#的地方就不要使用$,寫order by子句的時候應該用$而不是#。

144、解釋一下MyBatis中命名空間(namespace)的作用。

答:在大型項目中,可能存在大量的SQL語句,這時候爲每個SQL語句起一個唯一的標識(ID)就變得並不容易了。爲了解決這個問題,在MyBatis中,可以爲每個映射文件起一個唯一的命名空間,這樣定義在這個映射文件中的每個SQL語句就成了定義在這個命名空間中的一個ID。只要我們能夠保證每個命名空間中這個ID是唯一的,即使在不同映射文件中的語句ID相同,也不會再產生衝突了。

145、MyBatis中的動態SQL是什麼意思?

答:對於一些複雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,例如在58同城上面找房子,我們可能會指定面積、樓層和所在位置來查找房源,也可能會指定面積、價格、戶型和所在位置來查找房源,此時就需要根據用戶指定的條件動態生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用於實現動態SQL的元素主要有:


- if 
- choose / when / otherwise 
- trim 
- where 
- set 
- foreach

下面是映射文件的片段。


<select id="foo" parameterType="Blog" resultType="Blog">
        select * from t_blog where 1 = 1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="content != null">
            and content = #{content}
        </if>
        <if test="owner != null">
            and owner = #{owner}
        </if>
   </select>

當然也可以像下面這些書寫。


 <select id="foo" parameterType="Blog" resultType="Blog">
        select * from t_blog where 1 = 1 
        <choose>
            <when test="title != null">
                and title = #{title}
            </when>
            <when test="content != null">
                and content = #{content}
            </when>
            <otherwise>
                and owner = "owner1"
            </otherwise>
        </choose>
    </select>

再看看下面這個例子。


<select id="bar" resultType="Blog">
        select * from t_blog where id in
        <foreach collection="array" index="index" 
            item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>

146、什麼是IoC和DI?DI是如何實現的?

答:IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的"控制反轉"就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器,由容器來創建對象並管理對象之間的依賴關係。IoC體現了好萊塢原則 - "Don’t call me, we will call you"。依賴注入的基本原則是應用組件不應該負責查找資源或者其他依賴的協作對象。配置對象的工作應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即組件之間的依賴關係由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關係注入到組件之中。
舉個例子:一個類A需要用到接口B中的方法,那麼就需要爲類A和接口B建立關聯或依賴關係,最原始的方法是在類A中創建一個接口B的實現類C的實例,但這種方法需要開發人員自行維護二者的依賴關係,也就是說當依賴關係發生變動的時候需要修改代碼並重新構建整個系統。如果通過一個容器來管理這些對象以及對象的依賴關係,則只需要在類A中定義好用於關聯接口B的方法(構造器或setter方法),將類A和接口B的實現類C放入容器中,通過對容器的配置來實現二者的關聯。

依賴注入可以通過setter方法注入(設值注入)、構造器注入和接口注入三種方式來實現,Spring支持setter注入和構造器注入,通常使用構造器注入來注入必須的依賴關係,對於可選的依賴關係,則setter注入是更好的選擇,setter注入需要類提供無參構造器或者無參的靜態工廠方法來創建對象。

147、Spring中Bean的作用域有哪些?

答:在Spring的早期版本中,僅有兩個作用域:singleton和prototype,前者表示Bean以單例的方式存在;後者表示每次從容器中調用Bean時,都會返回一個新的實例,prototype通常翻譯爲原型。

補充:設計模式中的創建型模式中也有一個原型模式,原型模式也是一個常用的模式,例如做一個室內設計軟件,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個原型,可以通過對象克隆來實現原型模式。

Spring 2.x中針對WebApplicationContext新增了3個作用域,分別是:request(每次HTTP請求都會創建一個新的Bean)、session(同一個HttpSession共享同一個Bean,不同的HttpSession使用不同的Bean)和globalSession(同一個全局Session共享一個Bean)。

說明:單例模式和原型模式都是重要的設計模式。一般情況下,無狀態或狀態不可變的類適合使用單例模式。在傳統開發中,由於DAO持有Connection這個非線程安全對象因而沒有使用單例模式;但在Spring環境下,所有DAO類對可以採用單例模式,因爲Spring利用AOP和Java API中的ThreadLocal對非線程安全的對象進行了特殊處理。

ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。ThreadLocal,顧名思義是線程的一個本地化對象,當工作於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每個使用該變量的線程分配一個獨立的變量副本,所以每一個線程都可以獨立的改變自己的副本,而不影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量。
ThreadLocal類非常簡單好用,只有四個方法,能用上的也就是下面三個方法:

  • void set(T value):設置當前線程的線程局部變量的值。
  • T get():獲得當前線程所對應的線程局部變量的值。
  • void remove():刪除當前線程中線程局部變量的值。
    ThreadLocal是如何做到爲每一個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map,鍵爲線程對象,值是其線程對應的變量的副本,自己要模擬實現一個ThreadLocal類其實並不困難,代碼如下所示:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
    private Map<Thread, T> map = Collections
       .synchronizedMap(new HashMap<Thread, T>());
    public void set(T newValue) {
        map.put(Thread.currentThread(), newValue);
    }
    public T get() {
        return map.get(Thread.currentThread());
    }
    public void remove() {
        map.remove(Thread.currentThread());
    }
}

148、解釋一下什麼叫AOP(面向切面編程)?

答:AOP(Aspect-Oriented Programming)指一種程序設計範型,該範型以一種稱爲切面(aspect)的語言構造爲基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。

149、你是如何理解"橫切關注"這個概念的?

答:"橫切關注"是會影響到整個應用程序的關注功能,它跟正常的業務邏輯是正交的,沒有必然的聯繫,但是幾乎所有的業務邏輯都會涉及到這些關注功能。通常,事務、日誌、安全性等關注就是應用中的橫切關注功能。

150、你如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?

答:
a. 連接點(Joinpoint):程序執行的某個特定位置(如:某個方法調用前、調用後,方法拋出異常後)。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些代碼中的特定點就是連接點。Spring僅支持方法的連接點。
b. 切點(Pointcut):如果連接點相當於數據中的記錄,那麼切點相當於查詢條件,一個切點可以匹配多個連接點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連接點。
c. 增強(Advice):增強是織入到目標類連接點上的一段程序代碼。Spring提供的增強接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多資料上將增強譯爲“通知”,這明顯是個詞不達意的翻譯,讓很多程序員困惑了許久。


說明: Advice在國內的很多書面資料中都被翻譯成"通知",但是很顯然這個翻譯無法表達其本質,有少量的讀物上將這個詞翻譯爲"增強",這個翻譯是對Advice較爲準確的詮釋,我們通過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有業務邏輯的一種增強,這種增強可以是前置增強、後置增強、返回後增強、拋異常時增強和包圍型增強。

d. 引介(Introduction):引介是一種特殊的增強,它爲類添加一些屬性和方法。這樣,即使一個業務類原本沒有實現某個接口,通過引介功能,可以動態的未該業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。
e. 織入(Weaving):織入是將增強添加到目標類具體連接點上的過程,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行增強;③運行時織入:在運行時爲目標類生成代理實現增強。Spring採用了動態代理的方式實現了運行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。
f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連接點的定義。

補充:代理模式是GoF提出的23種設計模式中最爲經典的模式之一,代理模式是對象的結構模式,它給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。簡單的說,代理對象可以完成比原對象更多的職責,當需要爲原對象添加橫切關注功能時,就可以使用原對象的代理對象。我們在打開Office系列的Word文檔時,如果文檔中有插圖,當文檔剛加載時,文檔中的插圖都只是一個虛框佔位符,等用戶真正翻到某頁要查看該圖片時,纔會真正加載這張圖,這其實就是對代理模式的使用,代替真正圖片的虛框就是一個虛擬代理;Hibernate的load方法也是返回一個虛擬代理對象,等用戶真正需要訪問對象的屬性時,才向數據庫發出SQL語句獲得真實對象。

下面用一個找槍手代考的例子演示代理模式的使用:


/**
 * 參考人員接口
 * @author 駱昊
 *
 */
public interface Candidate {
    /**
     * 答題
     */
    public void answerTheQuestions();
}

/**
 * 懶學生
 * @author 駱昊
 */
public class LazyStudent implements Candidate {
    private String name;        // 姓名
    public LazyStudent(String name) {
        this.name = name;
    }
    @Override
    public void answerTheQuestions() {
        // 懶學生只能寫出自己的名字不會答題
        System.out.println("姓名: " + name);
    }
}

/**
 * 槍手
 * @author 駱昊
 */
public class Gunman implements Candidate {
    private Candidate target;   // 被代理對象
    public Gunman(Candidate target) {
        this.target = target;
    }
    @Override
    public void answerTheQuestions() {
        // 槍手要寫上代考的學生的姓名
        target.answerTheQuestions();
        // 槍手要幫助懶學生答題並交卷
        System.out.println("奮筆疾書正確答案");
        System.out.println("交卷");
    }
}

public class ProxyTest1 {
    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("王小二"));
        c.answerTheQuestions();
    }
}

說明:從JDK 1.3開始,Java提供了動態代理技術,允許開發者在運行時創建接口的代理實例,主要包括Proxy類和InvocationHandler接口。下面的例子使用動態代理爲ArrayList編寫一個代理,在添加和刪除元素時,在控制檯打印添加或刪除的元素以及ArrayList的大小:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
public class ListProxy<T>
 implements InvocationHandler {
    private List<T> target;
    public ListProxy(List<T> target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy,
           Method method, Object[] args)
            throws Throwable {
        Object retVal = null;
        System.out.println("[" + method.getName() + ": "
               + args[0] + "]");
        retVal = method.invoke(target, args);
        System.out.println("[size=" + target.size() + "]");
        return retVal;
    }
}

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public class ProxyTest2 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Class<?> clazz = list.getClass();
        ListProxy<String> myProxy = new ListProxy<String>(list);
        List<String> newList = (List<String>) 
                Proxy.newProxyInstance(clazz.getClassLoader(), 
                clazz.getInterfaces(), myProxy);
        newList.add("apple");
        newList.add("banana");
        newList.add("orange");
        newList.remove("banana");
    }
}

說明:使用Java的動態代理有一個侷限性就是代理的類必須要實現接口,雖然面向接口編程是每個優秀的Java程序都知道的規則,但現實往往不盡如人意,對於沒有實現接口的類如何爲其生成代理呢?繼承!繼承是最經典的擴展已有代碼能力的手段,雖然繼承常常被初學者濫用,但繼承也常常被進階的程序員忽視。CGLib採用非常底層的字節碼生成技術,通過爲一個類創建子類來生成代理,它彌補了Java動態代理的不足,因此Spring中動態代理和CGLib都是創建代理的重要手段,對於實現了接口的類就用動態代理爲其生成代理類,而沒有實現接口的類就用CGLib通過繼承的方式爲其創建代理。

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