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

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. public class Car {  
  4.     private String brand;  
  5.     private int currentSpeed;  
  6.     private int maxSpeed;  
  7.   
  8.     public Car(String brand, int maxSpeed) {  
  9.         this.brand = brand;  
  10.         this.maxSpeed = maxSpeed;  
  11.     }  
  12.       
  13.     public void run() {  
  14.         currentSpeed += 10;  
  15.         System.out.println(brand + ” is running at ” + currentSpeed + “ km/h”);  
  16.         if(currentSpeed > maxSpeed) {  
  17.             System.out.println(”It’s dangerous!”);  
  18.         }  
  19.     }  
  20.   
  21. }  
CarTest.java
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. import java.lang.reflect.Constructor;  
  4. import java.lang.reflect.Field;  
  5. import java.lang.reflect.Method;  
  6.   
  7. public class CarTest {  
  8.   
  9.     public static void main(String[] args) throws Exception {  
  10.         Constructor<Car> con = Car.class.getConstructor(String.classint.class);  
  11.         Car myCar = (Car) con.newInstance(”Benz”280);  
  12.         Method m = myCar.getClass().getDeclaredMethod(”run”);  
  13.         for(int i = 0; i < 10; i++) {  
  14.             m.invoke(myCar);  
  15.         }  
  16.         Field f1 = myCar.getClass().getDeclaredField(”maxSpeed”);  
  17.         Field f2 = myCar.getClass().getDeclaredField(”brand”);  
  18.         f1.setAccessible(true);  
  19.         f1.set(myCar, 80);  
  20.         f2.setAccessible(true);  
  21.         f2.set(myCar, ”QQ”);  
  22.         m.invoke(myCar);  
  23.     }  
  24. }  
