JAVA學習筆記及知識積累

世有因果知因求果發表於2018-09-28

 為什麼說Java具有跨平臺特性?

我們知道計算機只認識1,0兩種電平的訊號,所有資訊或者計算指令最終都編碼成16進位制的機器碼,這些機器碼作為程式儲存於計算機的記憶體中,由CPU去單個取指令執行直到程式執行完畢。然而計算機能認識的這些機器碼確實不是人類善於處理的,因此人們發明了組合語言,隨後使用匯編器(assembler)翻譯成為機器碼;再隨後貝爾實驗室發明了C語言,這個就是人類能夠理解並創造的高階程式了。同樣地,要在CPU上執行,我們必須翻譯成機器碼,這個由編譯器來完成。我們來看下面一句程式:

printf(1+2);

 

 

在x86 intel機器上翻譯成機器碼就是 101000101010 ,intel x86處理器執行這個程式碼,其中也包含了系統呼叫的機器碼,在這裡有兩個最關鍵的東西:1.intel處理器,2.作業系統。這兩者的組合我們就稱為platform.顯然對應不同的處理器和不同的作業系統會有不同的排列組合。對於我們應用開發者來說,我們當然希望我們的應用程式可以很方便地在不同平臺上執行。那我們來看看,有哪些方案:

1. 針對不同的處理器彙編問題,我們需要購買host在windows下的針對不同target cpu的彙編器負責轉換出能夠target到不同cpu的機器碼;這個過程叫做交叉編譯。並且不同的cpu輸出不同的機器碼檔案,不能混用

2. 針對不同的作業系統,我們printf做的系統呼叫也會有所不同

 

但是我們要知道的是交叉編譯並且針對不同OS特性做不同的適配,本身就是非常複雜昂貴的,不是每個高階語言開發工程師都能勝任的。這就有了java出現的機會,我們來看看java平臺是如何解決這個問題的。

java平臺中有一個非常重要的模組: Java Virtual Machine,所有預編譯過的bytecode就在這個虛擬機器上執行。詳細過程如下:

 

1. printf(1+2)這個程式碼用java就是System.out.println(1+2),該文字儲存為.java檔案;

2.使用java編譯器將這個.java檔案轉換成稱為bytecode的中間程式碼,輸出為.class檔案;

3.這個.class內容本身並不和任何特定平臺相關,也就是說任何平臺本身是無法直接執行的;

4.這個虛擬機器駐留在作業系統的記憶體中,當虛擬機器被喂入這些bytecode時,jvm會識別到我們正在工作的具體平臺並且將bytecode最終轉換為native machine code

這樣你的程式碼只要編譯一次,就能在所有平臺上執行!!

因此, java既是一個程式語言,更是一個平臺。

其他的程式語言,比如C語言,編譯器產生targeted到特定平臺的機器碼,比如wintel,比如linux+intel等,而java compiler只會將源程式編譯target到Java Virtual Machine. Bytecode是host system和java source的橋樑

https://www.guru99.com/java-virtual-machine-jvm.html

 

Maven是什麼以及Maven,IDE,Mark,Ant對比

Maven是一個java的構建工具,類似於C語言的make,同時Maven也是一個依賴管理的工具。

In short, Archetype is a Maven project templating toolkit. An archetype is defined as an original pattern or model from which all other things of the same kind are made. The name fits as we are trying to provide a system that provides a consistent means of generating Maven projects. Archetype will help authors create Maven project templates for users, and provides users with the means to generate parameterized versions of those project templates.

Maven archetype

maven提供了很多工程模版,當建立一個新專案時,就可以使用這些模版,自動建立配置好對應的環境

IDE對比:

Eclipse, IntelliJ Idea, myEclips等,我比較喜歡Idea,特別地,java for android新的正式工具也是基於idea設計的。寫程式碼超級爽.使用Idea可以開啟一個j2ee hello world程式來學習

JAVA SE/Java EE/ Java ME/JavaFX/Java Card/Java TV/Java DB

java主要分為3大平臺:

java SE (J2SE)= standard edition:這是核心的java程式設計平臺,包含了java.lang,java.io,java.math,java.net,java.util等通用的庫和API。主要用於桌面應用開發

java ee (J2EE)= enterprise edition: 在SE基礎上,增加了用於部署高容錯,分散式,多層級的java軟體的庫,這些基本上都以模組化的元件模式執行在application server上。也就是說,如果你的應用會形成巨大規模,分散式的系統,那麼你應該考慮JAVA EE。它還提供包括資料庫訪問(JDBC,JPA),遠端函式呼叫RMI,訊息(JMI),web services, XML解析,並且定義了企業級的JavaBeans, servlets, portlets, Java Server Pages等標準API。主要用於web(網路)應用開發

java me(J2ME) = mico edition.用於開發mobile device上的應用,嵌入於類似機頂盒的裝置中。主要用於手機應用

jdk變遷歷史

JAVA程式的編譯和執行過程

JAVA執行環境(JRE)

JRE = Java Runtime Environment = JVM + API(Lib)

