1. 程式人生 > >Java程式設計師面試題集(151-180)

Java程式設計師面試題集(151-180)

分享一下我的偶像大神的人工智慧教程!http://blog.csdn.net/jiangjunshow

Java面試題集(151-180)

摘要:這部分包含了Spring、Spring MVC以及Spring和其他框架整合以及測試相關的內容,除此之外還包含了大型網站技術架構相關面試內容。


151. Spring中的BeanFactory和ApplicationContext有什麼聯絡?

答:Spring通過配置檔案描述Bean以及Bean之間的依賴關係,利用Java的反射機制實現Bean的例項化,並建立Bean之間的依賴關係,在此基礎上,Spring的IoC容器還提供了Bean例項快取、生命週期管理、Bean例項代理、事件釋出、資源裝載等高階服務。BeanFactory是Spring框架最核心的介面,它提供了IoC容器的配置機制。ApplicationContext建立在BeanFactory之上,提供了更多面嚮應用的功能,包括對國際化和框架事件體系的支援。通常將BeanFactory稱為IoC容器,而ApplicationContext稱為應用上下文,前者更傾向於Spring本身,後者更傾向於開發者,因此被使用得更多。

【補充】反射(reflection)又叫自省(introspection),是獲得物件或型別元資料的方法,Java反射機制可以在執行時判斷物件所屬的類,在執行時構造任意一個類的物件,在執行時獲得一個類的屬性和方法,在執行時呼叫物件的方法,或者生成動態代理。在Java中,可以通過類的Class物件獲得類的構造器、屬性、方法等類的元資料,還可以訪問這些屬性或呼叫這些方法,和反射相關的類還包括:

  1. Constructor:代表類的構造器的類。通過Class物件的getConstructors方法可以獲得類的所有構造器的陣列,Java 5以後的版本還可以通過getConstrcutor(Class... parameterTypes)獲得擁有特定引數的構造器物件。Constructor物件的一個主要方法是newInstance,通過該方法可以建立一個類的例項。
  2. Method:代表方法的類。通過Class物件的getDeclaredMethods方法可以獲得所有方法的陣列,Java 5以後的版本還可以通過getDeclaredMethod(String name, Class... parameterTypes)獲得特定簽名的方法,其中name是方法名,可變引數代表方法的引數列表。Method物件最重要的方法是invoke(Object obj, Object[] args),其中obj是呼叫該方法的目標物件,args是傳給方法的引數,這樣就可以呼叫指定物件的方法。此外,Method物件還包括以下重要方法:
    • Class getReturnType():獲取方法的返回型別。
    • Class[] getParameterTypes():獲取方法引數型別的陣列。
    • Class[] getExceptionTypes():獲取方法異常型別陣列。
    • Annotation[][] getParameterAnnotations():獲得方法引數註解資訊的陣列。
  3. Field:代表屬性的類。通過Class物件的getDeclaredFields()方法可以獲取類的屬性陣列。通過getDeclaredField(String name)則可以獲取某個特定名稱的屬性。Field物件最重要的方法是set(Object obj, Object value),其中obj是目標物件,而value是要賦給屬性的值。

除此之外,Java還提供了Package類用於包的反射,Java 5以後的版本還提供了AnnotationElement類用於註解的反射。總之,Java的反射機制保證了可以通過程式設計的方式訪問目標類或物件的所有元素,對於被private和protected訪問修飾符修飾的成員,只要JVM的安全機制允許,也可以通過反射進行呼叫。下面是一個反射的例子:

Car.java


  
  1. package com.lovo;
  2. public class Car {
  3. private String brand;
  4. private int currentSpeed;
  5. private int maxSpeed;
  6. public Car(String brand, int maxSpeed) {
  7. this.brand = brand;
  8. this.maxSpeed = maxSpeed;
  9. }
  10. public void run() {
  11. currentSpeed += 10;
  12. System.out.println(brand + " is running at " + currentSpeed + " km/h");
  13. if(currentSpeed > maxSpeed) {
  14. System.out.println( "It's dangerous!");
  15. }
  16. }
  17. }