運行結果:



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

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.HashMap;  
  5. import java.util.Map;  
  6.   
  7. public class MyThreadLocal<T> {  
  8.     private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());  
  9.       
  10.     public void set(T newValue) {  
  11.         map.put(Thread.currentThread(), newValue);  
  12.     }  
  13.       
  14.     public T get() {  
  15.         return map.get(Thread.currentThread());  
  16.     }  
  17.       
  18.     public void remove() {  
  19.         map.remove(Thread.currentThread());  
  20.     }  
  21. }  

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語句獲得真實對象。代理模式的類圖如下所示:


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

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. /** 
  4.  * 參考人員接口 
  5.  */  
  6. public interface Candidate {  
  7.       
  8.     /** 
  9.      * 答題 
  10.      */  
  11.     public void answerTheQuestions();  
  12. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. /** 
  4.  * 懶學生 
  5.  */  
  6. public class LazyStudent implements Candidate {  
  7.     private String name;        // 姓名  
  8.       
  9.     public LazyStudent(String name) {  
  10.         this.name = name;  
  11.     }  
  12.       
  13.     @Override  
  14.     public void answerTheQuestions() {  
  15.         // 懶學生只能寫出自己的名字不會答題  
  16.         System.out.println(”姓名: ” + name);  
  17.     }  
  18.   
  19. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. /** 
  4.  * 槍手 
  5.  * 
  6.  */  
  7. public class Gunman implements Candidate {  
  8.     private Candidate target;   // 被代理對象  
  9.       
  10.     public Gunman(Candidate target) {  
  11.         this.target = target;  
  12.     }  
  13.       
  14.     @Override  
  15.     public void answerTheQuestions() {  
  16.         // 槍手要寫上代考的學生的姓名  
  17.         target.answerTheQuestions();  
  18.         // 槍手要幫助懶學生答題並交卷  
  19.         System.out.println(”奮筆疾書正確答案”);  
  20.         System.out.println(”交卷”);  
  21.     }  
  22.   
  23. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. public class ProxyTest1 {  
  4.   
  5.     public static void main(String[] args) {  
  6.         Candidate c = new Gunman(new LazyStudent(“王小二”));  
  7.         c.answerTheQuestions();  
  8.     }  
  9. }  

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

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.util.List;  
  6.   
  7. public class ListProxy<T> implements InvocationHandler {  
  8.     private List<T> target;  
  9.       
  10.     public ListProxy(List<T> target) {  
  11.         this.target = target;  
  12.     }  
  13.       
  14.     @Override  
  15.     public Object invoke(Object proxy, Method method, Object[] args)  
  16.             throws Throwable {  
  17.         Object retVal = null;  
  18.         System.out.println(”[“ + method.getName() + “: ” + args[0] + “]”);  
  19.         retVal = method.invoke(target, args);  
  20.         System.out.println(”[size=” + target.size() + “]”);  
  21.         return retVal;  
  22.     }  
  23.   
  24. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo;  
  2.   
  3. import java.lang.reflect.Proxy;  
  4. import java.util.ArrayList;  
  5. import java.util.List;  
  6.   
  7. public class ProxyTest2 {  
  8.   
  9.     @SuppressWarnings(“unchecked”)  
  10.     public static void main(String[] args) {  
  11.         List<String> list = new ArrayList<String>();  
  12.         Class<?> clazz = list.getClass();  
  13.         ListProxy<String> myProxy = new ListProxy<String>(list);  
  14.         List<String> newList = (List<String>)   
  15.                 Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), myProxy);  
  16.         newList.add(”apple”);  
  17.         newList.add(”banana”);  
  18.         newList.add(”orange”);  
  19.         newList.remove(”banana”);  
  20.     }  
  21. }  
程序運行結果:


使用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配置文件中增加如下配置:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <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中做出如下配置:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  6. <listener>  
  7.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  8. </listener>  


158. 如何在Web項目中配置Spring MVC?

答:要使用Spring MVC需要在Web項目配置文件中配置其前端控制器DispatcherServlet,如下所示:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <web-app>  
  2.   
  3.     <servlet>  
  4.         <servlet-name>example</servlet-name>  
  5.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  6.         <load-on-startup>1</load-on-startup>  
  7.     </servlet>  
  8.   
  9.     <servlet-mapping>  
  10.         <servlet-name>example</servlet-name>  
  11.         <url-pattern>*.html</url-pattern>  
  12.     </servlet-mapping>  
  13.   
  14. </web-app>  

【注意】上面的配置中使用了*.html的後綴映射,這樣做一方面不能夠通過URL推斷採用了何種服務器端的技術,另一方面可以欺騙搜索引擎,因爲搜索引擎不會搜索動態頁面,這種做法可以稱爲僞靜態化。


159. Spring MVC的工作原理是怎樣的?

答:Spring MVC的工作原理如下圖所示:


  1. 客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責調用系統的其他模塊來真正處理用戶的請求。
  2. DispatcherServlet收到請求後,將根據請求的信息(包括URL、HTTP協議方法、請求頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個對象都可以作爲請求的Handler)。當然,在這個地方Spring會通過HandlerAdapter對該處理器進行封裝,HandlerAdapter是一個適配器,它用統一的接口對各種Handler中的方法進行調用。
  3. Handler完成對用戶請求的處理後,會返回一個ModelAndView對象給DispatcherServlet,ModelAndView顧名思義,包含了數據模型以及相應的視圖的信息。當然,這裏的視圖是邏輯視圖,DispatcherServlet還要藉助ViewResolver完成從邏輯視圖到真實視圖對象的解析工作。
  4. 當得到真正的視圖對象後,Dispatcher會利用視圖對象對模型數據進行渲染。
  5. 客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字符串,還可以是一張圖片或者一個PDF文件。

160. 如何在Spring IoC容器中配置數據源?

答:

  • DBCP配置:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <bean id=“dataSource”  
  2.         class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”>  
  3.     <property name=“driverClassName” value=“${jdbc.driverClassName}”/>  
  4.     <property name=“url” value=“${jdbc.url}”/>  
  5.     <property name=“username” value=“${jdbc.username}”/>  
  6.     <property name=“password” value=“${jdbc.password}”/>  
  7. </bean>  
  8.   
  9. <context:property-placeholder location=“jdbc.properties”/>  
  • C3P0配置:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <bean id=“dataSource”  
  2.         class=“com.mchange.v2.c3p0.ComboPooledDataSource” destroy-method=“close”>  
  3.     <property name=“driverClass” value=“${jdbc.driverClassName}”/>  
  4.     <property name=“jdbcUrl” value=“${jdbc.url}”/>  
  5.     <property name=“user” value=“${jdbc.username}”/>  
  6.     <property name=“password” value=“${jdbc.password}”/>  
  7. </bean>  
  8.   
  9. <context:property-placeholder location=“jdbc.properties”/>  


161. 如何配置配置事務增強?

答:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version=“1.0” encoding=“UTF-8”?>  
  2. <beans xmlns=“http://www.springframework.org/schema/beans”  
  3.      xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  
  4.      xmlns:aop=“http://www.springframework.org/schema/aop”  
  5.      xmlns:tx=“http://www.springframework.org/schema/tx”  
  6.      xsi:schemaLocation=”  
  7.      http://www.springframework.org/schema/beans  
  8.      http://www.springframework.org/schema/beans/spring-beans.xsd  
  9.      http://www.springframework.org/schema/tx  
  10.      http://www.springframework.org/schema/tx/spring-tx.xsd  
  11.      http://www.springframework.org/schema/aop  
  12.      http://www.springframework.org/schema/aop/spring-aop.xsd”>  
  13.   
  14.   <!– this is the service object that we want to make transactional –>  
  15.   <bean id=“fooService” class=“x.y.service.DefaultFooService”/>  
  16.   
  17.   <!– the transactional advice (what ‘happens’; see the <aop:advisor/> bean below) –>  
  18.   <tx:advice id=“txAdvice” transaction-manager=“txManager”>  
  19.   <!– the transactional semantics… –>  
  20.   <tx:attributes>  
  21.     <!– all methods starting with ‘get’ are read-only –>  
  22.     <tx:method name=“get*” read-only=“true”/>  
  23.     <!– other methods use the default transaction settings (see below) –>  
  24.     <tx:method name=“*”/>  
  25.   </tx:attributes>  
  26.   </tx:advice>  
  27.   
  28.   <!– ensure that the above transactional advice runs for any execution  
  29.     of an operation defined by the FooService interface –>  
  30.   <aop:config>  
  31.   <aop:pointcut id=“fooServiceOperation” expression=“execution(* x.y.service.FooService.*(..))”/>  
  32.   <aop:advisor advice-ref=“txAdvice” pointcut-ref=“fooServiceOperation”/>  
  33.   </aop:config>  
  34.   
  35.   <!– don’t forget the DataSource –>  
  36.   <bean id=“dataSource” class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”>  
  37.   <property name=“driverClassName” value=“oracle.jdbc.driver.OracleDriver”/>  
  38.   <property name=“url” value=“jdbc:oracle:thin:@rj-t42:1521:elvis”/>  
  39.   <property name=“username” value=“scott”/>  
  40.   <property name=“password” value=“tiger”/>  
  41.   </bean>  
  42.   
  43.   <!– similarly, don’t forget the PlatformTransactionManager –>  
  44.   <bean id=“txManager” class=“org.springframework.jdbc.datasource.DataSourceTransactionManager”>  
  45.   <property name=“dataSource” ref=“dataSource”/>  
  46.   </bean>  
  47.   
  48.   <!– other <bean/> definitions here –>  
  49.   
  50. </beans>  


162. 選擇使用Spring框架的原因(Spring框架爲企業級開發帶來的好處)?

答:可以從以下幾個方面作答:

  1. 非侵入式:支持基於POJO的編程模式,不強制性的要求實現Spring框架中的接口或繼承Spring框架中的類。
  2. IoC容器:IoC容器幫助應用程序管理對象以及對象之間的依賴關係,對象之間的依賴關係如果發生了改變只需要修改配置文件而不是修改代碼,因爲代碼的修改可能意味着項目的重新構建和完整的迴歸測試。有了IoC容器,程序員再也不需要自己編寫工廠、單例,這一點特別符合Spring的精神“不要重複的發明輪子”。
  3. AOP:面向切面編程,將所有的橫切關注功能封裝到切面(aspect)中,通過配置的方式將橫切關注功能動態添加到目標代碼上,進一步實現了業務邏輯和系統服務之間的分離。另一方面,有了AOP程序員可以省去很多自己寫代理類的工作。
  4. MVC:Spring的MVC框架是非常優秀的,從各個方面都可以甩Struts 2幾條街,爲Web表示層提供了更好的解決方案。
  5. 事務管理:Spring以寬廣的胸懷接納多種持久層技術(我想這一點與Rod Johnson音樂學博士的身份不無關係,這一點特別值得Gavin King學習,上天不可能賦予一個人太多的優點以至於他沒有表達謙虛的餘地。關於這二位的軼事可以自己百度一下),並且爲其提供了聲明式的事務管理,在不需要任何一行代碼的情況下就能夠完成事務管理。
  6. 其他:選擇Spring框架的原因還遠不止於此,Spring爲Java企業級開發提供了一站式選擇,你可以在需要的時候使用它的部分和全部,更重要的是,你甚至可以在感覺不到Spring存在的情況下,在你的項目中使用Spring提供的各種優秀的功能。

163. 依賴注入的方式以及你在項目中的選擇?

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


164. 提供Spring IoC容器配置元數據的方式?

答:

  1. 基於XML文件進行配置。
  2. 基於註解進行配置。
  3. 基於Java程序進行配置(Spring 3+)
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo.bean;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.stereotype.Component;  
  5.   
  6. @Component  
  7. public class Person {  
  8.     private String name;  
  9.     private int age;  
  10.     @Autowired  
  11.     private Car car;  
  12.       
  13.     public Person(String name, int age) {  
  14.         this.name = name;  
  15.         this.age = age;  
  16.     }  
  17.   
  18.     public void setCar(Car car) {  
  19.         this.car = car;  
  20.     }  
  21.   
  22.     @Override  
  23.     public String toString() {  
  24.         return “Person [name=” + name + “, age=” + age + “, car=” + car + “]”;  
  25.     }  
  26.   
  27. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo.bean;  
  2.   
  3. import org.springframework.stereotype.Component;  
  4.   
  5. @Component  
  6. public class Car {  
  7.     private String brand;  
  8.     private int maxSpeed;  
  9.   
  10.     public Car(String brand, int maxSpeed) {  
  11.         this.brand = brand;  
  12.         this.maxSpeed = maxSpeed;  
  13.     }  
  14.   
  15.     @Override  
  16.     public String toString() {  
  17.         return “Car [brand=” + brand + “, maxSpeed=” + maxSpeed + “]”;  
  18.     }  
  19.   
  20. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo.config;  
  2.   
  3. import org.springframework.context.annotation.Bean;  
  4. import org.springframework.context.annotation.Configuration;  
  5.   
  6. import com.lovo.bean.Car;  
  7. import com.lovo.bean.Person;  
  8.   
  9. @Configuration  
  10. public class AppConfig {  
  11.   
  12.     @Bean  
  13.     public Car car() {  
  14.         return new Car(“Benz”320);  
  15.     }  
  16.       
  17.     @Bean  
  18.     public Person person() {  
  19.         return new Person(“Somnus”34);  
  20.     }  
  21. }  

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.lovo.test;  
  2.   
  3. import org.springframework.context.ConfigurableApplicationContext;  
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  5.   
  6. import com.lovo.bean.Person;  
  7. import com.lovo.config.AppConfig;  
  8.   
  9. class Test {  
  10.   
  11.     public static void main(String[] args) {  
  12.         // TWR (Java 7+)  
  13.         try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class)) {  
  14.             Person person = factory.getBean(Person.class);  
  15.             System.out.println(person);  
  16.         }  
  17.     }  
  18. }  

165. Spring提供了哪些Bean的作用域?

答:

  • singleton:Bean以單例的方式存在(IoC容器中僅存在該Bean的唯一實例)。
  • prototype:每次從容器中獲取Bean的實例時,都會返回一個新的實例(原型模式)。
  • request:每次HTTP請求都會創建新的實例,該作用域僅適用於WebApplicationContext環境。
  • session:同一個HTTP會話共享一個Bean的實例,不同的HTTP會話使用不同的Bean實例,該作用域僅適用於WebApplicationContext環境。
  • globalSession:同一個全局會話共享一個Bean的實例,一般應用於Portlet應用中。


166. 闡述Spring框架中Bean的生命週期?

答:

  1. Spring IoC容器找到關於Bean的定義並實例化該Bean。
  2. Spring IoC容器對Bean進行依賴注入。
  3. 如果Bean實現了BeanNameAware接口,則將該Bean的id傳給setBeanName方法。
  4. 如果Bean實現了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法。
  5. 如果Bean實現了BeanPostProcessor接口,則調用其postProcessBeforeInitialization方法。
  6. 如果Bean實現了InitializingBean接口,則調用其afterPropertySet方法。
  7. 如果有和Bean關聯的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調用。
  8. 當銷燬Bean實例時,如果Bean實現了DisposableBean接口,則調用其destroy方法。


167. 依賴注入時如何注入集合屬性?

答:可以在定義Bean屬性時,通過<list>/<set>/<map>/<props>分別爲其注入列表、集合、映射和鍵值都是字符串的映射屬性。


168. Spring中的自動裝配有哪些限制?

答:

  1. 如果使用了構造器注入或者setter注入,那麼將覆蓋自動裝配的依賴關係。
  2. 基本數據類型的值、字符串字面量、類字面量無法使用自動裝配來注入。
  3. 有先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。


169. 和自動裝配相關的註解有哪些?

答:

  • @Required:該依賴關係必須裝配(手動或自動裝配),否則將拋出BeanInitializationException異常。
  • @Autowired:自動裝配,默認按類型進行自動裝配。
  • @Qualifier:如果按類型自動裝配時有不止一個匹配的類型,那麼可以使用該註解指定名字來消除歧義。


170. 如何使用HibernateDaoSupport整合Spring和Hibernate?

答:

  1. 在Spring中配置Hibernate的會話工廠(LocalSessionFactoryBean或AnnotationSessionFactoryBean)。
  2. 讓DAO的實現類繼承HibernateDaoSupport(繼承getHibernateTemplate方法來調用模板方法)。
  3. 讓Spring來管理Hibernate的事務(推薦使用聲明式事務)。


171. 你是如何理解“橫切關注”這個概念的?

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


172. 如何理解Spring AOP中Advice這個概念?

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


173. 在Web項目中如何獲得Spring的IoC容器?

答:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);  


174. Spring MVC如何對RESTful風格提供支持?

答:如果不瞭解RESTful可以看看百度百科的講解,關於這個問題,可以看看blogjava上的另一個帖子。


175. 大型網站在架構上應當考慮哪些問題?

答:

  • 分層:分層是處理任何複雜系統最常見的手段之一,將系統橫向切分成若干個層面,每個層面只承擔單一的職責,然後通過下層爲上層提供的基礎設施和服務以及上層對下層的調用來形成一個完整的複雜的系統。計算機網絡的開放系統互聯參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結構,大型網站的軟件系統也可以使用分層的理念將其分爲持久層(提供數據存儲和訪問服務)、業務層(處理業務邏輯,系統中最核心的部分)和表示層(系統交互、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分,在物理上可以位於同一設備上也可以在不同的設備上部署不同的功能模塊,這樣可以使用更多的計算資源來應對用戶的併發訪問;(2)層與層之間應當有清晰的邊界,這樣分層纔有意義,才更利於軟件的開發和維護。
  • 分割:分割是對軟件的縱向切分。我們可以將大型網站的不同功能和服務分割開,形成高內聚低耦合的功能模塊(單元)。在設計初期可以做一個粗粒度的分割,將網站分割爲若干個功能模塊,後期還可以進一步對每個模塊進行細粒度的分割,這樣一方面有助於軟件的開發和維護,另一方面有助於分佈式的部署,提供網站的併發處理能力和功能的擴展。
  • 分佈式:除了上面提到的內容,網站的靜態資源(JavaScript、CSS、圖片等)也可以採用獨立分佈式部署並採用獨立的域名,這樣可以減輕應用服務器的負載壓力,也使得瀏覽器對資源的加載更快。數據的存取也應該是分佈式的,傳統的商業級關係型數據庫產品基本上都支持分佈式部署,而新生的NoSQL產品幾乎都是分佈式的。當然,網站後臺的業務處理也要使用分佈式技術,例如查詢索引的構建、數據分析等,這些業務計算規模龐大,可以使用Hadoop以及MapReduce分佈式計算框架來處理。
  • 集羣:集羣使得有更多的服務器提供相同的服務,可以更好的提供對併發的支持。
  • 緩存:所謂緩存就是用空間換取時間的技術,將數據儘可能放在距離計算最近的位置。使用緩存是網站優化的第一定律。我們通常說的CDN、反向代理、熱點數據都是對緩存技術的使用。
  • 異步:異步是實現軟件實體之間解耦合的又一重要手段。異步架構是典型的生產者消費者模式,二者之間沒有直接的調用關係,只要保持數據結構不變,彼此功能實現可以隨意變化而不互相影響,這對網站的擴展非常有利。使用異步處理還可以提高系統可用性,加快網站的響應速度(用Ajax加載數據就是一種異步技術),同時還可以起到削峯作用(應對瞬時高併發)。“能推遲處理的都要推遲處理”是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。
  • 冗餘:各種服務器都要提供相應的冗餘服務器以便在某臺或某些服務器宕機時還能保證網站可以正常工作,同時也提供了災難恢復的可能性。冗餘是網站高可用性的重要保證。


176. 你用過的網站前端優化的技術有哪些?

答:

  1. 瀏覽器訪問優化
    • 減少HTTP請求數量:合併CSS、合併JavaScript、合併圖片(CSS Sprite)
    • 使用瀏覽器緩存:通過設置HTTP響應頭中的Cache-Control和Expires屬性,將CSS、JavaScript、圖片等在瀏覽器中緩存,當這些靜態資源需要更新時,可以更新HTML文件中的引用來讓瀏覽器重新請求新的資源
    • 啓用壓縮
    • CSS前置,JavaScript後置
    • 減少Cookie傳輸
  2. CDN加速:CDN(Content Distribute Network)的本質仍然是緩存,將數據緩存在離用戶最近的地方,CDN通常部署在網絡運營商的機房,不僅可以提升響應速度,還可以減少應用服務器的壓力。當然,CDN緩存的通常都是靜態資源。
  3. 反向代理:反向代理相當於應用服務器的一個門面,可以保護網站的安全性,也可以實現負載均衡的功能,當然最重要的是它緩存了用戶訪問的熱點資源,可以直接從反向代理將某些內容返回給用戶瀏覽器。


177. 你使用過的應用服務器優化技術有哪些?

答:

  1. 分佈式緩存:緩存的本質就是內存中的哈希表,如果設計一個優質的哈希函數,那麼理論上哈希表讀寫的漸近時間複雜度爲O(1)。緩存主要用來存放那些讀寫比很高、變化很少的數據,這樣應用程序讀取數據時先到緩存中讀取,如果沒有或者數據已經失效再去訪問數據庫或文件系統,並根據擬定的規則將數據寫入緩存。對網站數據的訪問也符合二八定律(Pareto分佈,冪律分佈),即80%的訪問都集中在20%的數據上,如果能夠將這20%的數據緩存起來,那麼系統的性能將得到顯著的改善。當然,使用緩存需要解決以下幾個問題:(1)頻繁修改的數據;(2)數據不一致與髒讀;(3)緩存雪崩(可以採用分佈式緩存服務器集羣加以解決,Memcached是廣泛採用的解決方案,它是一種互不通信的集中式管理的分佈式緩存方案,可以從http://memcached.org/瞭解到關於Memcached的相關信息);(4)緩存預熱;(5)緩存穿透(惡意持續請求不存在的數據)。
  2. 異步操作:可以使用消息隊列將調用異步化,通過異步處理將短時間高併發產生的事件消息存儲在消息隊列中,從而起到削峯作用。電商網站在進行促銷活動時,可以將用戶的訂單請求存入消息隊列,這樣可以抵禦大量的併發訂單請求對系統和數據庫的衝擊。目前,絕大多數的電商網站即便不進行促銷活動,訂單系統都採用了消息隊列來處理。
  3. 使用集羣
  4. 代碼優化
    • 多線程:基於Java的Web開發基本上都通過多線程的方式響應用戶的併發請求,使用多線程技術在編程上要解決線程安全問題,主要可以考慮以下幾個方面:
      • 將對象設計爲無狀態對象(這和麪向對象的編程觀點是矛盾的,在面向對象的世界中被視爲不良設計),這樣就不會存在併發訪問時對象狀態不一致的問題。
      • 在方法內部創建對象,這樣對象由進入方法的線程創建,不會出現多個線程訪問同一對象的問題。使用ThreadLocal將對象與線程綁定也是很好的做法,這一點在前面已經探討過了。
      • 對資源進行併發訪問時應當使用合理的鎖機制。
    • 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認的比多線程的方式更能充分發揮服務器性能的應用模式,基於Node.js構建的服務器就採用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規範中又引入了異步Servlet的概念,這些都爲在服務器端採用非阻塞I/O提供了必要的基礎。
    • 資源複用:資源複用主要有兩種方式,一是單例,而是對象池,我們使用的數據庫連接池、線程池都是對象池化技術,這是典型的用空間換取時間的策略,另一方面也實現對資源的複用,從而避免了不必要的創建和釋放資源所帶來的開銷。


178. 什麼是XSS攻擊?什麼是SQL注入攻擊?什麼是CSRF攻擊?

答:

  • XSS(Cross Site Script,跨站腳本攻擊)是向網頁中注入惡意腳本在用戶瀏覽網頁時在用戶瀏覽器中執行惡意腳本的攻擊方式。跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點擊一個嵌入惡意腳本的鏈接以達到攻擊的目標,目前有很多攻擊者利用論壇、微博發佈含有惡意腳本的URL就屬於這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網站的數據庫中,用戶瀏覽網頁時,惡意腳本從數據庫中被加載到頁面執行,QQ郵箱的早期版本就曾經被利用作爲持久型跨站腳本攻擊的平臺)。XSS雖然不是什麼新鮮玩意,但是攻擊的手法卻不斷翻新,防範XSS主要有兩方面:消毒(對危險字符進行轉義)和HttpOnly(防範XSS攻擊者竊取Cookie數據)。
  • SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實施OS注入攻擊導致的)),當服務器使用請求參數構造SQL語句時,惡意的SQL被嵌入到SQL中交給數據庫執行。SQL注入攻擊需要攻擊者對數據庫結構有所瞭解才能進行,攻擊者想要獲得表結構有多種方式:(1)如果使用開源系統搭建網站,數據庫結構也是公開的(目前有很多現成的系統可以直接搭建論壇,電商網站,雖然方便快捷但是風險是必須要認真評估的);(2)錯誤回顯(如果將服務器的錯誤信息直接顯示在頁面上,攻擊者可以通過非法參數引發頁面錯誤從而通過錯誤信息瞭解數據庫結構,Web應用應當設置友好的錯誤頁,一方面符合最小驚訝原則,一方面屏蔽掉可能給系統帶來危險的錯誤回顯信息);(3)盲注。防範SQL注入攻擊也可以採用消毒的方式,通過正則表達式對請求參數進行驗證,此外,參數綁定也是很好的手段,這樣惡意的SQL會被當做SQL的參數而不是命令被執行,JDBC中的PreparedStatement就是支持參數綁定的語句對象,從性能和安全性上都明顯優於Statement。
  • CSRF攻擊(Cross Site Request Forgery,跨站請求僞造)是攻擊者通過跨站請求,以合法的用戶身份進行非法操作(如轉賬或發帖等)。CSRF的原理是利用瀏覽器的Cookie或服務器的Session,盜取用戶身份,其原理如下圖所示。防範CSRF的主要手段是識別請求者的身份,主要有以下幾種方式:(1)在表單中添加令牌(token);(2)驗證碼;(3)檢查請求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)。令牌和驗證都具有一次消費性的特徵,因此在原理上一致的,但是驗證碼是一種糟糕的用戶體驗,不是必要的情況下不要輕易使用驗證碼,目前很多網站的做法是如果在短時間內多次提交一個表單未獲得成功後纔要求提供驗證碼,這樣會獲得較好的用戶體驗。