JRE執行程式時的三項主要功能:由class loader來載入程式碼,由bytecode verifier來校驗程式碼,由runtime interpreter來執行程式碼

一句話:由虛擬機器來裝載編譯好的應用程式並且呼叫相應的指令具體地執行。虛擬機器可以理解為在實際的wintel, linux/intel平臺上虛擬出一個新的機器,有他自己的指令系統,作業系統API介面,對下會匹配到不同的平臺,對上展示的介面是相同的,因此具有跨平臺的特徵

JAVA開發工具包(JDK)

JDK = JRE+ Tools = JVM + API + Tools 

JDK提供以下主要的開發工具:

  • java編譯器: javac.exe
  • java執行器: java.exe
  • 文件生成器: javadoc.exe
  • java打包器: jar.exe
  • java偵錯程式:jdb.exe
  • javaw:執行圖形介面程式,並且不啟控制檯
  • javap:檢視類資訊及反彙編

javap反彙編後形成的jvm位元組碼指令和源程式的對應關係:

public class Main {

    public static void main(String[] args) {
        System.out.println("hello, world");
    }
}

J:\eclipse-workspace\TType>javap -c out\production\TType\Main.class
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String hello, world
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

 

物件導向設計的要點:

  • 將客觀世界中的各種物件對映為程式中的一個物件
  • 程式的分析和設計都圍繞著:
    • 分析有哪些物件類
    • 每個類都有哪些屬性,哪些方法
    • 類之間的關係:繼承關係和關聯關係(比如人有一個手錶,老師屬於一個學校,屬於一個院系)
    • 物件之間互相呼叫

JAVA Application .VS. Applet

兩者的執行環境不同,application是獨立的程式,需要執行器(呼叫虛擬機器)來執行;而applet則是嵌在HTML頁面中的非獨立程式,由專門的appletViewer來執行,或者由web瀏覽器呼叫JAVA虛擬機器來執行.applet是java最早提出的,是對網頁web技術發展的重大改進,從此網頁變得豐富,可互動了。要在網頁中執行applet必須在系統中有JRE安裝,並且要在瀏覽器中使能java,我們需要將.class以及.html檔案放到www伺服器上,然後用瀏覽器訪問。從java8開始, applet的執行就收到更加嚴格的限制。

applet替代方案有flash, silverlight等。甚至現在隨著javascript富體驗方案以及HTML5提供的豐富功能引入,applet已經慢慢退出歷史舞臺.

.jar包

.jar是一系列java的類位元組檔案打包生成的壓縮檔案,jar包伴隨一個非常重要的是清單檔案(mainifest),指出這個jar包的入口類是哪一個

要使用jar包一般經過:編譯->打包->執行三個階段

javac A.java
jar cvfm A.jar manifest.mf A.class
java -jar A.jar

 輸入與輸出

application輸入輸出可以是文字介面,也可以圖形介面,而applet則只能是圖形介面

文字介面的輸入輸出: java.util.Scanner類

圖形介面輸入輸出:初始化一個frame,在這個frame中add一些控制元件,控制元件中新增事件處理

Java資料型別劃分

基本資料型別:byte,short,int,long,float,double,char,boolean存在棧裡面

引用資料型別:class,interface,陣列:資料本身存在堆裡面,但是指標存在棧裡面

記憶體區間之常量池StringBuffer

看下面的程式碼, 由於常量是存在方法區中的常量池中,值相等的常量就是同一個常量,而字串"aaa"本質上就是一個“常量”.但是如果通過new String方式建立的字串則存在於系統堆中,區域性變數僅僅是指向了堆中分配的記憶體而已。由於字串本質上是一個常量,而我們經常需要做字串拼接賦值,每一次這類操作都會產生一個字串副本,效率低下。為解決這個問題,需要使用StringBuffer,也就是可以變更的string。

public static void main(String[] args) {
        String s1= "aaa";
        String s2="aaa";
        System.out.println(s1==s2); // true
        String s3 = new String("aaa");
        String s4 = new String("aaa");
        System.out.println(s3==s4); // false
        System.out.println(s3.equals(s4)); // true
        System.out.println(s3==s2);// false

        String s5 = "aaa";
        String s6 = s5;
        s6 = s6 + "bbb";
        // 字串是不可變的,每次修改本質上都是建立了一個副本
        System.out.println(s5==s6); // false
        StringBuffer s7 = new StringBuffer("aaa");
        StringBuffer s8 = s7;
        s7.append("bbb");
        System.out.println(s7==s8); // true
    }

package

package實際上為了解決名字空間,名字衝突,類似php的名稱空間,需要和對應儲存路徑相對應

 

package pkg1[.pkg2]

 

根目錄由classpath環境變數來確定,如果沒有package語句,則是default package.

java jdk實際上提供了很多包,比如: java.applet,java.awt,java.awt.image,java.net,java.util等

import語句

為了使用java中提供的類,需要用import語句來匯入所需要的類。

import java.util.Date

匯入類後就可以直接使用Date而不用加package的字首了。

package和物理目錄之間的關係

