2012年8月6日
版權信息: 可以任意轉載, 轉載時請務必以超鏈接形式標明文章原文出處, 即下面的聲明.
原文出處:http://blog.chenlb.com/2009/06/java-classloader-architecture.html
jvm classLoader architecture:
- Bootstrap ClassLoader/啟動類加載器
主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。 - Extension ClassLoader/擴展類加載器
主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。 - System ClassLoader/系統(tǒng)類加載器
主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。 - User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
在程序運行期間, 通過java.lang.ClassLoader的子類動態(tài)加載class文件, 體現(xiàn)java動態(tài)實時類裝入特性。
類加載器的特性:
- 每個ClassLoader都維護了一份自己的名稱空間, 同一個名稱空間里不能出現(xiàn)兩個同名的類。
- 為了實現(xiàn)java安全沙箱模型頂層的類加載器安全機制, java默認采用了 " 雙親委派的加載鏈 " 結構。

classloader-architecture

classloader-class-diagram
類圖中, BootstrapClassLoader是一個單獨的java類, 其實在這里, 不應該叫他是一個java類。因為,它已經(jīng)完全不用java實現(xiàn)了。它是在jvm啟動時, 就被構造起來的, 負責java平臺核心庫。
自定義類加載器加載一個類的步驟

classloader-load-class
ClassLoader 類加載邏輯分析, 以下邏輯是除 BootstrapClassLoader 外的類加載器加載流程:
- // 檢查類是否已被裝載過
- Class c = findLoadedClass(name);
- if (c == null ) {
- // 指定類未被裝載過
- try {
- if (parent != null ) {
- // 如果父類加載器不為空, 則委派給父類加載
- c = parent.loadClass(name, false );
- } else {
- // 如果父類加載器為空, 則委派給啟動類加載加載
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // 啟動類加載器或父類加載器拋出異常后, 當前類加載器將其
- // 捕獲, 并通過findClass方法, 由自身加載
- c = findClass(name);
- }
- }
線程上下文類加載器
java默認的線程上下文類加載器是 系統(tǒng)類加載器(AppClassLoader)。
- // Now create the class loader to use to launch the application
- try {
- loader = AppClassLoader.getAppClassLoader(extcl);
- } catch (IOException e) {
- throw new InternalError(
- "Could not create application class loader" );
- }
-
- // Also set the context class loader for the primordial thread.
- Thread.currentThread().setContextClassLoader(loader);
以上代碼摘自sun.misc.Launch的無參構造函數(shù)Launch()。
使用線程上下文類加載器, 可以在執(zhí)行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文里的類加載器加載類.
典型的例子有, 通過線程上下文來加載第三方庫jndi實現(xiàn), 而不依賴于雙親委派.
大部分java app服務器(jboss, tomcat..)也是采用contextClassLoader來處理web服務。
還有一些采用 hotswap 特性的框架, 也使用了線程上下文類加載器, 比如 seasar (full stack framework in japenese).
線程上下文從根本解決了一般應用不能違背雙親委派模式的問題.
使java類加載體系顯得更靈活.
隨著多核時代的來臨, 相信多線程開發(fā)將會越來越多地進入程序員的實際編碼過程中. 因此,
在編寫基礎設施時, 通過使用線程上下文來加載類, 應該是一個很好的選擇。
當然, 好東西都有利弊. 使用線程上下文加載類, 也要注意, 保證多根需要通信的線程間的類加載器應該是同一個,
防止因為不同的類加載器, 導致類型轉換異常(ClassCastException)。
為什么要使用這種雙親委托模式呢?
- 因為這樣可以避免重復加載,當父親已經(jīng)加載了該類的時候,就沒有必要子ClassLoader再加載一次。
- 考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態(tài)替代java核心api中定義類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經(jīng)在啟動時被加載,所以用戶自定義類是無法加載一個自定義的ClassLoader。
java動態(tài)載入class的兩種方式:
- implicit隱式,即利用實例化才載入的特性來動態(tài)載入class
- explicit顯式方式,又分兩種方式:
- java.lang.Class的forName()方法
- java.lang.ClassLoader的loadClass()方法
用Class.forName加載類
Class.forName使用的是被調用者的類加載器來加載類的。
這種特性, 證明了java類加載器中的名稱空間是唯一的, 不會相互干擾。
即在一般情況下, 保證同一個類中所關聯(lián)的其他類都是由當前類的類加載器所加載的。
- public static Class forName(String className)
- throws ClassNotFoundException {
- return forName0(className, true , ClassLoader.getCallerClassLoader());
- }
-
- /** Called after security checks have been made. */
- private static native Class forName0(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException;
上面中 ClassLoader.getCallerClassLoader 就是得到調用當前forName方法的類的類加載器
static塊在什么時候執(zhí)行?
- 當調用forName(String)載入class時執(zhí)行,如果調用ClassLoader.loadClass并不會執(zhí)行.forName(String,false,ClassLoader)時也不會執(zhí)行.
- 如果載入Class時沒有執(zhí)行static塊則在第一次實例化時執(zhí)行.比如new ,Class.newInstance()操作
- static塊僅執(zhí)行一次
各個java類由哪些classLoader加載?
- java類可以通過實例.getClass.getClassLoader()得知
- 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()獲得實例)載入
- ClassLoader類由bootstrap loader載入
NoClassDefFoundError和ClassNotFoundException
- NoClassDefFoundError:當java源文件已編譯成.class文件,但是ClassLoader在運行期間在其搜尋路徑load某個類時,沒有找到.class文件則報這個錯
- ClassNotFoundException:試圖通過一個String變量來創(chuàng)建一個Class類時不成功則拋出這個異常
一:使用場景
1)使用的地方:樹形結構,分支結構等
2)使用的好處:降低客戶端的使用,為了達到元件與組合件使用的一致性,增加了元件的編碼
3)使用后的壞處:代碼不容易理解,需要你認真去研究,發(fā)現(xiàn)元件與組合件是怎么組合的
二:一個實際的例子
畫圖形,這個模式,稍微要難理解一點,有了例子就說明了一切,我畫的圖是用接口做的,代碼實現(xiàn)是抽象類為基類,你自己選擇了,接口也可以。