CarTest.java

  
  1. package com.lovo;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.Method;
  5. public class CarTest {
  6. public static void main(String[] args) throws Exception {
  7. Constructor<Car> con = Car.class.getConstructor(String.class, int.class);
  8. Car myCar = (Car) con.newInstance( "Benz", 280);
  9. Method m = myCar.getClass().getDeclaredMethod( "run");
  10. for( int i = 0; i < 10; i++) {
  11. m.invoke(myCar);
  12. }
  13. Field f1 = myCar.getClass().getDeclaredField( "maxSpeed");
  14. Field f2 = myCar.getClass().getDeclaredField( "brand");
  15. f1.setAccessible( true);
  16. f1.set(myCar, 80);
  17. f2.setAccessible( true);
  18. f2.set(myCar, "QQ");
  19. m.invoke(myCar);
  20. }
  21. }
執行結果:



152. 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類其實並不困難,程式碼如下所示:


  
  1. package com.lovo;
  2. import java.util.Collections;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class MyThreadLocal<T> {
  6. private Map<Thread, T> map = Collections.synchronizedMap( new HashMap<Thread, T>());
  7. public void set(T newValue) {
  8. map.put(Thread.currentThread(), newValue);
  9. }
  10. public T get() {
  11. return map.get(Thread.currentThread());
  12. }
  13. public void remove() {
  14. map.remove(Thread.currentThread());
  15. }
  16. }

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

答:

  • 連線點:程式執行的某個特定位置(如:某個方法呼叫前、呼叫後,方法丟擲異常後)。一個類或一段程式程式碼擁有一些具有邊界性質的特定點,這些程式碼中的特定點就是連線點。Spring僅支援方法的連線點。
  • 切點:如果連線點相當於資料中的記錄,那麼切點相當於查詢條件,一個切點可以匹配多個連線點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連線點。
  • 增強:增強是織入到目標類連線點上的一段程式程式碼。Spring提供的增強介面都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多資料上將增強譯為“通知”,這明顯是個詞不達意的翻譯,讓很多程式設計師困惑了許久。
  • 引介:引介是一種特殊的增強,它為類新增一些屬性和方法。這樣,即使一個業務類原本沒有實現某個介面,通過引介功能,可以動態的未該業務類新增介面的實現邏輯,讓業務類成為這個介面的實現類。
  • 織入:織入是將增強新增到目標類具體連線點上的過程,AOP有三種織入方式:
    1. 編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc)
    2. 類裝載期織入:要求使用特殊的類載入器
    3. 動態代理織入:在執行時為目標類生成代理實現增強
Spring採用了動態代理織入,而AspectJ採用了編譯期織入和類裝載期織入的方式。
  • 切面:切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連線點的定義。

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


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


  
  1. package com.lovo;
  2. /**
  3. * 參考人員介面
  4. * @author 駱昊
  5. *
  6. */
  7. public interface Candidate {
  8. /**
  9. * 答題
  10. */
  11. public void answerTheQuestions();
  12. }


  
  1. package com.lovo;
  2. /**
  3. * 懶學生
  4. * @author 駱昊
  5. *
  6. */
  7. public class LazyStudent implements Candidate {
  8. private String name; // 姓名
  9. public LazyStudent(String name) {
  10. this.name = name;
  11. }
  12. @Override
  13. public void answerTheQuestions() {
  14. // 懶學生只能寫出自己的名字不會答題
  15. System.out.println( "姓名: " + name);
  16. }
  17. }


  
  1. package com.lovo;
  2. /**
  3. * 槍手
  4. * @author 駱昊
  5. *
  6. */
  7. public class Gunman implements Candidate {
  8. private Candidate target; // 被代理物件
  9. public Gunman(Candidate target) {
  10. this.target = target;
  11. }
  12. @Override
  13. public void answerTheQuestions() {
  14. // 槍手要寫上代考的學生的姓名
  15. target.answerTheQuestions();
  16. // 槍手要幫助懶學生答題並交卷
  17. System.out.println( "奮筆疾書正確答案");
  18. System.out.println( "交卷");
  19. }
  20. }


  
  1. package com.lovo;
  2. public class ProxyTest1 {
  3. public static void main(String[] args) {
  4. Candidate c = new Gunman( new LazyStudent( "王小二"));
  5. c.answerTheQuestions();
  6. }
  7. }