http://www.cnblogs.com/moveofgod/p/3809653.html

package和jar包

第三方的Package一般都以jar包形式來發布。我們知道jar包實際上是包含了一堆類位元組碼的壓縮包。我們要在專案中使用,首先得引入jar包,並將對應jar包加到對應的build path,其本質是classpath加入jar包,這樣在java程式碼中import 第三方jar包中的類時,jvm就能找到對應的類位元組碼並載入執行。

https://blog.csdn.net/zhenyusoso/article/details/6174834

 

編譯和執行包中的類

javac -d . pk\*.java
java pk.TestPkg  /*pk是package, TestPkg是包含main的class*/

java訪問許可權控制修飾符: public/protected/private/預設

需要注意的是,預設如果沒有任何修飾符,則成員在同一個包中可以訪問.首先要看是否類為public,也就是說是否可以在其他package中能夠訪問該類,只有有權訪問類,再談能否訪問該類的方法。

public class PubClass{
defaultFunction(){} // 預設可以被包內訪問
privateFunction(){} // 只能在本類中訪問
protectedFunction(){} // 在子類中也能夠訪問
publicFunction(){} // 任何地方都能夠訪問
}

private成員的setter/getter(setAttribute/getAttribute)

對於私有的成員,我們一般通過提供setter/getter函式提供讀和/或寫,好處是能夠做合法檢查,做量剛的變換等。。如果不提供setAttribute方法則該屬性就是隻讀的

static/final/abstract修飾符

非常常見的,比如System.in和System.out其中in和out就是在System這個類中定義的static變數,因此任何時候都可以訪問。

static如修飾方法,則該方法屬於類,不被任何類所專有,在呼叫該static方法時,無須事先例項化一個物件

同樣,如果沒有static關鍵字則方法會屬於特定的物件。由於static方法屬於類,而不屬於物件,因此static方法不能操作屬於類例項的成員變數!static方法不能使用this或super

final類表示不可被繼承,final方法表示不可被子類覆蓋overwrite的方法,final欄位表示一旦初始化就不可改變!

static final修飾某個欄位時,該欄位為常量,比如Math.PI, integer.MAX_VALUE都是static final型別的成員變數

abstract方法必須在子類中實現, abstract類不能被初始化

介面(interface)

介面就是某種特殊的約定,介面可以被多個類來實現,軟體工程趨勢於:面向介面的程式設計,而不是面向實現。

通過介面可以實現不相關的類的相同的行為,而無需考慮這些類之間的層次關係。某種意義上實現了多重繼承

介面和類的繼承層次無關,同一個介面可以被不同的類來實現。

比如上面這個圖中: Flyable這個介面定義了takeoff,fly,land三個介面方法,該介面被Airplane, Bird, Superman三個類來implement了,但是這三個類分別繼承於Vehicle, Animal這兩個抽象類(abstract class)

JAVA 8中實現了介面函式的預設實現,這樣有點像似繼承了,不用每個申明implements這個介面的類中都要實現每一個函式。

類成員欄位變數和區域性變數

欄位變數為物件的一部分,因此存在於物件中,也就是堆中;

區域性變數為成員函式內宣告的變數,存在於棧中

生命週期不同:欄位變數隨物件建立後一直存在直到物件銷燬,而區域性變數只有在函式被呼叫時存在,呼叫結束則釋放記憶體;

欄位變數自動賦初值為0, 區域性變數則不會自動初始化,必須顯式地初始化。

函式呼叫時變數的傳遞(值傳遞和引用傳遞)

總的來說,呼叫物件方法時,java是值傳遞,即:將表示式的值複製給形式引數。對於引用型變數,傳遞的值是引用值,不會複製物件實體本身

多型(polymorphism)

多型是指一個程式中相同的名字表示不同的含義。java的多型有兩種情形:

  • 編譯時多型:過載(overload)是多個同名但是不同簽名(引數不同)的不同方法,如:p.sayHello(),p.sayHello("zhang");
  • 執行時多型
    • 覆蓋override:子類對父類方法進行覆蓋,通過動態繫結(dynamic binding),也稱之為虛方法呼叫(virtual method invoking),因為程式呼叫的是虛的,只有在執行時系統根據呼叫該方法的例項的型別來決定呼叫哪個方法
    • 在呼叫方法時,程式會正確地呼叫子類物件的方法
public class Main {
    static void  callDraw(Shape s){
        s.draw();
    }
    public static void main(String[] args) {
        Circle c = new Circle();
        Triangle t = new Triangle();
        Line l = new Line();
        callDraw(c);
        callDraw(t);
        callDraw(l);
    }
}
class Shape{
    void draw(){        System.out.println("shape drawing");}
}
class Circle extends Shape{
    void draw(){        System.out.println("Circle drawing");}
}
class Triangle extends  Shape{
    void draw(){       System.out.println("triangle drawing");}
}
class Line extends Shape{
    void draw(){       System.out.println("line drawing");}
}

上面這個例子中到底呼叫的是哪個draw則在執行時決定。

虛方法呼叫和非虛呼叫