【補充】防火牆的架設是Web安全的重要保障,ModSecurity是開源的Web防火牆中的佼佼者,有興趣的可以在http://www.modsecurity.org/網站獲得相關信息。企業級防火牆的架設應當有兩級防火牆,Web服務器和部分應用服務器可以架設在兩級防火牆之間的DMZ,而數據和資源服務器應當架設在第二級防火牆之後,如下圖所示。


179. 什麼是領域模型(domain model)?貧血模型(anaemic domain model)  

和充血模型(rich domain model)有什麼區別?

答:領域模型是領域內的概念類或現實世界中對象的可視化表示,又稱爲概念模型或分析對象模型,它專注於分析問題領域本身,發掘重要的業務領域概念,並建立業務領域概念之間的關係。貧血模型是指使用的領域對象中只有setter和getter方法(POJO),所有的業務邏輯都不包含在領域對象中而是放在業務邏輯層。有人將我們這裏說的貧血模型進一步劃分成失血模型(領域對象完全沒有業務邏輯)和貧血模型(領域對象有少量的業務邏輯),我們這裏就不對此加以區分了。充血模型將大多數業務邏輯和持久化放在領域對象中,業務邏輯(業務門面)只是完成對業務邏輯的封裝、事務和權限等的處理。下面兩張圖分別展示了貧血模型和充血模型的分層架構。
貧血模型