從JDK 1.3開始,Java提供了動態代理技術,允許開發者在執行時建立介面的代理例項,主要包括Proxy類和InvocationHandler介面。下面的例子使用動態代理為ArrayList編寫一個代理,在新增和刪除元素時,在控制檯列印新增或刪除的元素以及ArrayList的大小:


  
  1. package com.lovo;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.util.List;
  5. public class ListProxy<T> implements InvocationHandler {
  6. private List<T> target;
  7. public ListProxy(List<T> target) {
  8. this.target = target;
  9. }
  10. @Override
  11. public Object invoke(Object proxy, Method method, Object[] args)
  12. throws Throwable {
  13. Object retVal = null;
  14. System.out.println( "[" + method.getName() + ": " + args[ 0] + "]");
  15. retVal = method.invoke(target, args);
  16. System.out.println( "[size=" + target.size() + "]");
  17. return retVal;
  18. }
  19. }


  
  1. package com.lovo;
  2. import java.lang.reflect.Proxy;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. public class ProxyTest2 {
  6. @SuppressWarnings( "unchecked")
  7. public static void main(String[] args) {
  8. List<String> list = new ArrayList<String>();
  9. Class<?> clazz = list.getClass();
  10. ListProxy<String> myProxy = new ListProxy<String>(list);
  11. List<String> newList = (List<String>)
  12. Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), myProxy);
  13. newList.add( "apple");
  14. newList.add( "banana");
  15. newList.add( "orange");
  16. newList.remove( "banana");
  17. }
  18. }
程式執行結果:


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


154. Spring中自動裝配的方式有哪些?

答:

  • no:不進行自動裝配,手動設定Bean的依賴關係
  • byName:根據Bean的名字進行自動裝配
  • byType:根據Bean的型別進行自動裝配
  • constructor:類似於byType,不過是應用於構造器的引數,如果正好有一個Bean與構造器的引數型別相同則可以自動裝配,否則會導致錯誤
  • autodetect:如果有預設的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配
【注意】自動裝配沒有自定義裝配方式那麼精確,而且不能自動裝配簡單屬性(基本型別、字串等),在使用時應注意。


155. Spring中如何使用註解來配置Bean?有哪些相關的註解?

答:首先需要在Spring配置檔案中增加如下配置:

<context:component-scan base-package="org.example"/>
  
然後可以用@Component、@Controller、@Service、@Repository註解來標註需要由Spring IoC容器進行物件託管的類。


156. Spring支援的事務管理型別有哪些?你在專案中使用哪種方式?

答:Spring支援程式設計式事務管理和宣告式事務管理。許多Spring框架的使用者選擇宣告式事務管理,因為這種方式和應用程式的關聯較少,因此更加符合輕量級容器的概念。宣告式事務管理要優於程式設計式事務管理,儘管在靈活性方面它弱於程式設計式事務管理(程式設計式事務允許你通過程式碼控制業務)。


157. 如何在Web專案中配置Spring的IoC容器?

答:如果需要在Web專案中使用Spring的IoC容器,可以在Web專案配置檔案web.xml中做出如下配置:


  
  1. <context-param>
  2. <param-name>contextConfigLocation </param-name>
  3. <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml </param-value>
  4. </context-param>
  5. <listener>
  6. <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class>