1)先建立圖形元件
package com.mike.pattern.structure.composite;
/**
* 圖形元件
*
* @author taoyu
*
* @since 2010-6-23
*/
public abstract class Graph {
/**圖形名稱*/
protected String name;
public Graph(String name){
this.name=name;
}
/**畫圖*/
public abstract void draw()throws GraphException;
/**添加圖形*/
public abstract void add(Graph graph)throws GraphException;
/**移掉圖形*/
public abstract void remove(Graph graph)throws GraphException;
}
2)建立基礎圖形圓
package com.mike.pattern.structure.composite;
import static com.mike.util.Print.print;
/**
* 圓圖形
*
* @author taoyu
*
* @since 2010-6-23
*/
public class Circle extends Graph {
public Circle(String name){
super(name);
}
/**
* 圓添加圖形
* @throws GraphException
*/
@Override
public void add(Graph graph) throws GraphException {
throw new GraphException("圓是基礎圖形,不能添加");
}
/**
* 圓畫圖
*/
@Override
public void draw()throws GraphException {
print(name+"畫好了");
}
/**
* 圓移掉圖形
*/
@Override
public void remove(Graph graph)throws GraphException {
throw new GraphException("圓是基礎圖形,不能移掉");
}
}
3)建立基礎圖形長方形
package com.mike.pattern.structure.composite;
import static com.mike.util.Print.print;
/**
* 長方形
*
* @author taoyu
*
* @since 2010-6-23
*/
public class Rectangle extends Graph {
public Rectangle(String name){
super(name);
}
/**
* 長方形添加
*/
@Override
public void add(Graph graph) throws GraphException {
throw new GraphException("長方形是基礎圖形,不能添加");
}
/**
* 畫長方形
*/
@Override
public void draw() throws GraphException {
print(name+"畫好了");
}
@Override
public void remove(Graph graph) throws GraphException {
throw new GraphException("長方形是基礎圖形,不能移掉");
}
}
4)最后簡歷組合圖形
package com.mike.pattern.structure.composite;
import java.util.ArrayList;
import java.util.List;
import static com.mike.util.Print.print;
/**
* 圖形組合體
*
* @author taoyu
*
* @since 2010-6-23
*/
public class Picture extends Graph {
private List<Graph> graphs;
public Picture(String name){
super(name);
/**默認是10個長度*/
graphs=new ArrayList<Graph>();
}
/**
* 添加圖形元件
*/
@Override
public void add(Graph graph) throws GraphException {
graphs.add(graph);
}
/**
* 圖形元件畫圖
*/
@Override
public void draw() throws GraphException {
print("圖形容器:"+name+" 開始創(chuàng)建");
for(Graph g : graphs){
g.draw();
}
}
/**
* 圖形元件移掉圖形元件
*/
@Override
public void remove(Graph graph) throws GraphException {
graphs.remove(graph);
}
}
5)最后測試
public static void main(String[] args)throws GraphException {
/**畫一個圓,圓里包含一個圓和長方形*/
Picture picture=new Picture("立方體圓");
picture.add(new Circle("圓"));
picture.add(new Rectangle("長方形"));
Picture root=new Picture("怪物圖形");
root.add(new Circle("圓"));
root.add(picture);
root.draw();
}
6)使用心得:的確降低了客戶端的使用情況,讓整個圖形可控了,當是你要深入去理解,才真名明白采用該模式的含義,不太容易理解。
一:使用場景
1)使用的地方:我想使用兩個不同類的方法,這個時候你需要把它們組合起來使用
2)目前使用的情況:我會把兩個類用戶組合的方式放到一起,編程思想think in java里已經(jīng)提到個,能盡量用組合就用組合,繼承一般考慮再后。
3)使用后的好處:你不需要改動以前的代碼,只是新封裝了一新類,由這個類來提供兩個類的方法,這個時候:一定會想到facade外觀模式,本來是多個類使用的情況,我新封裝成一個類來使用,而這個類我采用組合的方式來包裝新的方法。我的理解是,設計模式本身就是為了幫助解決特定的業(yè)務場景而故意把模式劃分對應的模式類別,其實大多數(shù)情況,都解決了同樣的問題,這個時候其實沒有必要過多的糾纏到模式的名字上了,你有好的注意,你甚至取一個新的名字來概括這樣的使用場景。
4)使用的壞處:適配器模式,有兩種方式來實現(xiàn)。一個是組合一個是繼承,我覺得,首先應該考慮組合,能用組合就不要用繼承,這是第一個。第二個,你采用繼承來實現(xiàn),那肯定會加大繼承樹結構,如果你的繼承關系本身就很復雜了,這肯定會加大繼承關系的維護,不有利于代碼的理解,或則更加繁瑣。繼承是為了解決重用的為題而出現(xiàn)的,所以我覺得不應該濫用繼承,有機會可以考慮同樣別的方案。
二:一個實際的例子
關聯(lián)營銷的例子,用戶購買完商品后,我又推薦他相關別的商品
由于減少代碼,方法我都不采用接口,直接由類來提供,代碼只是一個范例而已,都精簡了。
1)創(chuàng)建訂單信息
public class Order {
private Long orderId;
private String nickName;
public Order(Long orderId,String nickName){
this.orderId=orderId;
this.nickName=nickName;
}
/**
* 用戶下訂單
*/
public void insertOrder(){
}
}
2)商品信息
public class Auction {
/**商品名稱*/
private String name;
/**制造商*/
private String company;
/**制造日期*/
private Date date;
public Auction(String name,String company, Date date){
this.name=name;
this.company=company;
this.date=date;
}
/**
* 推廣的商品列表
*/
public void commendAuction(){
}
}
3)購物
public class Trade {
/**用戶訂單*/
private Order order;
/**商品信息*/
private Auction auction;
public Trade(Order order ,Auction auction){
this.order=order;
this.auction=auction;
}
/**
* 用戶產(chǎn)生訂單以及后續(xù)的事情
*/
public void trade(){
/**下訂單*/
order.insertOrder();
/**關聯(lián)推薦相關的商品*/
auction.commendAuction();
}
}
4)使用心得:其實外面采用了很多繼承的方式,order繼承auction之后,利用super .inserOrder()再加一個auction.recommendAuction(),實際上大同小異,我到覺得采用組合更容易理解以及代碼更加優(yōu)美點。
一:使用場景
1)使用到的地方:如果你想創(chuàng)建類似汽車這樣的對象,首先要創(chuàng)建輪子,玻璃,桌椅,發(fā)動機,外廓等,這些部件都創(chuàng)建好后,最后創(chuàng)建汽車成品,部件的創(chuàng)建和汽車的組裝過程本身都很復雜的情況,希望把部件的創(chuàng)建和成品的組裝分開來做,這樣把要做的事情分割開來,降低對象實現(xiàn)的復雜度,也降低以后成本的維護,把汽車的部件創(chuàng)建和組裝過程獨立出兩個對應的工廠來做,有點類似建立兩個對應的部件創(chuàng)建工廠和汽車組裝工廠兩個工廠,而工廠只是創(chuàng)建一個成品,并沒有把里面的步驟也獨立出來,應該說Builder模式比工廠模式又進了一步。
2)采用Builder模式后的好處:把一個負責的對象的創(chuàng)建過程分解,把一個對象的創(chuàng)建分成兩個對象來負責創(chuàng)建,代碼更有利于維護,可擴性比較好。
3)采用Builder模式后的壞處:實現(xiàn)起來,對應的接口以及部件的對象的創(chuàng)建比較多,代碼相對來講,比較多了,估計剛開始你會有點暈,這個可以考慮代碼精簡的問題,增加代碼的可讀性。
二:一個實際的例子
汽車的組裝
1)首先創(chuàng)建汽車這個成品對象,包含什么的成員
public class Car implements Serializable{
/**
* 汽車序列號
*/
private static final long serialVersionUID = 1L;
/**汽車輪子*/
private Wheel wheel;
/**汽車發(fā)動機*/
private Engine engine;
/**汽車玻璃*/
private Glass glass;
/**汽車座椅*/
private Chair chair;
public Wheel getWheel() {
return wheel;
}
public void setWheel(Wheel wheel) {
this.wheel = wheel;
}
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Glass getGlass() {
return glass;
}
public void setGlass(Glass glass) {
this.glass = glass;
}
public Chair getChair() {
return chair;
}
public void setChair(Chair chair) {
this.chair = chair;
}
}
2)創(chuàng)建對應汽車零部件
public class Wheel {
public Wheel(){
print("--汽車輪子構建完畢--");
}
}
public class Engine {
public Engine(){
print("--汽車發(fā)動機構建完畢--");
}
}
public class Glass {
public Glass(){
print("--汽車玻璃構建完畢--");
}
}
public class Chair {
public Chair(){
print("--汽車座椅構建完畢--");
}
}
3)開始重點了,汽車成品的組裝過程
public interface Builder {
/**組裝汽車輪子*/
public void buildWheel();
/**組裝汽車發(fā)動機*/
public void buildEngine();
/**組裝汽車玻璃*/
public void buildGlass();
/**組裝汽車座椅*/
public void buildChair();
/**返回組裝好的汽車*/
public Car getCar();
}
以及實現(xiàn)類
public class CarBuilder implements Builder {
/**汽車成品*/
private Car car;
public CarBuilder(){
car=new Car();
}
/**組裝汽車輪子*/
@Override
public void buildChair() {
car.setChair(new Chair());
}
/**組裝汽車發(fā)動機*/
@Override
public void buildEngine() {
car.setEngine(new Engine());
}
/**組裝汽車玻璃*/
@Override
public void buildGlass() {
car.setGlass(new Glass());
}
/**組裝汽車座椅*/
@Override
public void buildWheel() {
car.setWheel(new Wheel());
}
/**返回組裝好的汽車*/
@Override
public Car getCar() {
buildChair();
buildEngine();
buildGlass();
buildWheel();
print("--整個汽車構建完畢--");
return car;
}
}
4)最后汽車創(chuàng)建測試
public static void main(String[] args) {
/**創(chuàng)建汽車組裝*/
Builder carBuilder=new CarBuilder();
Car car=carBuilder.getCar();
}
最后輸出:
--汽車座椅構建完畢--
--汽車發(fā)動機構建完畢--
--汽車玻璃構建完畢--
--汽車輪子構建完畢--
--整個汽車構建完畢--
5)體會心得:Builder模式實際的重點就把汽車的組裝過程和零部件的生產(chǎn)分開來實現(xiàn),零部件的生成主要靠自己的對象來實現(xiàn),我上面只是在構造函數(shù)里創(chuàng)建了,比較簡單,而重點汽車的組裝則交給CarBuilder來實現(xiàn),最終由builder來先負責零部件的創(chuàng)建,最后返回出成品的汽車。
一:使用場景
1)經(jīng)常使用的地方:一個類只有一個實例,eg:頁面訪問統(tǒng)計pv,統(tǒng)計的個數(shù)就只能保證一個實例的統(tǒng)計。
2)我們目前使用的情況:比如我想創(chuàng)建一個對象,這個對象希望只有一份實例的維護,在內存的保存也只有一份,也就是在同一個jvm的java堆里只保存一份實例對象,所以你會想一辦法,在創(chuàng)建這個對象的時候,就已經(jīng)能保證只有一份。
3)怎么改進:定義該對象的時候,就保證是同一份實例,比如:定義為私有構造函數(shù),防止通過new的方式可以創(chuàng)建對象,然后在對象里定義一個靜態(tài)的私有成員(本身對象的一個實例),然后再創(chuàng)建一個外面訪問該對象的方法就好了。
4)改進的好處:代碼在編譯代碼這個級別就被控制了,不至于在jvm里運行的時候才來保證,把唯一實例的創(chuàng)建保證在編譯階段;jvm里內存只有一份,從而內存占有率更低,以及更方便java垃圾回收
5)改進后的壞處:只能是代碼稍微需要更多點,其實大家最后發(fā)現(xiàn)改進后的壞處,都是代碼定義比之間要多一點,但以后的維護代碼就降下來了,也短暫的代碼量偏大來換取以后代碼的精簡。
二:一個實際的例子
總體的例子
package com.mike.pattern.singleton;
/**
* 總統(tǒng)
*
* @author taoyu
*
* @since 2010-6-22
*/
public class President {
private President(){
System.out.println("總統(tǒng)已經(jīng)選舉出來了");
}
/**總統(tǒng)只有一個*/
private static President president=new President();
/**
* 返回總統(tǒng)
*/
public static President getPresident(){
return president;
}
/**
* 總統(tǒng)宣布選舉成功
*/
public void announce(){
System.out.println("偉大的中國人民,我將成你們新的總統(tǒng)");
}
}
/**
* @param args
*/
public static void main(String[] args) {
President president=President.getPresident();
president.announce();
}
1.使用場景
1)子類過多,不容易管理;構造對象過程過長;精簡代碼創(chuàng)建;
2)目前我們代碼情況: 編寫代碼的時候,我們經(jīng)常都在new對象,創(chuàng)建一個個的對象,而且還有很多麻煩的創(chuàng)建方式,eg:HashMap<String,Float> grade=new HashMap<String,Float>(),這樣的代碼創(chuàng)建方式太冗長了,難道你沒有想過把這個創(chuàng)建變的短一點么,比如:HashMap<String,Float>grade=HashMapFactory.new(),可以把你創(chuàng)建精簡一點;你也可以還有別的需求,在創(chuàng)建對象的時候,你需要不同的情況,創(chuàng)建統(tǒng)一種類別的對象,eg:我想生成不同的汽車,創(chuàng)建小轎車,創(chuàng)建卡車,創(chuàng)建公交汽車等等,都屬于同種類別:汽車,你難道沒有想過,我把這些創(chuàng)建的對象在一個工廠里來負責創(chuàng)建,我把創(chuàng)建分開化,交給一人來負責,這樣可以讓代碼更加容易管理,創(chuàng)建方式也可以簡單點。
比如:Car BMW=CarFactory.create(bmw); 把創(chuàng)建new由一個統(tǒng)一負責,這樣管理起來相當方便
3)怎么改進:這個時候,你會想到,創(chuàng)建這樣同類別的東西,我把這個權利分出去,讓一個人來單獨管理,它只負責創(chuàng)建我的對象這個事情,所以你單獨簡歷一個對象來創(chuàng)建同類的對象,這個時候,你想這個東西有點像工廠一樣,生成同樣的產(chǎn)品,所以取了個名字:工廠模式,顧名思義,只負責對象的創(chuàng)建
4)改進后的好處:代碼更加容易管理了,代碼的創(chuàng)建要簡潔很多。
5)改進后的壞處:那就是你需要單獨加一個工廠對象來負責創(chuàng)建,多需要寫點代碼。
2.一個實際的例子
創(chuàng)建寶馬汽車與奔馳汽車的例子
1)先提取出一個汽車的公用接口Car
public interface Car{
/**行駛*/
public void drive();
}
2)寶馬和奔馳汽車對象
public class BMWCar implements Car {
/**
* 汽車發(fā)動
*/
public void drive(){
System.out.println("BMW Car drive");
}
}
public class BengCar implements Car {
/**
* 汽車發(fā)動
*/
public void drive(){
System.out.println("BengChi Care drive");
}
}
3)單獨一個汽車工廠來負責創(chuàng)建
public class FactoryCar {
/**
* 制造汽車
*
* @param company 汽車公司
* @return 汽車
* @throws CreateCarException 制造汽車失敗異常
*/
public static Car createCar(Company company)throws CreateCarException{
if(company==Company.BMW){
return new BMWCar();
}else if(company==Company.Beng){
return new BengCar();
}
return null;
}
}
4)最后的代碼實現(xiàn):
Car BMWCar=FactoryCar.createCar(Company.BMW);
BMWCar.drive();
1. 我說下我對設計模式的理解:任何一樣事物都是因為有需求的驅動才誕生的,所以設計模式也不例外,我們平時在編寫代碼的時候,隨著時間的深入,發(fā)現(xiàn)很多代碼很難維護,可擴展性級差,以及代碼的效率也比較低,這個時候你肯定會想辦法讓代碼變的優(yōu)美又能解決你項目中的問題,所以在面向對象語言里,你肯定會去發(fā)現(xiàn)很多可以重用的公用的方法,比如:接口的存在,你自然就想到了,讓你定義的方法與你的實現(xiàn)分開,也可以很方便把不同的類與接口匹配起來,形成了一個公用的接口,你會發(fā)現(xiàn)這樣做,好處會是非常多的,解決了你平時想把代碼的申明與邏輯實現(xiàn)的分開。
2. 這個時候,你發(fā)現(xiàn)了,本身面向對象的語言里,已經(jīng)暗藏了很多好處,你肯定會仔細去分析面向對象這個語言,認真去挖掘里面更多的奧秘,最后,你發(fā)現(xiàn)了,原來你可以把面向對象的特性提取成一個公用的實現(xiàn)案例,這些案例里能幫助你解決你平時編寫代碼的困擾,而這樣一群人,就是所謂gof的成員,他們從平時設計建筑方面找到了靈感,建筑的設計也可以公用化以及重用化,所以他們也提取了相關的軟件設計方面的公用案例,也就有了下面的相關的所謂23種設計模式,而里面這么多模式,你也可以把他們歸類起來,最后發(fā)現(xiàn)就幾類模式:創(chuàng)建,結構,行為等模式類別,而這些現(xiàn)成的方案,也可以在實際應用中充分發(fā)揮作用,隨著大家的使用以及理解,發(fā)現(xiàn)其實這些所謂的模式里,你的確可以讓你的代碼變的更加優(yōu)美與簡練。
3. 我比較喜歡把代碼變的更加優(yōu)美與簡練,優(yōu)美的代碼就是一看就懂,結構很清晰,而簡歷就是一目了然,又可以解決你的問題,就是代碼又少效率又高,所以平時要養(yǎng)成寫java doc的習慣,這樣的代碼才為清晰,所以才會更加優(yōu)美。
4. 這些就是我對設計模式的理解,所以這么好的寶貝,我們不去深入的了解,的確可惜了,這就叫站到巨人的肩膀上.....