Java explore

1.Java字節代碼的操縱
     在通常的Java應用開發過程當中,開發人員使用Java的方式比較簡單。打開慣用的IDE, 編寫Java源代碼,再利用IDE提供的功能直接運行Java 程序就能夠了。這種開發模式 背後的過程是:開發人員編寫的是Java源代碼文件(.java),IDE會負責調用Java的編 譯器把Java源代碼編譯成平臺無關的字節代碼(byte code),以類文件的形式保存在
磁盤上(.class)。Java虛擬機(JVM)會負責把Java字節代碼加載並執行。Java經過這 種方式來實現其  「編寫一次,處處運行(Write once, run anywhere)」 的目標。Java 類文件中包含的字節代碼能夠被不一樣平臺上的JVM所使用。Java字節代碼不只能夠以 文件形式存在於磁盤上,也能夠經過網絡方式來下載,  還能夠只存在於內存中。JVM
中的類加載器會負責從包含字節代碼的字節數組(byte[])中定義出Java類。在某些 狀況下,可能會須要動態的生成  Java字節代碼,或是對已有的Java字節代碼進行修 改。

1.1動態編譯Java源文件:
     在通常狀況下,開發人員都是在程序運行以前就編寫完成了所有的Java源代碼而且 成功編譯。對有些應用來講,Java源代碼的內容在運行時刻才能肯定。這個時候就 須要動態編譯源代碼來生成Java字節代碼,再由JVM來加載執行。 Java源代碼的內容在運行時刻才能肯定。

    可使用JDK6自帶的API、com.sum.tools.java.Main、Eclipse JDT Core提供的編譯器、