java中,普通的方法就是虛方法,但是以下幾種情形不是虛方法呼叫:

static/private/final

請注意下面的例子中,由於Shape中的draw為static也就是說屬於類的,就不會觸發虛方法呼叫,而輸出3個相同的shape drawing。這時的呼叫依賴於申明的類,這裡就是Shape類,和傳入的是circle,triangle等無關了

public class Main {

    static void  callDraw(Shape s){
        s.draw();
    }
    public static void main(String[] args) {
        Circle c = new Circle();
        Triangle t = new Triangle();
        Line l = new Line();
        callDraw(c);
        callDraw(t);
        callDraw(l);
    }
}
class Shape{
    static void draw(){ System.out.println("shape drawing");}
}
class Circle extends Shape{
    static void draw(){System.out.println("Circle drawing");}
}
class Triangle extends  Shape{
    static void draw(){System.out.println("triangle drawing");}
}
class Line extends Shape{
    static void draw(){System.out.println("line drawing");}
}
// shape drawing
// shape drawing
// shape drawing

建構函式的層級呼叫

在建立一個物件時,如果類中沒有建構函式,則系統會自動呼叫super,這時一定要注意父類中的建構函式必須是無引數的函式,否則就會出錯。java編譯器的原則是必須令所有父類的構造方法都能得到呼叫

因此,如果不顯式地呼叫super,則必須保證其父類中的建構函式為無引數,否則編譯出錯 

例項初始化與靜態初始化