充血模型

貧血模型下組織領域邏輯通常使用事務腳本模式,讓每個過程對應用戶可能要做的一個動作,每個動作由一個過程來驅動。也就是說在設計業務邏輯接口的時候,每個方法對應着用戶的一個操作,這種模式有以下幾個有點:
  1. 它是一個大多數開發者都能夠理解的簡單過程模型(適合國內的絕大多數開發者)。
  2. 它能夠與一個使用行數據入口或表數據入口的簡單數據訪問層很好的協作。
  3. 事務邊界的顯而易見,一個事務開始於腳本的開始,終止於腳本的結束,很容易通過代理(或切面)實現聲明式事務。
然後,事務腳本模式的缺點也是很多的,隨着領域邏輯複雜性的增加,系統的複雜性將迅速增加,程序結構將變得極度混亂。開源中國社區上有一篇很好的譯文《貧血領域模型是如何導致糟糕的軟件產生》對這個問題做了比較細緻的闡述。

180. 談一談測試驅動開發(TDD)的好處以及你的理解。
答:TDD是指在編寫真正的功能實現代碼之前先寫測試代碼,然後根據需要重構實現代碼。在JUnit的作者Kent Beck的大作《測試驅動開發:實戰與模式解析》(Test-Driven Development: by Example)一書中有這麼一段內容:“消除恐懼和不確定性是編寫測試驅動代碼的重要原因”。因爲編寫代碼時的恐懼會讓你小心試探,讓你迴避溝通,讓你羞於得到反饋,讓你變得焦躁不安,而TDD是消除恐懼、讓Java開發者更加自信更加樂於溝通的重要手段。TDD會帶來的好處可能不會馬上呈現,但是你在某個時候一定會發現,這些好處包括:
  1. 更清晰的代碼 — 只寫需要的代碼
  2. 更好的設計
  3. 更出色的靈活性 — 鼓勵程序員面向接口編程
  4. 更快速的反饋 — 不會到系統上線時才知道bug的存在