1.2Java字節代碼加強:
    在Java字節代碼生成以後,對其進行修改,加強其功能。
    (至關於對應用程序的二進制文件進行修改

    Java字節代碼加強一般與Java源文件中的註解(annotation)一塊使用。
    (註解在Java源代碼中聲明須要加強的行爲及相關的元數據據,由框架在運行時刻完成對字節代碼加強)

    能夠用在:集中減小冗餘代碼和對開發人員屏蔽底層的實現細節上。
    (JavaBeans中的getter/setter,經過字節代碼加強,只須要聲明Bean中的屬性便可,getter/setter方法能夠經過修改字節代碼來自動添加。)
    (JPA,在調試程序的時候,會發現實體類中被添加了一些額外的域或方法。這些內容是在運行時刻由JPA實現動態添加的。)
    (面向方面編程AOP中也有使用。)

    Java類或接口的字節代碼組織形式:

    使用類庫:ASM、cglib、serp、BCEL等。

    對類文件進行加強的時機是須要在Java源代碼編譯以後,在JVM執行以前:
  • 由IDE在完成編譯操做以後執行。如Google App Engine 的Eclipse 插件會在編譯以後運行DataNucleus 來實現對實體類進行加強。
  • 在構建過程當中完成,好比經過Ant 或 Maven 來執行相關操做。
  • 實現本身的類加載器。當獲取到Java 類的字節代碼以後,先進行加強處理,再從修改過的字節代碼中定義出Java類。
  • 經過JDK5 引入的Java.lang.instrument 包來完成

1.3 java.lang.instrument 基本思路:
          在JVM 啓動時候添加一些代理(agent)。每一個代理是一個jar 包,其清單(manifest)文件中會指定一個代           理類。這個類包含一個permain 方法。JVM 在啓動的時候會首先執行代理類的premain 方法,在執行Java 程序自己main 方法。在premian 方法中就能夠對程序自己的字節代碼進行修改。

    JDK6 中還容許JVM 啓動後進行動態代理。

    (java.lang.instrument 支持兩種修改場景,一種是重定義一個Java類,即徹底替代一個Java 類的字節碼;另外一種是轉換已有的Java 類,至關於前面提到的類字節代碼加強。)

1.4總結:
    Java字節碼:能夠很容易的對二進制分發的Java 程序進行修改,很是適合於性能分析、調試跟蹤和日記記錄等任務。另一個很是重要的做用是把開發人員從繁瑣的Java 語法中解放出來。開發人員應該只須要負責編寫與業務邏輯相關的重要代碼。對於那些只是由於語法要求而添加的,或是模式固定的代碼,徹底能夠將其字節代碼動態生成出來。字節代碼加強和源代碼生成是不一樣的概念。  源代碼生成以後,就已經成爲了程序的一部分,開發人員須要去維護它:要麼手工修改生成出來的源代碼,要麼從新生成。而字節代碼的加強過程,對於開發人員是徹底透明的。
    

2.Java類的加載、連接和初始化
    Java 字節碼的表現是字節數組(byte[]),而Java 類在JVM中表現是 java.lang.Class類的對象。
    
2.1Java 類加載器
    Java 類的加載是由類加載器來完成的。

    類加載器分紅兩類:
    啓動類加載器(bootstrap)和用戶自定義加載器(user-defined)。
    二者的區別在於啓動類加載器是由JVM 的原生代碼實現的,而用戶自定義的類加載器都是繼承java.lang.ClassLoader 類。

     類加載器須要完成的最終功能是定義一個Java 類,即把java 字節碼轉換成JVM 中的java.lang.Class類對象。

    層次組織結構和代理模式。
    (因爲代理模式的存在,啓動一個類的加載過程的類加載器和最終定義這個類的類加載器可能並非一個,前者稱爲初始化類加載器,後者稱爲定義類加載器。二者的關聯在於:一個Java 類的定義類加載器是該類所導入的其它Java 類的初始化類加載器。好比類A 經過import 導入類B ,那麼由類A 的定義加載器負責啓動類B 的加載器過程。)

    通常類加載器會先代理給其父類加載器,當父類加載器找不到的時候,纔會本身加載。
    (這個邏輯封裝在java.lang.ClassLoader 類的 loadClass()方法中。)     

    Tomcat爲每一個Web應用都提供一個獨立的類加載器,使用的就是本身優先加載策略。

    類加載器的一個重要用途是在JVM中爲相同名稱的Java類建立隔離空間。
    在JVM中,判斷兩個類是否相同,不只根據該類的二進制名稱,還須要根據兩個類的定義類加載
     (爲兩個類定義加載的同一個類的兩個對象之間賦值:java.lang.ClassCastException)

2.2Java 類的連接
    Java類的連接指的是將Java 類的二進制代碼合併到JVM 運行狀態之中的過程。
    (在連接以前,這個類必須被成功加載。)

    類的連接包括:驗證、準備、解析。

    驗證:確保Java 類的二進制表示在結構上徹底正確。(Java.lang.VerifyError)
    準備:建立Java類中的靜態域,並設置默認值。(不會執行代碼。)
    解析:確保(在一個Java 類中會包含對其它類或接口的形式引用、包括它的父類、所實現的接口、方法形式參數和返回值Java類等。)這些被引用的類能被正確的找到。(解析過程可能會致使其它的Java 類被加載)

    兩種解析策略:
    一種作法是在連接時候,就遞歸把全部依賴的形式引用都進行解析。
    另外一種則是隻在一個形式引用真正須要的時候才進行解析。(Oracle的JDK 6採用的是這種
     (Java類只是被引用了,可是並無被真正用到,那麼這個類有可能就不被解析)

2.3Java類的初始化
    當一個Java類第一次被真正使用到的時候,JVM 會進行該類的初始化操做。
    (執行靜態代碼塊和初始化靜態域,會按照源代碼從上到下順序執行靜態代碼塊和初始化靜態域。
    (一個類被初始化前,它的直接父類也須要被初始化。可是一個接口的初始化,不會引發父接口的初始化。)
   
    Java類和接口初始化條件:
  • 建立一個Java類實例:MyClass obj = new MyClass();
  • 調用一個Java類中的靜態方法:MyClass.sayHello();
  • 給Java 類或結構中聲明的今天域賦值:MyClass.value = 10;
  • 訪問Java類或接口中聲明的靜態域,而且該域不是常值變量:int value = MyClass.value();
  • 在頂層Java類中執行assert 語句。
    (經過Java反射API 也可能形成類和接口被初始化,須要注意的是:當訪問一個Java類或接口中的靜態域的時候,只有真正聲明這個域的類或接口才會被初始化。)

2.4建立本身的類加載器
    典型場景包括實現特定的Java字節代碼查找方式、對字節代碼進行加密/ 機密以及實現同名 Java類的隔離。

    只須要繼承java.lang.ClassLoader 類並覆蓋寫對應的方法便可。

  • defineClass():這個方法用來完成從Java字節代碼的字節數組到java.lang.Class的轉換。這個方法是不能被覆寫的,通常是用原生代碼來實現的。
  • findLoadedClass():這個方法用來根據名稱查找已經加載過的Java類。一個類加載器不會重複加載同一名稱的類。
  • findClass():這個方法用來根據名稱查找並加載Java類。(覆蓋,自定義)
  • loadClass():這個方法用來根據名稱加載Java類。
  • resolveClass():這個方法用來連接一個Java類

3.Java線程:基本概念、可見性與同步
    應用的例子:高性能Web服務器、遊戲服務器、搜索引擎爬蟲等。
     (須要同時處理成千上萬個請求,通常採用多線程或事件驅動架構)

3.1Java線程的基本概念
    進程(process)和線程(thread)

    操做系統中進程是資源的組織單位。 進程有一個包含了程序內容和數據的地址空間, 以及其它的資源,包括打開的文件、子進程和信號處理器等。不一樣進程的地址空間 是互相隔離的。

    線程表示程序執行流程,是CPU調度的基本單位。 程有本身的程序計數器、寄存器、棧和幀等。
 
3.2可見性  
    可見性(visibility)的問題是Java多線程應用中的錯誤根源。
  • CPU 內部的緩存:如今的CPU通常都擁有層次結構的幾級緩存。CPU直接操做的是緩存中的數據,並在須要的時候把緩存中的數據與主存進行同步。所以在某些時刻,緩存中的數據與主存內的數據多是不一致的。某個線程所執行的寫入操做的新值可能當前還保存在CPU的緩存中,尚未被寫回到主存中。這個時候,另一個線程的讀取操做讀取的就仍是主存中的舊值。
  • CPU的指令執行順序:在某些時候,CPU可能改變指令的執行順序。這有可能致使一個線程過早的看到另一個線程的寫入操做完成以後的新值。
  • 編譯器代碼重排:出於性能優化的目的,編譯器可能在編譯的時候對生成的目標代碼進行從新排列。

    Java內存模型(Java Memory Model)就是爲了實現「編寫一次,處處運行」爲目的而引用的。
    (描述了程序中共享變量的關係以及在主存中寫入和讀取這些變量值的底層細節)

     Java內存模型定義了Java語言中的synchronized、volatile和final等關鍵詞 對主存中變量讀寫操做的意義。Java開發人員使用這些關鍵詞來描述程序所指望的 行爲,而編譯器和JVM負責保證生成的代碼在運行時刻的行爲符合內存模型的描述。
    好比對聲明爲volatile的變量來講,在讀取以前,JVM會確保CPU中緩存的值首先會失 效,從新從主存中進行讀取;而寫入以後,新的值會被立刻寫入到主存中。

    Java內存模型中一個重要的概念是定義了「在以前發生(happens-before)」的順序。
    ( 若是一個動做按照「在以前發生」的順序發生在另一個動做以前,那麼前一個動 做的結果在多線程的狀況下對於後一個動做就是確定可見的。

     最多見的「在以前發 生」的順序包括:對一個對象上的監視器的解鎖操做確定發生在下一個對同一個監 視器的加鎖操做以前;對聲明爲volatile的變量的寫操做確定發生在後續的讀操做之 前。

     有了「在以前發生」順序,多線程程序在運行時刻的行爲在關鍵部分上就是可 預測的了。
    ( 編譯器和JVM會確保「在以前發生」順序能夠獲得保證。)

    若是一個變量的值可能被多個線程讀取,又能被最少一個線程鎖寫入,同時這些讀 寫操做之間並無定義好的「在以前發生」的順序的話,那麼在這個變量上就存在 數據競爭(data race)。
     數據競爭的存在是Java多線程應用中要解決的首要問題。 決的辦法就是經過synchronized和volatile關鍵詞來定義好「在以前發生」順序。)

3.3Java中的鎖
    當數據競爭存在的時候,最簡單的解決辦法就是加鎖。鎖機制限制在同一時間只容許一個線程訪問產生競爭的數據的臨界區。
    ( 一個線程可 以在一個Java對象上加屢次鎖。同時JVM保證了在獲取鎖以前和釋放鎖以後,變量 的值是與主存中的內容同步的。

3.4Java線程的同步    
    Java提供的線程之間的 等待-通知機制。
    ( 當線程所要求的條件 不知足時,就進入等待狀態;而另外的線程則負責在合適的時機發出通知來喚醒等 待中的線程。Java中的java.lang.Object類中的wait/notify/notifyAll 方法組就是完成線 程之間的同步的。

     在某個Java對象上面調用wait方法的時候,首先要檢查當前線程是否獲取到了這個對 象上的鎖。若是沒有的話,就會直接拋出java.lang.IllegalMonitorStateException異常。 若是有鎖的話,就把當前線程添加到對象的等待集合中,並釋放其所擁有的鎖。當 前線程被阻塞,沒法繼續執行,直到被從對象的等待集合中移除。引發某個線程從 對象的等待集合中移除的緣由有不少:對象上的notify方法被調用時,該線程被選中; 對象上的notifyAll方法被調用;線程被中斷;對於有超時限制的wait操做,當超過期 間限制時;JVM內部實如今非正常狀況下的操做。
    
     :wait/notify/notifyAll操做須要放在synchronized 代碼塊或方法中,這樣才能保證在執行  wait/notify/notifyAll的時候,當前線程已經 得到了所須要的鎖。當對於某個對象的等待集合中的線程數目沒有把握的時候,最 好使用  notifyAll而不是notify。notifyAll雖然會致使線程在沒有必要的狀況下被喚醒 而產生性能影響,可是在使用上更加簡單一些。因爲線程可能在非正常狀況下被意 外喚醒,通常須要把wait操做放在一個循環中,並檢查所要求的邏輯條件是否知足。
     典型的使用模式以下所示:     
private Object lock = new Object();
synchronized (lock) {
while (/* 邏輯條件不知足的時候 */) {  
try {
    lock.wait();
} catch (InterruptedException e) {}
}
//處理邏輯
上述代碼中使用了一個私有對象lock 來做爲加鎖的對象,其好處是能夠避免其它代 碼錯誤的使用這個對象。

3.5中斷線程
     經過一個線程對象的interrupt()方  法能夠向該線程發出一箇中斷請求。中斷請求是 一種線程之間的協做方式。

     當線程A經過調用線程B的interrupt()方法來發出中斷請求 的時候,線程A 是在請求線程B的注意。線程B應該在方便的時候來處理這個中斷請 求,固然這不是必須的。

     當中斷髮生的時候,線程對象中會有一個標記來記錄當前 的中斷狀態。經過isInterrupted()方法能夠判斷是否有中斷請求發生。

     若是當中斷請 求發生的時候,線程正處於阻塞狀態,那麼這個中斷請求會致使該線程退出阻塞狀 態。

     可能形成線程處於阻塞狀態的狀況有:當線程經過調用wait()方法進入一個對象 的等待集合中,或是經過sleep()方法來暫時休眠,或是經過join()方法來等待另一 個線程完成的時候。
    ( 在線程阻塞的狀況下,當中斷髮生的時候,會拋 出java.lang.InterruptedException,  代碼會進入相應的異常處理邏輯之中。

     實際上在 調用wait/sleep/join方法的時候,是必須捕獲這個異常的。中斷一個正在某個對象的 等待集合中的線程,會使得這個線程從等待集合中被移除,使得它能夠在再次得到 鎖以後,繼續執行java.lang.InterruptedException異常的處理邏輯。

    經過中斷線程能夠實現可取消的任務。在任務的執行過程當中能夠按期檢查當前線程 的中斷標記,若是線程收到了中斷請求,那麼就能夠終止這個任務的執行。當遇到 java.lang.InterruptedException 的異常,不要捕獲了以後不作任何處理。若是不想在 這個層次上處理這個異常,就把異常從新拋出。當一個在阻塞狀態的線程被中斷並 且拋出java.lang.InterruptedException 異常的時候,其對象中的中斷狀態標記會被清 空。若是捕獲了java.lang.InterruptedException 異常可是又不能從新拋出的話,須要 經過再次調用interrupt()方法來從新設置這個標記。

4.Java垃圾回收機制與引用類型
    Java語言的一個重要特性是引入了自動的內存管理機制

4.1Java垃圾回收機制
     Java的垃圾回收器要負責完成3件任務:分配內存、確保被引用的對象的內存不被 錯誤回收以及回收再也不被引用的對象的內存空間。

     般狀況下,當垃圾回收器在進行回收操做的時候,整個應用的執行是被暫時停止 (stop-the-world)的。
    ( 這是由於垃圾回收器須要更新應用中全部對象引用的實際內 存地址。

    不一樣的硬件平臺所能支持的垃圾回收方式也不一樣。
    ( 多CPU的平臺上, 就能夠經過並行的方式來回收垃圾。而單CPU平臺則只能串行進行。)

     服務器端應用可能但願在應用的整個運行時間 中,花在垃圾回收上的時間總數越小越好。
     而對於與用戶交互的應用來講,則可能 但願所垃圾回收所帶來的應用停頓的時間間隔越小越好。

    Java 垃圾回收機制最基本的作法是 分代回收。
{
     內存中的區域被劃分紅不一樣的世代, 對象根據其存活的時間被保存在對應世代的區域中。

    通常的實現是劃分紅3個世代:   年輕、年老和永久。
  •     內存的分配是發生在年輕世代中的。
  •     當一個對象存活時間足夠長的時候,它就會被複制到年老世代中。
     對於不一樣的世代可使用不一樣的垃圾回收算法
    
    年輕世代的內存區域被進一步劃分紅 伊甸園(Eden)和兩個存活區(survivor space)。
  • 伊甸園是進行內存分配的地方,是一塊連續的空閒內存區域。在上面進行內存分配速度很是快,由於不須要進行可用內存塊的查找。
  • 兩個存活區中始終有一個是空白的。
 
     在進行垃圾回收的時候,伊甸園和其中一個非空存活區中還存活的對象根據其 存活時間被複制到當前空白的存活區或年老世代中。 通過這一次的複製以後,以前 非空的存活區中包含了當前還存活的對象,而伊甸園和另外一個存活區中的內容已經 再也不須要了,只須要簡單地把這兩個區域清空便可。下一次垃圾回收的時候,這兩 個存活區的角色就發生了交換。通常來講,年輕世代區域較小,並且大部分對象都 已經再也不存活,所以在其中查找存活對象的效率較高。

     而對於年老和永久世代的內存區域,則採用的是不一樣的回收算法,稱爲「標記-清除-壓縮(Mark-Sweep-Compact)」。標記的過程是找出當前還存活的對象,並進行標 記;清除則遍歷整個內存區域,找出其中須要進行回收的區域;而壓縮則把存活對 象的內存移動到整個內存區域的一端,使得另外一端是一塊連續的空閒區域,方便進 行內存分配和複製。
}

    JDK 5 中提供了4種不一樣的垃圾回收機制。
     串行回收方式、分代回收、並行回收方式、併發標記-清除回收。
  • 最經常使用的是串行回收方式,即便用單個CPU回收年輕和年老世代的內存。在回收的過程當中,應用程序被暫時停止。回收方式使用的是上面提到的最基本的分代回收。串行回收方式適合於通常的單CPU桌面平臺。
  • 若是是多CPU的平臺,則適合的是並行回收方式。這種方式在對年輕世代  進行回收的時候,會使用多個CPU來並行處理,能夠提高回收的性能。
  • 併發標記-清除回收方式適合於對應用的響應時間要求比較  高的狀況,即須要減小垃圾回收所帶來的應用暫時停止的時間。這種作法的優勢在於能夠在應用運行的同時標記存活對象與回收垃圾,而只須要暫時停止應用比較短的時間。

     經過JDK中提供的JConsole能夠很容易的查看當前應用的內存使用狀況。在JVM啓動 的時候添加參數  -verbose:gc  能夠查看垃圾回收器的運行結果。

4.2Java引用類型
    若是一個內存中的對象沒有任何引用的話,就說明這個對象已經再也不被使用了,從而能夠成爲被垃圾回收的候選。
    (有引用存在的對象超出JVM中的內存總數:OutOfMemory)

    強引用:
     在通常的Java程序中,見到最多的就是強引用(strong reference)。如Date date = new  Date(),date就是一個對象的強引用。對象的強引用能夠在程序中處處傳遞。 強引用的存在限制了對象在內存中的 存活時間。

    軟引用:
     軟引用(soft reference)在強度上弱於強引用,經過類SoftReference來表示。它的 做用是告訴垃圾回收器,程序中的哪些對象是不那麼重要,當內存不足的時候是可 以被暫時回收的。
    ( 軟引用很是適合於建立緩存。當系統內存不足的時候,緩存中的內容是能夠 被釋放的。

    弱引用:
     弱引用(weak reference)在強度上弱於軟引用,經過類WeakReference來  表示。它 的做用是引用一個對象,可是並不阻止該對象被回收。
    ( 弱引用的做用在於解決強引用所帶來的對象之間在存活時間上的耦合關係。弱 引用最多見的用處是在集合類中,尤爲在哈希表中。 哈希表的接口容許使用任何Java 對象做爲鍵來使用。當一個鍵值對被放入到哈希表中以後,哈希表  對象自己就有了 對這些鍵和值對象的引用。若是這種引用是強引用的話,那麼只要哈希表對象自己 還存活,其中所包含的鍵和值對象是不會被回收的。若是某個存活  時間很長的哈希 表中包含的鍵值對不少,最終就有可能消耗掉JVM中所有的內存。 對於這種狀況的解決辦法就是使用弱引用來引用這些對象,這樣哈希表中的鍵和值 對象都能被垃圾回收。

    幽靈引用:
    ( Java提供的對象終止化機制(finalization)。 在Object 類裏面有個finalize方法,其設計的初衷是在一個對象被真正回收以前,能夠用來執 行一些清理的工做。 由於Java並無提供相似C++的析構函數同樣的機制,就經過 finalize方法來實現。 可是問題在於垃圾回收器的運行時間是不固定的,因此這些清 理工做的實際運行時間也是不能預知的。 幽靈引用(phantom reference)能夠解決 這個問題。
     在建立幽靈引用PhantomReference的時候必需要指定一個引用隊列。當 一個對象的finalize方法已經被調用了以後,這個對象的幽靈引用會被加入到隊列中。 經過檢查該隊列裏面的內容就知道一個對象是否是已經準備要被回收了。
    (移動設備: 程序能夠在肯定一個對象要被回收以後,再申 請內存建立新的對象。

    引用隊列:
     在有些狀況下,程序會須要在一個對象的可達到性發生變化的時候獲得通知。
    ( 好比 某個對象的強引用都已經不存在了,只剩下軟引用或是弱引用。可是還須要對引用 自己作一些的處理。)
    ( 典型的情景是在哈希表中。引用對象是做爲WeakHashMap中的 鍵對象的,當其引用的實際對象被垃圾回收以後,就須要把該鍵值對從哈希表中刪 除。
     有了引用隊列(ReferenceQueue),就能夠方便的獲取到這些弱引用對象,將它 們從表中刪除。在軟引用和弱引用對象被添加到隊列以前,其對實際對象的引用會 被自動清空。經過引用隊列的poll/remove方法就能夠分別以非阻塞和阻塞的方式獲 取隊列中的引用對象。
    

5.Java泛型
    Java泛型(generics)是JDK 5中引入的一個新特性,容許在定義類和接口的時候使 用類型參數(type parameter)。聲明的類型參數在使用時用具體的類型來替換。
    ( 型最主要的應用是在JDK 5中的新集合類框架中。
    ( 一個方法 若是接收List<Object>做爲形式參數,那麼若是嘗試將一個List<String>的對象做爲實 際參數傳進去,卻發現沒法經過編譯。
    
5.1類型擦除(type erasure)
     Java中的泛型基本 上都是在編譯器這個層次來實現的。 在生成的Java字節代碼中是不包含泛型中的類 型信息的。 使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個 過程就稱爲類型擦除。

    ( 而由泛型附加的類型信息對JVM來講是不可 見的。Java編譯器會在編譯時儘量的發現可能出錯的  地方,可是仍然沒法避免在 運行時刻出現類型轉換異常的狀況。

不少泛型的奇怪特性都與這個類型擦除的存在有關,包括:
  • 泛型類並無本身獨有的Class類對象。好比並不存在List<String>.class 或是List<Integer>.class,而只有List.class。
  • 靜態變量是被泛型類的全部實例所共享的。對於聲明爲MyClass<T>的類,訪問其中的靜態變量的方法仍然是  MyClass.myStaticVar。無論是經過new MyClass<String>仍是new MyClass<Integer>建立的對象,都是共享一個靜態變量。
  • 泛型的類型參數不能用在Java異常處理的catch語句中。由於異常處理是由JVM在運行時刻來進行的。因爲類型信息被擦除,JVM 是沒法區分兩個異常類型MyException<String>和MyException<Integer>的。對於JVM 來講,它們都是MyException類型的。也就沒法執行與異常對應的catch語句。

5.2通配符與上下界
     如List<?>就聲明瞭List中包含的 元素類型是未知的。  通配符所表明的實際上是一組類型,但具體的類型是未知的。
List<?>所聲明的就是全部類型都是能夠的。可是List<?>並不等同於List<Object>。

     由於對於List<?>中的元素只能用Object來引用,在有些狀況下不是很方便。在這些 狀況下,可使用上下界來限制未知類型的範圍。

     List<? extends Number>說明List 中可能包含的元素類型是Number及其子類。

     List<? super Number>則說明List中包含的是 Number及其父類。

5.3系統類型
    根據Liskov替換原則,子類是能夠替換父類的。 可是反過來的話,即用父類的引 用替換子類引用  的時候,就須要進行強制類型轉換。

    引入泛型以後的類型系統增長了兩個維度:
    一個是 類型參數自身的繼承體系結構
    對於  List<String>和 List<Object>這樣的狀況,類型參數String是繼承自Object的。

    另一個是 泛型類或接口自身的繼承體系結構。
    ( List 接口繼承自Collection接口。
  • 相同類型參數的泛型類的關係取決於泛型類自身的繼承體系結構。即List<String> 是  Collection<String>  的子類型,List<String> 能夠替換Collection<String>。這種狀況也適用於帶有上下界的類型聲明。
  • 當泛型類的類型聲明中使用了通配符的時候,其子類型能夠在兩個維度上分別展開。如對Collection<? extends Number>來講,其子類型能夠在Collection這個維度上展開,即List<? extends Number>和Set<? extends Number>等;也能夠在Number這個層次上展開,即Collection<Double>和  Collection<Integer>等。如此循環下去,ArrayList<Long>和  HashSet<Double>等也都算是Collection<? extends Number>的子類型。
  • 若是泛型類中包含多個類型參數,則對於每一個類型參數分別應用上面的規則。
    ( List<Object>改爲List<?>。List<String>是List<?>的子類型,所以傳遞參數時不會 發生錯誤。 


5.4開發本身的泛型類
     泛型類與通常的Java類基本相同,只是在類和接口定義上多出來了用<>聲明的類型 參數。一個類能夠有多個類型參數,如  MyClass<X, Y, Z>。  每一個類型參數在聲明的 時候能夠指定上界。所聲明的類型參數在Java類中能夠像通常的類型同樣做爲方法 的參數和返回值,或是做爲域和局部變量的類型。
    ( 可是因爲類型擦除機制,類型參 數並不能用來建立對象或是做爲靜態變量的類型。考慮下面的泛型類中的正確和錯 誤的用法。
class ClassTest<X extends Number, Y, Z> { 
    private X x
    private static Y y; //編譯錯誤,不能用在靜態變量中
    public X getFirst() {
        //正確用法 
        return x
    } 
    public void wrong() { 
        Z z = new Z(); //編譯錯誤,不能建立對象
    }
}  

5.5最佳實踐
  • 在代碼中避免泛型類和原始類型的混用。好比List<String>和List不該該共同使用。這樣會產生一些編譯器警告和潛在的運行時異常。當須要利用JDK 5以前開發的遺留代碼,而不得不這麼作時,也儘量的隔離相關的代碼。
  • 在使用帶通配符的泛型類的時候,須要明確通配符所表明的一組類型的概念。因爲具體的類型是未知的,不少操做是不容許的。
  • 泛型類最好不要同數組一塊使用。你只能建立new List<?>[10]這樣的數組,沒法建立new List<String>[10]這樣的。這限制了數組的使用能力,並且會帶來不少費解的問題。所以,當須要相似數組的功能時候,使用集合類便可。
  • 不要忽視編譯器給出的警告信息。 

6.Java 註解
     JDK 5中引入了源代碼中 的註解(annotation)這一機制。註解使得Java源代碼中不但能夠包含功能性的實現
代碼,還能夠添加元數據。註解的功能相似於代碼中的註釋,所不一樣的是註解不是 提供代碼功能的說明,而是實現程序功能的重要組成部分。Java註解已經在不少框 架中獲得了普遍的使用,用來簡化程序中的配置。

6.1使用註解
     在通常的Java開發中,最常接觸到的可能就是@Override@SuppressWarnings這 兩 個註解了。

     使用@Override的時候只須要一個簡單的聲明便可。這種稱爲標記註解 (marker annotation ),它的出現就表明了某種配置語義。 而其它的註解是能夠有 本身的配置參數的。配置參數以名值對的方式出現。

     使用@SupressWarnings的時候 須要相似@SupressWarnings({"uncheck", "unused"})這樣的語法。
     在括號裏面的是該注 解可供配置的值。因爲這個註解只有一個配置參數,該參數的名稱默認爲value,並 且能夠省略。
    而花括號則表示是數組類型。
    ( 在JPA中的@Table註解使用相似 @Table(name = "Customer", schema = "APP")這樣的語法。從這裏能夠看到名值對的 用法。在使用註解時候的配置參數的值必須是編譯時刻的常量。

    從某種角度來講,能夠把註解當作是一個XML元素,該元素能夠有不一樣的預約義的 屬性。而屬性的值是能夠在聲明該元素的時候自行指定的。在代碼中使用註解,就 至關於把一部分元數據從XML文件移到了代碼自己之中,在一個地方管理和維護。

6.2開發註解
     經過該註解能夠在源代碼中記錄每一個類或接口的分工和進 度狀況。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Assignment {
    String assignee();
    int effort();
    double finished() default 0;
}
@interface用來聲明一個註解,其中的每個方法其實是聲明瞭一個配置參數。
(方 法的名稱就是參數的名稱,返回值類型就是參數的類型。能夠經過default來聲明參 數的默認值。)

在這裏能夠看到@Retention和@Target這樣的元註解,用來聲明註解 自己的行爲。
@Retention用來聲明註解的保留策略,有 CLASS、RUNTIME和SOURCE 這三種,分別表示註解保存在類文件、JVM運行時刻和源代碼中。
只有當聲明爲 RUNTIME的時候,纔可以在運行時刻經過反射API來獲取到註解的信息。@Target用 來聲明註解能夠被添加在哪些類型的元素上,如類型、方法和域等。

6.3處理註解
     JDK 5中提供了apt工具用來對註解進行 處理。apt是一個命令行工具,與之配套的還有一套用來描述程序語義結構的Mirror  API。Mirror API(com.sun.mirror.*)描述的是程序在編譯時刻的靜態結構。經過Mirror  API能夠獲取到被註解的Java類型元素的信息,從而提供相應的處理邏輯。

    體的處 理工做交給apt工具來完成。編寫註解處理器的核心是AnnotationProcessorFactory AnnotationProcessor兩個接口。
(後者表示的是註解處理器,而前者則是爲某些注 解類型建立註解處理器的工廠。)


7.Java反射與動態代理
    經過反射API能夠獲取程序在運行時刻的內部結構。 反射API中提供的動 態代理也是很是強大的功能,能夠原生實現AOP中的方法攔截功能。 知道了Java類的內部結構以後,就能夠與它進行交互,包括建立新的對象和調用對 象中的方法等。這種交互方式與直接在源代碼中使用的效果是相同的,可是又額外 提供了運行時刻的靈活性。

7.1基本用法
     Java反射API的第一個主要做用是獲取程序在運行時刻的內部結構。

     只要有了java.lang.Class類的對象,就能夠經過其中 的方法來獲取到該類中的構造方法、域和方法。對應的方法分別 getConstructor、getField和getMethod。
     這三個方法還有相應的getDeclaredXXX 本,區別在於getDeclaredXXX版本的方法只會獲取該類自身所聲明的元素,而不會考 慮繼承下來的。
    ( Constructor、Field和Method這三個類分別表示類中的構造方法、域 和方法。這些類中的方法能夠獲取到所對應結構的元數據

     反射API的另一個做用是在運行時刻對一個Java對象進行操做。
    
     這些操做包括動 態建立一個Java類的對象,獲取某個域的值以及調用某個方法。在Java源代碼中編 寫的對類和對象的操做,均可以在運行時刻經過反射API來實現。

    一個簡 單的Java類。
class MyClass {
    public int count;
    public MyClass(int start) {
        count = start;
    }
    public void increase(int step) {
        count = count + step;
    }
}

MyClass myClass = new MyClass(0); // 通常作法
myClass.increase(2);
System.out.println("Normal -> " + myClass.count);
try {
    // 獲取構造方法
    Constructor constructor = MyClass.class.getConstructor(int.class);
    // 建立對象
    MyClass myClassReflect = constructor.newInstance(10);
    // 獲取方法
    Method method = MyClass.class.getMethod("increase"int.class);
    // 調用方法
    method.invoke(myClassReflect, 5);
    // 獲取域
    Field field = MyClass.class.getField("count");
    // 獲取域的值
    System.out.println("Reflect -> " + field.getInt(myClassReflect));
catch (Exception e) {
    e.printStackTrace();
}

    因爲 數組的特殊性,Array類提供了一系列的靜態方法用來建立數組和對數組中的元 素進行訪問和操做。
Object array = Array.newInstance(String.class, 10); //等價於 new String[10]
Array.set(array, 0, "Hello"); //等價於array[0] = "Hello"
Array.set(array, 1, "World"); //等價於array[1] = "World"
System.out.println(Array.get(array, 0)); //等價於array[0] 

     使用Java反射API的時候能夠繞過Java默認的訪問控制檢查,好比能夠直接獲取到對 象的私有域的值或是調用私有方法。只須要在獲取到Constructor、Field和Method類 的對象以後,調用setAccessible方法並設爲true便可。

7.2處理泛型
好比在代碼中聲明瞭一個域是List<String>類型的,雖然在運行時刻其類型會變成原 始類型List,可是仍然能夠經過反射來獲取到所用的實際的類型參數。
Field field = Pair.class.getDeclaredField("myList"); // myList的類型是List
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
    ParameterizedType paramType = (ParameterizedType) type;
    Type[] actualTypes = paramType.getActualTypeArguments();
    for (Type aType : actualTypes) {
        if (aType instanceof Class) {
            Class clz = (Class) aType;
            System.out.println(clz.getName()); // 輸出java.lang.String
        }     
    }
}

7.3動態代理
     熟悉設計模式的人對於代理模式可能都不陌生。  代理對象和被代理對象通常實現 相同的接口,調用者與代理對象進行交互。代理的存在對於調用者來講是透明的, 調用者看到的只是接口。代理對象則能夠封裝一些內部的處理邏輯,如訪問控制、 遠程通訊、日誌、緩存等。 好比一個對象訪問代理就能夠在普通的訪問機制之上添 加緩存的支持。
    
    JDK 5引入的動態代理機制,容許開發人員在運行時刻動態的建立出代理類及 其對象。
     在運行時刻,能夠動態建立出一個實現了多個接口的代理類。
    
     每一個代理類 的對象都會關聯一個表示內部處理邏輯的InvocationHandler接口的實現。

     當使用者 調用了代理對象所代理的接口中的方法的時候,這個調用的信息會被傳遞給 InvocationHandler的invoke方法。

     在  invoke方法的參數中能夠獲取到代理對象、方法 對應的Method對象和調用的實際參數。invoke方法的返回值被返回給使用者。 這種 作法實際上至關於對方法調用進行了攔截。

     下面的代碼用來代理一個實現了List接口的對象。所實現的功能也很是簡單,那就 是禁止使用List接口中的add方法。
    public List getList(final List list) {
        return (List) Proxy.newProxyInstance(
                DummyProxy.class.getClassLoader(),
                new Class[] { List.class }, 
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method,Object[] args) 
                                   throws  Throwable {
                        if ("add".equals(method.getName())) {
                            throw new UnsupportedOperationException();
                        } else {
                            return method.invoke(list, args);
                        }
                    }
                });
    }


    Java 反射API實際上定義了一種相對於編譯時刻而言更加鬆散的契約。若是被調用 的Java對象中並不包含某個方法,而在調用者代碼中進行引用的話,在編譯時刻就 會出現錯誤。而反射API則能夠把這樣的檢查推遲到運行時刻來完成。經過把Java 中的字節代碼加強、類加載器和反射API結合起來,能夠處理一些對靈活性要求很 高的場景。

     在有些狀況下,可能會須要從遠端加載一個Java 類來執行。好比一個客戶端Java 程序能夠經過網絡從服務器端下載Java類來執行,從而能夠實現自動更新的機制。 當代碼邏輯須要更新的時候,只須要部署一個新的Java類到服務器端便可。通常的 作法是經過自定義類加載器下載了類字節代碼以後,定義出  Class類的對象,再通 過newInstance方法就能夠建立出實例了。不過這種作法要求客戶端和服務器端都 具備某個接口的定義,從服務器端下載的是這個接口的實現。這樣的話才能在客戶 端進行所需的類型轉換,並經過接口來使用這個對象實例。若是但願客戶端和服務 器端採用更加鬆散的契約的話,使用反射API就能夠了。二者之間的契約只須要在 方法的名稱和參數這個級別就足夠了。服務器端Java類並不須要實現特定的接口, 能夠是通常的Java類。

     動態代理的使用場景就更加普遍了。須要使用AOP中的方法攔截功能的地方均可以 用到動態代理。Spring框架的AOP實現默認也使用動態代理。不過JDK中的動態代理 只支持對接口的代理,不能對一個普通的Java類提供代理。不過這種實如今大部分 的時候已經夠用了。

8.Java I / O 
     在應用程序中,一般會涉及到兩種類型的計算:CPU計算和I/O 計算。對於大多數 應用來講,花費在等待I/O 上的時間是佔較大比重的。 一般須要等待速度較慢的磁 盤或是網絡鏈接完成I/O 請求,才能繼續後面的CPU計算任務。所以提升I/O 操做 的效率對應用的性能有較大的幫助。

8.1流
     Java語言提供了多個層次不一樣的概念來對I/O操做進行抽象。Java I/O中最先的概念是 流,包括輸入流和輸出流。
    
     流是一個連續的字節的序列。
    輸入流是用來讀取這個序列,而輸出流則構建這個序列。
   
     InputStream 和OutputStream所操縱的基本單元就是字節。每次讀取和寫入單個字節或是字節數 組。
    
    讀取或輸出 Java的基本數據類型
    ( DataInputStream和DataOutputStream。它們所提供的相似readFloat和writeDouble 這樣的方法,會讓處理基本數據類型變得很簡單。

     讀取或寫入的是Java中 的對象
    ( ObjectInputStream和ObjectOutputStream。它們與對象的序 列化機制一塊兒,能夠實現Java對象狀態的持久化和數據傳遞。

    
8.2流的使用
     每一個打 開的流都須要被正確的關閉以釋放資源。 所遵循的原則是誰打開誰釋放。
     若是一個 流只在某個方法體內使用,則經過finally語句或是JDK 7 中的try-with-resources語句 來確保在方法返回以前,流被正確的關閉

8.3緩衝區
     因爲流背後的數據有可能比較大,在實際的操做中,一般會使用緩衝區來提升性能。 傳統的緩衝區的實現是使用數組來完成。
    ( 好比經典的從InputStream到OutputStream 的複製的實現,就是使用一個字節數組做爲中間的緩衝區。
      
     NIO中引入的Buffer 及其子類,能夠很方便的用來建立各類基本數據類型的緩衝區。

     在Buffer上進行的元素添加和刪除操做,都圍繞3個屬性position、limit和capacity 開,分別表示Buffer當前的讀寫位置、可用的讀寫範圍和容量限制。
    ( 容量限制是在創 建的時候指定的。Buffer提供的get/put方法都有相對和絕對兩種形式。相對讀寫時的位置是相對於position的值,而絕對讀寫則須要指定起始的序號。
    ( 在使用Buffer的 常見錯誤就是在讀寫操做時沒有考慮到這3個元素的值,由於大多數時候都是使用 的是相對讀寫操做,而position的值可能早就發生了變化。
    ( 將數據讀入緩衝區以前,  須要調用clear方法;將緩衝區中的數據輸出以前,須要調用flip方法。

    
8.4字符與編碼
     在程序中,老是免不了與字符打交道,畢竟字符是用戶直接可見的信息。而與字符 處理直接相關的就是編碼。
    (須要理解字符集和編碼的概念

     字符集,顧名思義,就是字符的 集合。 一個字符集中所包含的字符一般與地區和語言有關。字符集中的每一個字符通
常會有一個整數編碼與其對應。常見的字符集有ASCII、ISO-8859-1和Unicode等。
    
     對於字符集中的每一個字符,爲了在計算機中表示,都須要轉換某種字節的序列,即 該字符的編碼。同一個字符集能夠有不一樣的編碼方式。
    ( 若是某種編碼格式產生的字 節序列,用另一種編碼格式來解碼的話,就可能會獲得錯誤的字符,從而產生亂
碼的狀況。
    
     NIO中的java.nio.charset包提供了與字符集相關的類,能夠用來進行編碼和解碼。其 中的CharsetEncoderCharsetDecoder容許對編碼和解碼過程進行精細的控制,如處 理非法的輸入以及字符集中沒法識別的字符等。
String input = "你123好";
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
CharsetDecoder decoder = charset.newDecoder();
CharBuffer buffer = CharBuffer.allocate(32);
buffer.put(input);
buffer.flip();
try {
ByteBuffer byteBuffer = encoder.encode(buffer);
CharBuffer cbuf = decoder.decode(byteBuffer);
System.out.println(cbuf); //輸出123
} catch (CharacterCodingException e) {
e.printStackTrace();
}

     Java I/O在處理字節流字以外,還提供了處理字符流的類,即Reader/Writer類及其子 類,它們所操縱的基本單位是char類型。在字節和字符之間的橋樑就是編碼格式。 經過編碼器來完成這二者之間的轉換。在建立  Reader/Writer子類實例的時候,老是 應該使用兩個參數的構造方法,即顯式指定使用的字符集或編碼解碼器。若是不顯 式指定,使用的是JVM的默認字符集,有可能在其它平臺上產生錯誤。

8.5通道
     通道做爲NIO中的核心概念,在設計上比以前的流要好很多。通道相關的不少實現 都是接口而不是抽象類。通道自己的抽象層次也更加合理。通道表示的是對支持I/O 操做的實體的一個鏈接。一旦通道被打開以後,就能夠執行讀取和寫入操做,而不 須要像流那樣由輸入流或輸出流來分別進行處理。與流相比,通道的操做使用的是 Buffer而不是數組,使用更加方便靈活。通道的引入提高了I/O 操做的靈活性和性 能,主要體如今文件操做和網絡操做上。


8.6文件通道
     對文件操做方面,文件通道FileChannel提供了與其它通道之間高效傳輸數據的能力, 比傳統的基於流和字節數組做爲緩衝區的作法,要來得簡單和快速。 好比下面的把 一個網頁的內容保存到本地文件的實現。
FileOutputStream output = new FileOutputStream("baidu.txt");
FileChannel channel = output.getChannel();
URL url = new URL("http://www.baidu.com");
InputStream input = url.openStream();
ReadableByteChannel readChannel = Channels.newChannel(input);
channel.transferFrom(readChannel, 0, Integer.MAX_VALUE);

    文件通道的另一個功能是對文件的部分片斷進行加鎖。
    文件通道上的鎖是由JVM所持有的,所以適合於與其它應用程序協同時使用。

     另一個在性能方面有很大提高的功能是內存映射文件的支持。經過FileChannel map方法能夠建立出一個MappedByteBuffer對象,對這個緩衝區的操做都會直接 反映到文件內容上
     這點尤爲適合對大文件進行讀寫操做。
    
8.7套接字通道
    在套接字通道方面的改進是提供了對 非阻塞I/O和多路複用I/O的支持。
    
     NIO中引入了非阻塞I/O的支持,不過只限於套接字I/O操做。全部繼承 SelectableChannel的通道類均可以經過configureBlocking方法來設置是否採用非阻 塞模式。
    
     多路複用I/O是一種新的I/O編程模型。傳統的套接字服務器的處理方式是對於每一 個客戶端套接字鏈接,都新建立一個線程來進行處理。建立線程是很耗時的操做, 而有的實現會採用線程池。
    
     而多路複用  I/O的 基本作法是由一個線程來管理多個套接字鏈接。 該線程會負責根據鏈接的狀態,來 進行相應的處理。
     多路複用I/O依靠操做系統提供的select或類似系統調用的支持, 選擇那些已經就緒的套接字鏈接來處理。能夠把多個非阻塞I/O通道註冊在某 Selector上,並聲明所感興趣的操做類型。每次調用Selector的select方法,就能夠
選擇到某些感興趣的操做已經就緒的通道的集合,從而能夠進行相應的處理。若是 要執行的處理比較複雜,能夠把處理轉發給其它的線程來執行。
public class IOWorker implements Runnable{
    @Override
    public void run() {
        try {
            Selector selector = Selector.open();
            ServerSocketChannel channel = ServerSocketChannel.open();
            channel.configureBlocking(false);
            ServerSocket socket = channel.socket();
            socket.bind(new InetSocketAddress("localhost",10800));
            channel.register(selector, channel.validOps());
            while(true){
                selector.select();
                Iterator iterator = selector.selectedKeys().iterator();
                while(!iterator.hasNext()){
                    SelectionKey key = (SelectionKey) iterator.next();
                    iterator.remove();
                    if(!key.isValid()){
                        continue;
                    }
                    if(key.isAcceptable()){
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        sc.register(selector, sc.validOps());
                    }
                    if(key.isWritable()){
                        SocketChannel client = (SocketChannel) key.channel();
                        Charset charset = Charset.forName("UTF-8");
                        CharsetEncoder encoder = charset.newEncoder();
                        CharBuffer charBuffer = CharBuffer.allocate(32);
                        charBuffer.put("Hello World");
                        charBuffer.flip();
                        ByteBuffer content = encoder.encode(charBuffer);
                        client.write(content);
                        key.cancel();
                    }
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
目前來講最流行的兩個Java NIO網絡應用 框架是Apache MINA和Netty。


9.Java安全
     安全性是Java應用程序的非功能性需求的重要組成部分,如同其它的非功能性需求 同樣,安全性很容易被開發人員所忽略。

9.1認證
     用戶認證是應用安全性的重要組成部分,其目的是確保應用的使用者具備合法的身 份。  

     Java安全中使用術語主體(Subject)來表示訪問請求的來源。一個主體能夠是 任何的實體。一個主體能夠有多個不一樣的身份標識(Principal)。好比一個應用的用 戶這類主體,就能夠有用戶名、身份證號碼和手機號碼等多種身份標識。除了身份 標識以外,一個主體還能夠有公開或是私有的安全相關的憑證(Credential),包括 密碼和密鑰等。

     典型的用戶認證過程是經過登陸操做來完成的。在登陸成功以後,一個主體中就具 備了相應的身份標識。Java提供了一個可擴展的登陸框架,使得應用開發人員能夠 很容易的定製和擴展與登陸相關的邏輯。登陸的過程由LoginContext啓動。在建立 LoginContext的時候須要指定一個登陸配置(Configuration)的名稱。該登陸配置中 包含了登陸所需的多個LoginModule的信息。每一個LoginModule實現了一種登陸方式。 當調用LoginContext的login方法的時候,所配置的每一個LoginModule會被調用來執行 登陸操做。若是整個登陸過程成功,則經過getSubject方法就能夠獲取到包含了身 份標識信息的主體。開發人員能夠實現本身的LoginModule來定製不一樣的登陸邏輯。


9.2權限控制
     在驗證了訪問請求來源的合法身份以後,另外一項工做是驗證其是否具備相應的權限。 權限由Permission及其子類來表示。
     每一個權限都有一個名稱,該名稱的含義與權限 類型相關。某些權限有與之對應的動做列表。

     比較典型的是文件操做權 FilePermission,它的名稱是文件的路徑,而它的動做列表則包括讀取、寫入和執 行等。Permission類中最重要的是implies方法,它定義了權限之間的包含關係,是 進行驗證的基礎。

    權限控制包括 管理和驗證兩個部分。
     管理指的是定義應用中的權限控制策略,而驗 證指的則是在運行時刻根據策略來判斷某次請求是否合法。

    策略由 Policy來表示,JDK提供了基於文件存儲的基本實現。 開發 人員也能夠提供本身的實現。
    ( 在應用運行過程當中,只可能有一個Policy處於生效的狀 態。)

     驗證部分的具體執行者是AccessController,其中的checkPermission方法用來驗 證給定的權限是否被容許。
    ( 在應用中執行相關的訪問請求以前,都須要調用 checkPermission方法來進行驗證。AccessControlException

     JVM中內置提供了一些對訪問關鍵部份內容的訪問 控制檢查,不過只有在啓動應用的時經過參數-Djava.security.manager啓用了安全管 理器以後才能生效,並與策略相配合。

     與訪問控制相關的另一個概念是特權動做。特權動做只關心動做自己所要求的權 限是否具有,而並不關心調用者是誰。

    特權動做根據是否 拋出受檢異常,分爲PrivilegedActionPrivilegedExceptionAction。這兩個接口都只 有一個run方法用來執行相關的動做,也能夠向調用者返回結果。經過 AccessController的doPrivileged方法就能夠執行特權動做。

     Java安全使用了保護域的概念。每一個保護域都包含一組類、身份標識和權限,其意 義是在當訪問請求的來源是這些身份標識的時候,這些類的實例就自動具備給定的 這些權限。

     ProtectionDomain類用來表示保護域,它的兩個構造方法分別用來支持靜態和動 態的權限。

9.3加密、解密與簽名
     構建安全的Java應用離不開加密和解密。Java的密碼框架採用了常見的服務提供者架 構,以提供所需的可擴展性和互操做性。該密碼框架提供了一系列經常使用的服務,包 括加密、數字簽名和報文摘要等。

     這些服務都有服務提供者接口(SPI),服務的實 現者只須要實現這些接口,並註冊到密碼框架中便可。

     好比加密服務Cipher的SPI 接口就是CipherSpi。每一個服務均可以有不一樣的算法來實現。
     密碼框架也提供了相應 的工廠方法用來獲取到服務的實例。
    ( 好比想使用採用MD5算法的報文摘要服務,只 須要調用MessageDigest.getInstance("MD5")便可。)

    加密和解密過程當中並不可少的就是密鑰(Key)。

     加密算法通常分紅對稱和非對稱 種。

    對稱加密算法使用同一個密鑰進行加密和解密;
     而非對稱加密算法使用一對 公鑰和私鑰,一個加密的時候,另一個就用來解密。
    
     不一樣的加密算法,有不一樣的 密鑰。對稱加密算法使用的是SecretKey,而非對稱加密算法則使用PublicKey
PrivateKey

     與密鑰Key對應的另外一個接口是KeySpec,用來描述不一樣算法的密鑰 的具體內容。
    好比一個典型的使用 對稱加密的方式以下:
KeyGenerator generator = KeyGenerator.getInstance("DES");
SecretKey key = generator.generateKey();
saveFile("key.data", key.getEncoded());
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
String text = "Hello World";
byte[] encrypted = cipher.doFinal(text.getBytes());
saveFile("encrypted.bin", encrypted);
    加密的時候首先要生成一個密鑰,再由Cipher服務來完成。能夠把密鑰的內容保存 起來,方便傳遞給須要解密的程序。
byte[] keyData = getData("key.data");
SecretKeySpec keySpec = new SecretKeySpec(keyData, "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] data = getData("encrypted.bin");
byte[] result = cipher.doFinal(data);
    
     解密的時候先從保存的文件中獲得密鑰編碼以後的內容,再經過 SecretKeySpec獲取 到密鑰自己的內容,再進行解密。

     報文摘要的目的在於防止信息被有意或無心的修改。經過對原始數據應用某些算 法,能夠獲得一個校驗碼。當收到數據以後,只須要應用一樣的算法,再比較校驗 碼是否一致,就能夠判斷數據是否被修改過。相對原始數據來講,校驗碼長度更小, 更容易進行比較。

     消息認證碼(Message Authentication Code)與報文摘要相似,不 同的是計算的過程當中加入了密鑰,只有掌握了密鑰的接收者才能驗證數據的完整性。

     使用公鑰和私鑰就能夠實現數字簽名的功能。某個發送者使用私鑰對消息進行加密, 接收者使用公鑰進行解密。因爲私鑰只有發送者知道,當接收者使用公鑰解密成功 以後,就能夠斷定消息的來源確定是特定的發送者。這就至關於發送者對消息進行 了簽名。

     數字簽名由Signature服務提供,簽名和驗證的過程都比較直接。

9.4安全套接字鏈接
     安全套接 字鏈接指的是對套接字鏈接進行加密。

     非對稱加密算法則適合於這種狀況。私鑰自 己保管,公鑰則公開出去。發送數據的時候,用私鑰加密,接收者用公開的公鑰解 密;接收數據的時候,則正好相反。這種作法解決了共享密鑰的問題,可是另外的 一個問題是如何確保接收者所獲得的公鑰確實來自所聲明的發送者,而不是僞造的。 爲此,又引入了證書的概念。
    
     證書中包含了身份標識和對應的公鑰。證書由用戶所 信任的機構簽發,並用該機構的私鑰來加密。在有些狀況下,某個證書籤發機構的 真實性會須要由另一個機構的證書來證實。經過這種證實關係,會造成一個證書 的鏈條。而鏈條的根則是公認的值得信任的機構。只有當證書鏈條上的全部證書都 被信任的時候,才能信任證書中所給出的公鑰。 

     平常開發中比較常接觸的就是HTTPS,即安全的HTTP鏈接。大部分用Java程序訪問 採用HTTPS網站時出現的錯誤都與證書鏈條相關。有些網站採用的不是由正規安全 機構簽發的證書,或是證書已通過期。若是必須訪問這樣的HTTPS網站的話,能夠 提供本身的套接字工廠和主機名驗證類來繞過去。

     另一種作法是經過keytool工具 把證書導入到系統的信任證書庫之中。
    
URL url = new URL("https://localhost:8443");
SSLContext context = SSLContext.getInstance("TLS");
context.init(new KeyManager[] {}, 
    new TrustManager[] {new  MyTrustManager()}, 
     new SecureRandom());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(context.getSocketFactory());
connection.setHostnameVerifier(new MyHostnameVerifier());


10.Java對象序列化與RMI
     對於一個存在於Java虛擬機中的對象來講,其內部的狀態只保持在內存中。JVM中止 以後,這些狀態就丟失了。在不少狀況下,對象的內部狀態是須要被持久化下來的。 提到持久化,最直接的作法是保存到文件系統或是數據庫之中。這種作法通常涉及 到自定義存儲格式以及繁瑣的數據轉換。

     對象關係映射(Object-relational mapping) 是一種典型的用關係數據庫來持久化對象的方式,也存在不少直接存儲對象的對象 數據庫。
    
     對象序列化機制(object serialization)是Java語言內建的一種對象持久化 方式,能夠很容易的在JVM中的活動對象和字節數組(流)之間進行轉換。除了可 以很簡單的實現持久化以外,序列化機制的另一個重要用途是在遠程方法調用 中,用來對開發人員屏蔽底層實現細節。

10.1基本對象序列化
     待序 列化的Java類只須要實現Serializable接口便可。

     實際的序列化和反序列化工做是經過ObjectOuputStream ObjectInputStream來完成的。

     ObjectOutputStream的writeObject方法能夠把一個 Java對象寫入到流中,
     ObjectInputStream的readObject方  法能夠從流中讀取一個Java 對象。

    ( 在寫入和讀取的時候,雖然用的參數或返回值是單個對象,但實際上操縱的 是一個對象圖,包括該對象所引用的其它對象,以  及這些對象所引用的另外的對象。 Java會自動幫你遍歷對象圖並逐個序列化。

     除了對象以外,Java中的基本類型和數組 也是能夠經過  ObjectOutputStream和ObjectInputStream來序列化的。

try {
User user = new User("Alex", "Cheng");
ObjectOutputStream output = new ObjectOutputStream(new  FileOutputStream("user.bin"));
output.writeObject(user);
output.close();
} catch (IOException e) {
  e.printStackTrace();
}
try {
ObjectInputStream input = new ObjectInputStream(new  FileInputStream("user.bin"));
User user = (User) input.readObject();
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}

上面的代碼給出了典型的把Java對象序列化以後保存到磁盤上,以及從磁盤上讀取 的基本方式。User類只是聲明瞭實現Serializable接口。

     在默認的序列化實現中,Java對象中的非靜態和非瞬時域都會被包括進來,而與域 的可見性聲明沒有關係。這可能會致使某些不該該出現的域被包含在序列化以後的 字節數組中,好比密碼等隱私信息

     因爲Java對象序列化以後的格式是固定的,其 它人能夠很容易的從中分析出其中的各類信息。

    對於這種狀況,一種解決辦法是把 域聲明爲瞬時的,即便用 transient 關鍵詞。

     另一種作法是添加一個 serialPersistentFields? 域來聲明序列化時要包含的域。
private static final ObjectStreamField[] serialPersistentFields = {
    new ObjectStreamField("firstName", String.class)
};

10.2自定義對象序列化
     基本的對象序列化機制讓開發人員能夠在包含哪些域上進行定製。若是想對序列化 的過程進行更加細粒度的控制,就須要在類中添加writeObject和對應的  readObject 方法。

10.3序列化時的對象替換
    在有些狀況下,可能會但願在序列化的時候使用另一個對象來代替當前對象。
     替換對 象的做用相似於Java EE中會使用到的傳輸對象(Transfer Object)。

    readResolve writeReplace

     在Order類的writeReplace方 法中返回了一個OrderReplace對象。這個對象會被做爲替代寫入到流中。一樣的,
須要在OrderReplace類中定義一個readResolve 方法,用來在讀取的時候再轉換回 Order類對象。這樣對調用者來講,替換對象的存在就是透明的。

10.4序列化對象的建立
     在經過ObjectInputStream的readObject方法讀取到一個對象以後,這個對象是一個 新的實例,可是其構造方法是沒有被調用的,其中的域的初始化代碼也沒有被執行。 對於那些沒有被序列化的域,在新建立出來的對象中的值都是默認的。也就是說, 這個對象從某種角度上來講是不完備的。這有可能會形成一些隱含的錯誤。調用者 並不知道對象是經過通常的new操做符來建立的,仍是經過反序列化所獲得的。解 決的辦法就是在類的readObject方法裏面,再執行所需的對象初始化邏輯。對於一 般的Java類來講,構造方法中包含了初始化的邏輯。能夠把這些邏輯提取到一個方
法中,在readObject方法中調用此方法

10.5序列化安全性
    對序列化以後的流進行加密。這能夠經過 CipherOutputStream來實現。
     實現本身的writeObject和readObject 方法,在調用defaultWriteObject以前,先對 要序列化的域的值進行加密處理。

     使用一個SignedObject或SealedObject來封裝當前對象,用SignedObject或 SealedObject進行序列化。
在從流中進行反序列化的時候,能夠經過ObjectInputStream的registerValidation方法 添加ObjectInputValidation接口的實現,用來驗證反序列化以後獲得的對象是否合 法。

     在從流中進行反序列化的時候,能夠經過ObjectInputStream的registerValidation方法 添加ObjectInputValidation接口的實現,用來驗證反序列化以後獲得的對象是否合 法。

10.6 RMI
     RMI(Remote Method Invocation)是Java中的遠程過程調用(Remote Procedure Call, RPC)實現,是一種分佈式Java應用的實現方式。它的目的在於對開發人員屏蔽橫跨 不一樣JVM和網絡鏈接等細節,使得分佈在不一樣JVM上的對  象像是存在於一個統一的 JVM中同樣,能夠很方便的互相通信。

     開發人員能夠基於Apache MINA或是Netty這樣的框架來寫本身的 網絡服務器,亦或是能夠採用REST架構風格來  編寫HTTP服務。
    ( 但這些解決方案中, 不可迴避的一個部分就是數據的編排和解排(marshal/unmarshal)

    RMI採用的是典型的客戶端-服務器端架構。
    
    首先須要定義的是服務器端的遠程接口,這一步是設計好服務器端須要提供什麼樣的服務。
    ( 對遠程接口的要求很簡單,只需 要繼承自RMI中的Remote接口便可。
          程接口中的方法須要拋出RemoteException。

     實現了遠程接口的類的實例稱爲遠程對象。建立出遠程對象以後,須要把它註冊到 一個註冊表之中。這是爲了客戶端可以找到該遠程對象並調用。

     爲了經過Java的序列化機制來進行傳輸,遠程接口中的方法的參數和返回值,要麼 是Java的基本類型,要麼是遠程對象,要麼是實現了  Serializable接口的Java類。

     當客 戶端經過RMI註冊表找到一個遠程接口的時候,所獲得的實際上是遠程接口的一個動 態代理對象。當客戶端調用  其中的方法的時候,方法的參數對象會在序列化以後, 傳輸到服務器端。服務器端接收到以後,進行反序列化獲得參數對象。並使用這些 參數對象,在服務器端調用  實際的方法。調用的返回值Java對象通過序列化以後, 再發送回客戶端。客戶端再通過反序列化以後獲得Java對象,返回給調用者。這中 間的序列化過程對於使用者來講是透明的,由動態代理對象自動完成。除了序列化 以外,RMI還使用了動態類加載技術。當須要進行反序列化的時候,若是該對象的
類定義在當前JVM中沒有找到,RMI會嘗試從遠端下載所需的類文件定義。能夠在 RMI程序啓動的時候,經過JVM參數java.rmi.server.codebase來指定動態下載Java類文 件的URL。


相關文章
相關標籤/搜索