public class Main {
    public static void main(String[] args) {
        InitialTest2 init2 = new InitialTest2(2);
    }
}
class InitialTest{
    static int x=0;
    static {
        x++;
        System.out.println("static..."+x);
    }
}
class InitialTest2 extends  InitialTest{
    InitialTest2(int a){
        this.a = a;
        System.out.println("consturction2 : this.a="+a);
    }
    int a;
    {
        System.out.println("IntialTest2 before instance created..."+this.a);
    }
    static {
        x++;
        System.out.println("static2 init..."+x);
    }
}
/* 輸出結果:
static...1
static2 init...2
IntialTest2 before instance created...0
consturction2 : this.a=2
*

由於通過super呼叫在construct中可能會在呼叫虛方法時繞回到子類中訪問未初始化的資料,因此儘量不要在建構函式中呼叫方法,如果必須呼叫的話就呼叫final方法

物件清除(garbage collection)

System.gc()呼叫僅僅建議啟動系統的垃圾回收,但是並不能真正做到垃圾的回收。

也可以在子類的finalize()過載實現中去釋放資源,類似c++的解構函式。

對於實現了java.lang.AutoCloseable的物件,我們可以使用try正規化在程式碼執行完畢後自動關閉資源:

 

        try(Scanner scanner = new Scanner(...)) {
            ...
        }

 

 

類的類、匿名類

匿名類沒有類名,在定義類的同時就生成該物件的一個例項,一次性使用

lambda表示式

相當於匿名函式

(引數)->結果

的形式,類似於javascript的匿名函式

@override是幹嘛的

@override是java的虛擬碼,表示在這裡要重寫下面的方法,當然也可以沒有。寫了這個虛擬碼的好處:

1、可以當註釋用,方便閱讀;
2、編譯器可以給你驗證@Override下面的方法名是否是你父類中所有的,如果沒有則報錯。例如,你如果沒寫@Override,而你下面的方法名又寫錯了,這時你的編譯器是可以編譯通過的,因為編譯器以為這個方法是你的子類中自己增加的方法。

注意過載和重寫的區別,過載是方法的引數個數或者引數型別不同,導致是不同的方法。而重寫是子類對父類相同方法的覆蓋重寫

POJO(Plain Old Java Object)

POJO = Plain Old Java Object字面意思是普通java物件。其內在含義為沒有繼承任何類的普通類例項物件

JavaBean

一個pojo類(plain old java object)物件中,如果其欄位都有對應的getter和setter,並且有一個無參的建構函式,這類物件我們稱之為JavaBean,JavaBean往往由容器來建立,比如tomcat建立javabean,因此需要保證有一個無參建構函式。更多用於資料的臨時中轉

war檔案

war檔案是一個適用於tomcat webapp目錄下部署的web專案包檔案。通常使用一下命令打包和檢視,通常包含一堆的jar包檔案

jar -cvf blog.war * // 打包
jar -tf blog.war //檢視

範型Point<T>

有的時候,具體使用什麼class的引數,只有在使用時才能確定,那麼比較好的方案就是使用範型。比如要設計一個Point類,其成員變數可能可以使用整形,也可以使用浮點數,那麼就可以使用:

 

public class Main {

    public static void main(String[] args) {
        Point<String> p1 = new Point<String>(); // 在呼叫處指定對應型別
        p1.x = "20";
        p1.y = "24";
        System.out.println(p1.x);
    }
}
class Point<T>{
    T x;
    T y;
}

JVM exception處理

java程式執行時出錯的話要麼自己try{}catch主動處理,要麼通過throws呼叫交由jvm自行處理。java程式也可以主動丟擲異常,層層向上。

throw vs throws

throw在方法體內,由java自己主動丟擲異常,而throws則在方法後面緊跟本方法可能丟擲的異常,而在別人呼叫這個異常時,就必須實現catch相應的異常,或者再次主動丟擲異常。

反射

類載入器

類載入器負責將.class位元組碼檔案載入到記憶體中,併為之生成對應的class物件。以便後續使用。

  • 類載入器分以下幾類:根類載入器,也稱為引導類載入器,負責java核心類的載入,比如system, string等,在JRE的lib目錄下rt.jar檔案中;
  • 擴充套件類載入器,負責JRE擴充套件目錄中jar包的載入,在jre lib目錄下的ext目錄
  • 系統類載入器,負責在JVM啟動時載入來自java命令的class檔案以及在classpath環境變數中所指定的jar包和類路徑

什麼是反射

java反射機制是在執行狀態中,對於任何一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取物件的資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。

要使用反射,就必須獲得位元組碼檔案

Class.forName/obj.getClass獲取位元組碼

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        Class pclass1 = Class.forName("Point");
        Class pclass2 = Point.class;
        Point pobj = new Point();
        Class pclass3 = pobj.getClass();
        System.out.println(pclass1==pclass2); // true
        System.out.println(pclass2 == pclass3); // true
    }
}

以上說明幾種方式獲取class位元組碼都是相同的拷貝,其中如果使用idea則隨時可能要使用alt+enter快捷鍵增加一個local變數來引用返回值,及快捷增加exception處理。

 使用反射暴力修改private成員

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Class pClass = Class.forName("Point");
        Point pObj = (Point)pClass.newInstance();
        Field privateVarField = pClass.getDeclaredField("privateVar");
        privateVarField.setAccessible(true);
        privateVarField.set(pObj,205);
        System.out.println(pObj.getPrivateVar()); // 私有變數已被修改為205 
    }
}

使用反射暴力修改private方法訪問許可權

class Point{
    private int privateVar = 1;
    int x;
    int y = 2;

    private int privateGetX(){
        return  x;
    }
    public int setX(int a){
        x = a;
        return 0;
    }
    public int publicGetY() {
        return y;
    }
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        Class pClass = Class.forName("Point");
        Point pObj = (Point)pClass.newInstance();
        pObj.setX(5);
        Method publicGetY = pClass.getMethod("publicGetY");
        System.out.println(publicGetY.invoke(pObj)); // 2
        Method privateGetX = pClass.getDeclaredMethod("privateGetX");
        // 暴力破解private Method,使得可以通過反射來呼叫
        privateGetX.setAccessible(true);
        System.out.println(privateGetX.invoke(pObj)); // 5
    }
}

JAVA內部類

在java中,一個類內部又可以定義內部類,內部類有以下幾種存在形式

1.成員內部類

class OutClass{
    private int num = 3;
    class Inner { // 成員內部類
        int num = 4;
        void show(){
            int num = 5;
            System.out.println("num" + num); // num:5
            System.out.println("num" + this.num); // num:4 本內部類的成員
            System.out.println("num" + OutClass.this.num); // num:3 外部類的成員訪問
        }
    }
    public static void main(String[] args){
        OutClass.Inner inner = new OutClass().new Inner(); // 由於有內部成員類,因此有了new方法,須先new一個外部類例項,再new內部類
        inner.show();
    }
}

2.靜態內部類

class OutClass{
    private static int num = 3;
    static class Inner { // 成員內部類
        int num = 4;
        void show(){
            int num = 5;
            System.out.println("num" + num); // num:5
            System.out.println("num" + this.num); // num:4 本內部類的成員
            System.out.println("num" + OutClass.num); // num:3只能訪問外部類靜態成員
        }
        static void showstatic(){
            int num = 5;
            System.out.println("num" + num); // num:5
            System.out.println("num" + OutClass.num); // num:3 外部類的成員訪問
        }
    }
    public static void main(String[] args){
        OutClass.Inner inner = new OutClass.Inner();
        inner.show();
        OutClass.Inner.showstatic();
    }
}

 

3.匿名內部類

匿名內部類往往用於從介面定義一個類,僅僅一次使用,沒有必要給他一個命名的情形

4.區域性內部類

Tomcat體系結構

tomcat作為server run time environment,以web容器的方式提供服務,包括connector(http,https,ajp及其他請求方式)的接入引擎,服務引擎,Host主機服務及host下面的context project等部分組成。

JAVAWeb分層結構

web層負責http請求的接收和響應;web層適用於MVC設計模式,controller實際上就是servlet,view則是JSP,model對於C操作,由input引數建立對應的model並經由service呼叫DAO持久化,對於R操作,則反過來由DAO層獲取到資料呈現到response中.

service層負責核心的業務邏輯;

Dao層則唯一負責和資料庫CRUD操作並以model方式返回資料

J2EE web專案執行包體系

JAVA多執行緒

java執行緒在呼叫start方法後將進入就緒狀態佇列,該佇列為一個先入先出的FIFO佇列,CPU會根據一定的排程演算法從該佇列中取出一個執行緒分配時間片進入執行狀態。

在執行過程中,如果時間片到時則會被搶佔進入就緒態,等待下一次排程;如果執行中需要等待某一資源,則阻塞自己進入等待態。執行緒執行完畢則銷燬態。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("當前執行緒名稱為:"+ Thread.currentThread().getName());
    }
}
public class ThreadDemo{
    public static void main(String[] args){
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.start(); // 分別列印   當前執行緒名稱為:Thread-0,注意順序可能是隨機的哦
        myThread2.start();  // 分別列印   當前執行緒名稱為:Thread-1
    }
}

  

tomcat http請求處理及響應

Servlet

servlet是執行在服務端的java程式段,遵循sun公司提供的一套規範介面。該介面中最重要的是service方法,還有init,destroy等介面。主要用來負責處理客戶端的請求並響應給瀏覽器以動態資源。servlet的實質就是java程式碼,通過java提供的API來動態向客戶端輸出內容。

需要注意的是,在java web以及junit開發中,我們不需要再寫main函式,因為該main函式在javaweb開發時是由tomcat的bootstrap類來提供的,是一個無限迴圈,永遠不會撤銷。而在junit中則在junit的框架程式碼中。我們的servlet程式由tomcat的main函式在接收到http請求時通過反射機制來具體呼叫servlet的位元組程式碼,並最終返回響應。servlet是連線web前端和後端邏輯的橋樑。servlet是singleton,因此在servlet中不能儲存使用者相關的資料(相當於是全域性互斥資料,會導致執行緒衝突),否則會導致混亂,注意執行緒安全

servlet的建立和對映:

可以通過手工在web.xml中定義,或者通過註解@WebServlet("/myservnet")的方式來指示編譯器完成對映。

單例項,多執行緒servlet呼叫模型

 

servlet配置過程,類似於PHP Laravel的路由配置過程,主要要指定對應的url-pattern,以及對應的servlet類。配置時也存在優先順序及覆蓋的問題。我們可以在站點級全域性web.xml中配置公共路由,也可以在專案級別建立web.xml實現專案級別的路由。

tomcat中靜態資源載入過程

當path後面寫的是靜態資源名稱,比如index.html,tomcat也會去找url-pattern有沒有可以匹配的內容,如果有,就載入對應servnet,如果沒有就找到配置中的預設url-pattern.

如果當前project沒有預設url-pattern,則找到站點級別的web.xml的預設匹配的url-pattern,一般在default servlet name項中

<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

JSP的本質

JSP本身實質上是一段將來被編譯為位元組碼的java程式,也是一個servlet,類似於PHP的模版引擎(xxx.blade.php),最終編譯出來的位元組碼(php native原始碼)完成的工作實際上就是拼接java資料到對應前端html中.

 

servlet的forward和redirect

當tomcat收到一個http請求時,路由到servlet,對應的servlet根據業務邏輯可能需要forward到其他servlet(這是內部轉移)或者直接返回一個重定向讓瀏覽器做redirect操作,最終才能完成業務。

如果需要servlet共享資料給jsp,則需要使用forward轉發,轉發只能轉到內部的資源。

往往通過request物件setAttribute增加一個資料,然後forward給jsp來顯示資料

request.setAttribute('productList',proList);
RequestDispatcher requestDispatcher = request.getRequestDispatcher('/product/query.jsp');
requestDispatcher.forward(request,response);

JSTL/EL/OGNL(Struts2)

JSTL/EL是用於簡化jsp編寫而定義的java規範,JSTL(tld)定義了一些描述性的標籤,EL則定義了一種以${ java程式碼 }的方式便於在jsp中執行java程式碼獲取資料,並且使用類似php的blade模版來表達展示資料

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
  <head>
    <title>查詢商品</title>
  </head>
  <body>
  <c:forEach items="${requestScope.prodList}" var="prod">
    <tr>
      <td>${prod.id}</td>
      <td>${prod.name}</td>
      <td>${prod.price}</td>
    </tr>
  </c:forEach>
  </body>
</html>

需要注意的是jstl標籤裡面訪問的都是pojo的get方法,因為類似於name, price等欄位都是私有的,所以不可能通過obj.propery來訪問到,只能通過getXXX的方式來獲取,這也是為什麼我們需要JavaBean的原因。我們儘量不要在jsp中使用<%= pageContext.request.contextPath %>,這樣的方式來寫java程式碼,而儘可能要使用EL表示式方式

struts2值域valueStack

雖然在上面的演示中,我們通過request,session,application等域物件可以在頁面處理過程中交換資料,但是這類方法更多限定於在jsp頁面中訪問相關資料,對於如果想在action中訪問相關資料,則可以使用struts2框架的值域。action一旦建立,就會生成一個valueStack,他就是一個存放資料的容器。struts2中會將所有的域物件(request,response,application,session)也都存放在了valueStack中,因此最好的方式,我們們都統一稱為valueStack方式來處理資料。

 

jspf檔案簡化前端資源引用

在web頁面中,有一些css,js等前端資源的引用實際上是公共的,幾乎所有的頁面都需要。並且有的時候我們也需要在所有jsp檔案中都可能需要引用類似工程基地址的變數,這時一個比較好的辦法就是使用類似php的partial.blade.php檔案,將這些東西抽取出來,在所有jsp檔案中通過@include來引用。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="projectbaseuri" value ="${pageContext.request.contextPath}"/>

 

 

JVM記憶體分佈

所謂本地方法區是指非Java語言編寫的C或者C++程式需要的棧,而程式計數器則屬於執行緒專有,儲存著每個執行緒執行時場景的堆疊,PC計數器等,可以用於執行緒的切換

資料庫JDBC

jdbc是java定義的一套java訪問資料庫的介面,資料庫的vendor可以根據該介面規範來實現資料庫訪問驅動,比如mysql的驅動為Mysql-JDBC-impl.jar,Oracle-JDBC-impl.jar, SQL servier-JDBC-impl.jar. 

java程式遵循JDBC規範通過對應的驅動來對實際資料庫的訪問。

DAO元件

通過上面的JDBC介面,雖然JAVA程式可以直接訪問到資料庫,但是每次資料庫的訪問還是比較複雜的過程,比如建立連線,構建SQL,執行SQL,關閉sql statement,關閉jdbc連線。為了責任單一,一般需要抽象出來一層DAO元件,上層程式呼叫該元件的方法實現資料庫操作。

資料庫連線池

如上面所描述,每次資料庫的操作都需要建立資料庫的連線,資料庫操作完畢後將連線關閉,而socket建立和關閉是很消耗資源並且緩慢的過程,我們有必要事先建立好這些connection,而資料庫訪問時,臨時從這些connection中取出一個,資料操作完畢後並不真正釋放連線,而是將連線物件返回到連線池,供後續應用使用。

過濾器filter

類似於PHP laravel的middleware,我們可以使用filter來對某些servlet進行保護和授權。

 

 

監聽器listener

我們可以監聽servletContext, HttpSession, ServletRequest物件的建立屬性更改等事件。

需要注意的是針對監聽的物件不同,監聽器的作用範圍也是不同的,比如針對監聽servletContext,則是全域性性質的,監聽Session的,則只針對單個訪問過程週期有效(只要瀏覽器沒有關閉,session就存在),而監聽ServletRequest的,則只針對單次請求有效

java web開發xml中三大元件

servlet < filter < listener優先順序排序

由於listener優先順序最高,最先執行,因此往往把整個專案的初始化資料載入工作放在這裡執行

https://blog.csdn.net/sunxianghuang/article/details/52107376

https://blog.csdn.net/xiaojie119120/article/details/73274759

java web中四大作用域及資料物件request/response(PageContext),ServletRequest(整個請求鏈有效,包括forward和redirect),HttpSession,application(ServletContext)

application:tomcat全域性唯一

session:單使用者唯一

request/response:單個pv唯一

JavaWeb的四大作用域為:PageContext,ServletRequest,HttpSession,ServletContext;

PageContext域:作用範圍是整個JSP頁面,是四大作用域中最小的一個;生命週期是當對JSP的請求時開始,當響應結束時銷燬。

ServletRequest域:作用範圍是整個請求鏈(請求轉發也存在);生命週期是在service方法呼叫前由伺服器建立,傳入service方法。整個請求結束,request生命結束. 

HttpSession域:作用範圍是一次會話。生命週期是在第一次呼叫request.getSession()方法時,伺服器會檢查是否已經有對應的session,如果沒有就在記憶體中建立一個session並返回。當一段時間內session沒有被使用(預設為30分鐘),則伺服器會銷燬該session。如果伺服器非正常關閉(強行關閉),沒有到期的session也會跟著銷燬。如果呼叫session提供的invalidate() ,可以立即銷燬session。

注意:伺服器正常關閉,再啟動,Session物件會進行鈍化和活化操作。同時如果伺服器鈍化的時間在session 預設銷燬時間之內,則活化後session還是存在的。否則Session不存在。  如果JavaBean 資料在session鈍化時,沒有實現Serializable 則當Session活化時,會消失。

ServletContext域:作用範圍是整個Web應用。當Web應用被載入進容器時建立代表整個web應用的ServletContext物件,當伺服器關閉或Web應用被移除時,ServletContext物件跟著銷燬。 

作用域從小到大為:PageContext(jsp頁面),ServletRequest(一次請求),HttpSession(一次會話),ServletContext(整個web應用)

JAVA ObjectOutputStream(序列化和反序列化)實現Serializable介面

類似於python,c, java中也存在對物件持久化的需求.比較典型的例子是tomcat在關閉前會將session記憶體資料序列化存放到硬碟,而重新啟動tomcat則反序列化讀取恢復到記憶體。

Tomcat除錯中的詳細log使能

在tomcat的conf/loggings.properties檔案或者該應用的WEB-INF/classes目錄中新建一個loggings.properties檔案,再加上以下兩句:

org.apache.catalina.core.ContainerBase.[Catalina].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].handlers = java.util.logging.ConsoleHandler

https://blog.csdn.net/Q_AN1314/article/details/52832460

微服務/SOA

https://blog.csdn.net/zhengzhaoyang122/article/details/80142955

面向服務架構,和傳統的單機單程式搞定一切業務需求不一樣,他更強調模組化,層次化,更加容錯,系統容易維護,但是也會帶來不必要的複雜性。模組之間通過RPC或者REST API呼叫來通訊

JRebel實現j2ee熱載入開發

在正常的開發過程中,我們寫程式碼,build,重新部署,使用瀏覽器檢查結果。往往build和重新部署是非常耗時也是頻繁發生的,JRebel就是解決這個痛點的,類似於Nodejs、webpack中的HRM模組,在編寫前端元件程式碼時,無需重新整理瀏覽器,程式碼直接編譯並灌入瀏覽器,這樣的開發體驗是非常高效完美的。

https://zeroturnaround.com/software/jrebel/pricing/

Struts2

struts實際上就是web層的MVC框架,通過一個前端過濾控制器,擷取所有的request,分發到對應的action,在action中可以通過result結果頁返回對應的web頁面(result實際上就是相當於laravel中的view)

Spring

spring是一個開放原始碼的設計層面框架,他將面向介面程式設計思想貫穿始終,是一個分層的JavaSE/JavaEE full-stack一站式輕量級開源框架,他主要為了解決業務邏輯層和其他各層的鬆耦合關係。這個更加類似於laravel

IOC模式下的Bean配置

通過工廠實現介面與實現的分離。當需求變化時,比如一個mysql需要變更為oracle的資料庫系統,則只需要配置bean對應的class類名,由於物件建立都是由spring提供的工廠來提供的,而工廠根據新的配置就能通過反射機制建立新的類物件了。

IOC本質上就是控制反轉,由spring來給我們建立類例項,而對應的類由我們在xml中配置指定,方便解耦。

1. xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 這裡bean id就是你要例項化類的alias,而其實現則由class來決定,屆時由IOC自動呼叫這裡定義的實現類 -->
    <bean id="userDao" class="cn.kidsit.project1.UserDaoOracleImpl">
<property name="userName" value="zzh"></property>

</
bean> </beans>

2. 介面類及實現類:

package cn.kidsit.project1;

public interface UserDao {
    public void save();

    public void delete();
}

public class UserDapMysqImpl implements UserDao {

    @Override
    public void save() {
        System.out.println("mysql save");
    }

    @Override
    public void delete() {
        System.out.println("mysql delete");
    }
}

public class UserDaoOracleImpl implements UserDao {

public String userName;
public void setUserName(String userName) {
this.userName = userName;
}
@Override public void save() { System.out.println("oracle save"); } @Override public void delete() { System.out.println("oracle delete"); } }

3.單元測試程式碼:

package cn.kidsit.project1;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    @Test
    public void test(){
//    1.載入配置檔案
        ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//    2.根據id建立獲取物件
        UserDao userDao = (UserDao) appContext.getBean("userDao");
        userDao.save(); // 這時將建立的是oracle的實現類
        userDao.delete();
UserDaoOracleImpl userDIDao = (UserDaoOracleImpl) appContext.getBean("userDao");
System.out.println(userDIDao.userName); // 這裡就有了初始化的值,由於依賴注入的功勞
} }

DI依賴注入

將spring管理的類中依賴的屬性賦值時,通過配置檔案來指定spring建立物件的過程就是依賴注入

Bean的scope:單例還是多例(singleton .vs. prototype)

在上面的xml配置程式碼中,預設建立的物件都是單例模式,也就是隻建立了一個,以後都是使用相同的物件。但是很多時候我們必須指定多例模式,比如action對於每次訪問都是不同的。

singleton:預設的scope配置,單例模式;

prototype:多例模式

request:應用在web專案中,Spring建立這個類之後,將這個類物件存入到request範圍中;

session:應用在web專案中,Spring建立這個類之後,將這個類物件存入到session範圍內;

globalsession:應用在web專案中,必須在porlet(基於java的web元件)環境。

通過註解方式宣告Bean

@Component("user") // 相當於在applicationContext.xml中配置對應的bean
public class UserDaoOracleImpl implements UserDao {

    public String userName;

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public void save() {
        System.out.println("oracle save");
    }

    @Override
    public void delete() {
        System.out.println("oracle delete");
    }
}

注意:需要在IDE的compiler配置選項中使能註解

AOP面向切面程式設計(類似於python的proxy功能)

後面也可以直接使用AOP自定義的類,並通過配置檔案來指定在哪個切入點新增切面(就是增強函式)。。。

// 定義增強的功能切片類
public class AspectClass {
    public void checkPrevilidge(){
        System.out.println("許可權校驗");
    }
    public void log(){
        System.out.println("日誌記錄");
    }
}

 

前置通知,後置通知往往用於日誌的增強功能,我們來看對應的配置檔案

 

2018年IEEE Spectrum language Ranking

 

相關文章