【補充:】敏捷軟件開發的概念已經有很多年了,而且也部分的改變了軟件開發這個行業,TDD也是敏捷開發所倡導的。

TDD可以在多個層級上應用,包括單元測試(測試一個類中的代碼)、集成測試(測試類之間的交互)、系統測試(測試運行的系統)和系統集成測試(測試運行的系統包括使用的第三方組件)。TDD的實施步驟是:紅(失敗測試) — 綠(通過測試) — 重構。關於實施TDD的詳細步驟請參考另一篇文章《測試驅動開發之初窺門徑》。

在使用TDD開發時,經常會遇到需要被測對象需要依賴其他子系統的情況,但是你希望將測試代碼跟依賴項隔離,以保證測試代碼僅僅針對當前被測對象或方法展開,這時候你需要的是測試替身。測試替身可以分爲四類:

  1. 虛設替身:只傳遞但是不會使用到的對象,一般用於填充方法的參數列表
  2. 存根替身:總是返回相同的預設響應,其中可能包括一些虛設狀態
  3. 僞裝替身:可以取代真實版本的可用版本(比真實版本還是會差很多)
  4. 模擬替身:可以表示一系列期望值的對象,並且可以提供預設響應
Java世界中實現模擬替身的第三方工具非常多,包括EasyMock、Mockito、jMock等。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章