最新章節(jié)
書友吧第1章
141、Hibernate 中 DetachedCriteria 類是做什么的?
答:DetachedCriteria 和 Criteria 的用法基本上是一致的,但 Criteria 是由 Session 的createCriteria()方法創(chuàng)建的,也就意味著離開創(chuàng)建它的 Session,Criteria 就無法使用了。 DetachedCriteria 不需要 Session 就可以創(chuàng)建(使用 DetachedCriteria.forClass()方法創(chuàng)建),所以通常也稱其為離線的 Criteria,在需要進(jìn)行查詢操作的時(shí)候再和 Session 綁定(調(diào)用其 getExecutableCriteria(Session)方法),這也就意味著一個(gè) DetachedCriteria 可以在需要的時(shí)候和不同的 Session 進(jìn)行綁定。
142、@OneToMany 注解的 mappedBy 屬性有什么作用?
答:@OneToMany 用來配置一對多關(guān)聯(lián)映射,但通常情況下,一對多關(guān)聯(lián)映射都由多的一方來維護(hù)關(guān)聯(lián)關(guān)系,例如學(xué)生和班級(jí),應(yīng)該在學(xué)生類中添加班級(jí)屬性來維持學(xué)生和班級(jí)的關(guān)聯(lián)關(guān)系(在數(shù)據(jù)庫中是由學(xué)生表中的外鍵班級(jí)編號(hào)來維護(hù)學(xué)生表和班級(jí)表的多對一關(guān)系),如果要使用雙向關(guān)聯(lián),在班級(jí)類中添加一個(gè)容器屬性來存放學(xué)生,并使用@OneToMany 注解進(jìn)行映射,此時(shí) mappedBy 屬性就非常重要。如果使用 XML 進(jìn)行配置,可以用<set>標(biāo)簽的inverse=“true“設(shè)置來達(dá)到同樣的效果。
143、MyBatis 中使用#和$書寫占位符有什么區(qū)別?
答:#將傳入的數(shù)據(jù)都當(dāng)成一個(gè)字符串,會(huì)對傳入的數(shù)據(jù)自動(dòng)加上引號(hào);$將傳入的數(shù)據(jù)直接顯示生成在 SQL 中。注意:使用$占位符可能會(huì)導(dǎo)致 SQL 注射攻擊,能用#的地方就不要使用$,寫 order by 子句的時(shí)候應(yīng)該用$而不是#。
144、解釋一下 MyBatis 中命名空間(namespace)的作用。
答:在大型項(xiàng)目中,可能存在大量的 SQL 語句,這時(shí)候?yàn)槊總€(gè) SQL 語句起一個(gè)唯一的標(biāo)識(shí)(ID)就變得并不容易了。為了解決這個(gè)問題,在 MyBatis 中,可以為每個(gè)映射文件起一個(gè)唯一的命名空間,這樣定義在這個(gè)映射文件中的每個(gè) SQL 語句就成了定義在這個(gè)命名空間中的一個(gè)ID。只要我們能夠保證每個(gè)命名空間中這個(gè) ID 是唯一的,即使在不同映射文件中的語句 ID 相同,也不會(huì)再產(chǎn)生沖突了。
145、MyBatis 中的動(dòng)態(tài) SQL 是什么意思?
答:對于一些復(fù)雜的查詢,我們可能會(huì)指定多個(gè)查詢條件,但是這些條件可能存在也可能不存在,例如在 58 同城上面找房子,我們可能會(huì)指定面積、樓層和所在位置來查找房源,也可能會(huì)指定面積、價(jià)格、戶型和所在位置來查找房源,此時(shí)就需要根據(jù)用戶指定的條件動(dòng)態(tài)生成 SQL 語句。如果不使用持久層框架我們可能需要自己拼裝 SQL 語句,還好 MyBatis 提供了動(dòng)態(tài) SQL 的功能來解決這個(gè)問題。MyBatis 中用于實(shí)現(xiàn)動(dòng)態(tài) 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>
當(dāng)然也可以像下面這些書寫。
<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>
再看看下面這個(gè)例子。
<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 是如何實(shí)現(xiàn)的?
答:IoC 叫控制反轉(zhuǎn),是 Inversion of Control 的縮寫,DI(Dependency Injection)叫依賴注入,是對 IoC 更簡單的詮釋。控制反轉(zhuǎn)是把傳統(tǒng)上由程序代碼直接操控的對象的調(diào)用權(quán)交給容器,通過容器來實(shí)現(xiàn)對象組件的裝配和管理。所謂的“控制反轉(zhuǎn)“就是對組件對象控制權(quán)的轉(zhuǎn)移,從程序代碼本身轉(zhuǎn)移到了外部容器,由容器來創(chuàng)建對象并管理對象之間的依賴關(guān)系。IoC 體現(xiàn)了好萊塢原則-“Don’t call me, we will call you“。依賴注入的基本原則是應(yīng)用組件不應(yīng)該負(fù)責(zé)查找資源或者其他依賴的協(xié)作對象。配置對象的工作應(yīng)該由容器負(fù)責(zé),查找資源的邏輯應(yīng)該從應(yīng)用組件的代碼中抽取出來,交給容器來完成。DI 是對 IoC 更準(zhǔn)確的描述,即組件之間的依賴關(guān)系由容器在運(yùn)行期決定,形象的來說,即由容器動(dòng)態(tài)的將某種依賴關(guān)系注入到組件之中。
舉個(gè)例子:一個(gè)類 A 需要用到接口 B 中的方法,那么就需要為類 A 和接口 B 建立關(guān)聯(lián)或依賴關(guān)系,最原始的方法是在類 A 中創(chuàng)建一個(gè)接口 B 的實(shí)現(xiàn)類 C 的實(shí)例,但這種方法需要開發(fā)人員自行維護(hù)二者的依賴關(guān)系,也就是說當(dāng)依賴關(guān)系發(fā)生變動(dòng)的時(shí)候需要修改代碼并重新構(gòu)建整個(gè)系統(tǒng)。如果通過一個(gè)容器來管理這些對象以及對象的依賴關(guān)系,則只需要在類 A 中定義好用于關(guān)聯(lián)接口 B 的方法(構(gòu)造器或 setter 方法),將類 A 和接口 B 的實(shí)現(xiàn)類 C 放入容器中,通過對容器的配置來實(shí)現(xiàn)二者的關(guān)聯(lián)。
依賴注入可以通過 setter 方法注入(設(shè)值注入)、構(gòu)造器注入和接口注入三種方式來實(shí)現(xiàn), Spring 支持 setter 注入和構(gòu)造器注入,通常使用構(gòu)造器注入來注入必須的依賴關(guān)系,對于可選的依賴關(guān)系,則 setter 注入是更好的選擇,setter 注入需要類提供無參構(gòu)造器或者無參的靜態(tài)工廠方法來創(chuàng)建對象。
147、Spring 中 Bean 的作用域有哪些?
答:在 Spring 的早期版本中,僅有兩個(gè)作用域:singleton 和 prototype,前者表示 Bean 以單例的方式存在;后者表示每次從容器中調(diào)用 Bean 時(shí),都會(huì)返回一個(gè)新的實(shí)例,prototype通常翻譯為原型。
補(bǔ)充:設(shè)計(jì)模式中的創(chuàng)建型模式中也有一個(gè)原型模式,原型模式也是一個(gè)常用的模式,例如做一個(gè)室內(nèi)設(shè)計(jì)軟件,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個(gè)原型,可以通過對象克隆來實(shí)現(xiàn)原型模式。
Spring 2.x 中針對 WebApplicationContext 新增了 3 個(gè)作用域,分別是:request(每次 HTTP 請求都會(huì)創(chuàng)建一個(gè)新的 Bean)、session(同一個(gè) HttpSession 共享同一個(gè) Bean,不同的 HttpSession 使用不同的 Bean)和 globalSession(同一個(gè)全局 Session 共享一個(gè) Bean)。說明:單例模式和原型模式都是重要的設(shè)計(jì)模式。一般情況下,無狀態(tài)或狀態(tài)不可變的類適合使用單例模式。在傳統(tǒng)開發(fā)中,由于 DAO 持有 Connection 這個(gè)非線程安全對象因而沒有使用單例模式;但在 Spring 環(huán)境下,所有 DAO 類對可以采用單例模式,因?yàn)?Spring 利用 AOP 和 Java API 中的 ThreadLocal 對非線程安全的對象進(jìn)行了特殊處理。
ThreadLocal 為解決多線程程序的并發(fā)問題提供了一種新的思路。ThreadLocal,顧名思義是線程的一個(gè)本地化對象,當(dāng)工作于多線程中的對象使用 ThreadLocal 維護(hù)變量時(shí), ThreadLocal 為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立的改變自己的副本,而不影響其他線程所對應(yīng)的副本。從線程的角度看,這個(gè)變量就像是線程的本地變量。
ThreadLocal 類非常簡單好用,只有四個(gè)方法,能用上的也就是下面三個(gè)方法:
- void set(T value):設(shè)置當(dāng)前線程的線程局部變量的值。
- T get():獲得當(dāng)前線程所對應(yīng)的線程局部變量的值。
- void remove():刪除當(dāng)前線程中線程局部變量的值。
ThreadLocal 是如何做到為每一個(gè)線程維護(hù)一份獨(dú)立的變量副本的呢?在 ThreadLocal 類中有一個(gè) Map ,鍵為線程對象,值是其線程對應(yīng)的變量的副本,自己要模擬實(shí)現(xiàn)一個(gè) ThreadLocal 類其實(shí)并不困難,代碼如下所示: 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(), ewValue);
}
public T get(){
return map.get(Thread.currentThread());
}
public void remove(){
map.remove(Thread.currentThread());
}
}
148、解釋一下什么叫 AOP(面向切面編程)?
答:AOP(Aspect-Oriented Programming)指一種程序設(shè)計(jì)范型,該范型以一種稱為切面(aspect)的語言構(gòu)造為基礎(chǔ),切面是一種新的模塊化機(jī)制,用來描述分散在對象、類或方法中的橫切關(guān)注點(diǎn)(crosscutting concern)。
149、你是如何理解“橫切關(guān)注“這個(gè)概念的?
答:“橫切關(guān)注“是會(huì)影響到整個(gè)應(yīng)用程序的關(guān)注功能,它跟正常的業(yè)務(wù)邏輯是正交的,沒有必然的聯(lián)系,但是幾乎所有的業(yè)務(wù)邏輯都會(huì)涉及到這些關(guān)注功能。通常,事務(wù)、日志、安全性等關(guān)注就是應(yīng)用中的橫切關(guān)注功能。
150、你如何理解 AOP 中的連接點(diǎn)(Joinpoint)、切點(diǎn)(Pointcut)、增強(qiáng)(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?
答:
連接點(diǎn)(Joinpoint):程序執(zhí)行的某個(gè)特定位置(如:某個(gè)方法調(diào)用前、調(diào)用后,方法拋出異常后)。一個(gè)類或一段程序代碼擁有一些具有邊界性質(zhì)的特定點(diǎn),這些代碼中的特定點(diǎn)就是連接點(diǎn)。Spring 僅支持方法的連接點(diǎn)。
切點(diǎn)(Pointcut):如果連接點(diǎn)相當(dāng)于數(shù)據(jù)中的記錄,那么切點(diǎn)相當(dāng)于查詢條件,一個(gè)切點(diǎn)可以匹配多個(gè)連接點(diǎn)。Spring AOP 的規(guī)則解析引擎負(fù)責(zé)解析切點(diǎn)所設(shè)定的查詢條件,找到對應(yīng)的連接點(diǎn)。
增強(qiáng)(Advice):增強(qiáng)是織入到目標(biāo)類連接點(diǎn)上的一段程序代碼。Spring 提供的增強(qiáng)接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice 等。很多資料上將增強(qiáng)譯為“通知”,這明顯是個(gè)詞不達(dá)意的翻譯,讓很多程序員困惑了許久。
說明: Advice 在國內(nèi)的很多書面資料中都被翻譯成“通知“,但是很顯然這個(gè)翻譯無法表達(dá)其本質(zhì),有少量的讀物上將這個(gè)詞翻譯為“增強(qiáng)“,這個(gè)翻譯是對 Advice 較為準(zhǔn)確的詮釋,我們通過 AOP 將橫切關(guān)注功能加到原有的業(yè)務(wù)邏輯上,這就是對原有業(yè)務(wù)邏輯的一種增強(qiáng),這種增強(qiáng)可以是前置增強(qiáng)、后置增強(qiáng)、返回后增強(qiáng)、拋異常時(shí)增強(qiáng)和包圍型增強(qiáng)。
引介(Introduction):引介是一種特殊的增強(qiáng),它為類添加一些屬性和方法。這樣,即使一個(gè)業(yè)務(wù)類原本沒有實(shí)現(xiàn)某個(gè)接口,通過引介功能,可以動(dòng)態(tài)的未該業(yè)務(wù)類添加接口的實(shí)現(xiàn)邏輯,讓業(yè)務(wù)類成為這個(gè)接口的實(shí)現(xiàn)類。
織入(Weaving):織入是將增強(qiáng)添加到目標(biāo)類具體連接點(diǎn)上的過程,AOP 有三種織入方式:①編譯期織入:需要特殊的 Java 編譯期(例如 AspectJ 的 ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時(shí)候?qū)︻愡M(jìn)行增強(qiáng);③運(yùn)行時(shí)織入:在運(yùn)行時(shí)為目標(biāo)類生成代理實(shí)現(xiàn)增強(qiáng)。Spring 采用了動(dòng)態(tài)代理的方式實(shí)現(xiàn)了運(yùn)行時(shí)織入,而 AspectJ 采用了編譯期織入和裝載期織入的方式。
切面(Aspect):切面是由切點(diǎn)和增強(qiáng)(引介)組成的,它包括了對橫切關(guān)注功能的定義,也包括了對連接點(diǎn)的定義。
補(bǔ)充:代理模式是 GoF 提出的 23 種設(shè)計(jì)模式中最為經(jīng)典的模式之一,代理模式是對象的結(jié)構(gòu)模式,它給某一個(gè)對象提供一個(gè)代理對象,并由代理對象控制對原對象的引用。簡單的說,代理對象可以完成比原對象更多的職責(zé),當(dāng)需要為原對象添加橫切關(guān)注功能時(shí),就可以使用原對象的代理對象。我們在打開 Office 系列的 Word 文檔時(shí),如果文檔中有插圖,當(dāng)文檔剛加載時(shí),文檔中的插圖都只是一個(gè)虛框占位符,等用戶真正翻到某頁要查看該圖片時(shí),才會(huì)真正加載這張圖,這其實(shí)就是對代理模式的使用,代替真正圖片的虛框就是一個(gè)虛擬代理; Hibernate 的 load 方法也是返回一個(gè)虛擬代理對象,等用戶真正需要訪問對象的屬性時(shí),才向數(shù)據(jù)庫發(fā)出 SQL 語句獲得真實(shí)對象。
下面用一個(gè)找槍手代考的例子演示代理模式的使用:
/**
*參考人員接口
*/
public interface Candidate {
/**
答題
*/
public void answerTheQuestions();
}
/**
*懶學(xué)生
*@author 駱昊
*
*/
public class LazyStudent implements Candidate {
private String name;//姓名
public LazyStudent(String name){
this.name = name;
}
@Override
public void answerTheQuestions(){
//懶學(xué)生只能寫出自己的名字不會(huì)答題
System.out.println(“姓名:“+ name);
}
}
/**
槍手
@author 駱昊
*/
public class Gunman implements Candidate { private Candidate target;//被代理對象
public Gunman(Candidate target){
this.target = target;
}
@Override
public void answerTheQuestions(){
//槍手要寫上代考的學(xué)生的姓名
target.answerTheQuestions();
//槍手要幫助懶學(xué)生答題并交卷
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 提供了動(dòng)態(tài)代理技術(shù),允許開發(fā)者在運(yùn)行時(shí)創(chuàng)建接口的代理實(shí)例,主要包括 Proxy 類和 InvocationHandler 接口。下面的例子使用動(dòng)態(tài)代理為 ArrayList 編寫一個(gè)代理,在添加和刪除元素時(shí),在控制臺(tái)打印添加或刪除的元素以及 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 的動(dòng)態(tài)代理有一個(gè)局限性就是代理的類必須要實(shí)現(xiàn)接口,雖然面向接口編程是每個(gè)優(yōu)秀的 Java 程序都知道的規(guī)則,但現(xiàn)實(shí)往往不盡如人意,對于沒有實(shí)現(xiàn)接口的類如何為其生成代理呢?繼承!繼承是最經(jīng)典的擴(kuò)展已有代碼能力的手段,雖然繼承常常被初學(xué)者濫用,但繼承也常常被進(jìn)階的程序員忽視。CGLib 采用非常底層的字節(jié)碼生成技術(shù),通過為一個(gè)類創(chuàng)建子類來生成代理,它彌補(bǔ)了 Java 動(dòng)態(tài)代理的不足,因此 Spring 中動(dòng)態(tài)代理和 CGLib 都是創(chuàng)建代理的重要手段,對于實(shí)現(xiàn)了接口的類就用動(dòng)態(tài)代理為其生成代理類,而沒有實(shí)現(xiàn)接口的類就用 CGLib 通過繼承的方式為其創(chuàng)建代理。