2019年快結束了,給你們整理了今年來最經典的面試真題100道,每一個題目都有詳細的解答,收集了java基礎,容器,多線程,反射,對象拷貝,Java Web,異常,網絡,設計模式,Spring / Spring MVC,等專題的經典面試真題,和詳細分析。沒道題目都詳細講解,文章過長,你們必定要耐心的看完哦。php
具體來講 JDK 其實包含了 JRE,同時還包含了編譯 java 源碼的編譯器 javac,還包含了不少 java 程序調試和分析的工具。簡單來講:若是你須要運行 java 程序,只需安裝 JRE 就能夠了,若是你須要編寫 java 程序,須要安裝 JDK。html
== 解讀前端
對於基本類型和引用類型 == 的做用效果是不一樣的,以下所示:java
代碼示例:程序員
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true複製代碼
代碼解讀:由於 x 和 y 指向的是同一個引用,因此 == 也是 true,而 new String()方法則重寫開闢了內存空間,因此 == 結果爲 false,而 equals 比較的一直是值,因此結果都爲 true。web
equals 解讀面試
equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。看下面的代碼就明白了。正則表達式
首先來看默認狀況下 equals 比較一個有相同值的對象,代碼以下:算法
class Cat {
public Cat(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat c1 = new Cat;
Cat c2 = new Cat;
System.out.println(c1.equals(c2)); // false複製代碼
輸出結果出乎咱們的意料,居然是 false?這是怎麼回事,看了 equals 源碼就知道了,源碼以下:spring
public boolean equals(Object obj) {
return (this == obj);
}複製代碼
原來 equals 本質上就是 ==。
那問題來了,兩個相同值的 String 對象,爲何返回的是 true?代碼以下:
String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true複製代碼
一樣的,當咱們進入 String 的 equals 方法,找到了答案,代碼以下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}複製代碼
原來是 String 重寫了 Object 的 equals 方法,把引用比較改爲了值比較。
總結 :== 對於基本類型來講是值比較,對於引用類型來講是比較的是引用;而 equals 默認狀況下是引用比較,只是不少類從新了 equals 方法,好比 String、Integer 等把它變成了值比較,因此通常狀況下 equals 比較的是值是否相等。
不對,兩個對象的 hashCode()相同,equals()不必定 true。
代碼示例:
String str1 = "通話";
String str2 = "重地";
System.out.println(String.format("str1:%d | str2:%d", str1.hashCode(),str2.hashCode()));
System.out.println(str1.equals(str2));複製代碼
執行的結果:
str1:1179395 | str2:1179395
false複製代碼
代碼解讀:很顯然「通話」和「重地」的 hashCode() 相同,然而 equals() 則爲 false,由於在散列表中,hashCode()相等即兩個鍵值對的哈希值相等,然而哈希值相等,並不必定能得出鍵值對相等。
等於 -1,由於在數軸上取值時,中間值(0.5)向右取整,因此正 0.5 是往上取整,負 0.5 是直接捨棄。
String 不屬於基礎類型,基礎類型有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬於對象。
操做字符串的類有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的區別在於 String 聲明的是不可變的對象,每次操做都會生成新的 String 對象,而後將指針指向新的 String 對象,而 StringBuffer、StringBuilder 能夠在原有對象的基礎上進行操做,因此在常常改變字符串內容的狀況下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高於 StringBuffer,因此在單線程環境下推薦使用 StringBuilder,多線程環境下推薦使用 StringBuffer。
不同,由於內存的分配方式不同。String str="i"的方式,java 虛擬機會將其分配到常量池中;而 String str=new String("i") 則會被分到堆內存中。
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代碼:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba複製代碼
不須要,抽象類不必定非要有抽象方法。
示例代碼:
abstract class Cat {
public static void sayHi() {
System.out.println("hi~");
}
}複製代碼
上面代碼,抽象類並無抽象方法但徹底能夠正常運行。
不能,定義抽象類就是讓其餘類繼承的,若是定義爲 final 該類就不能被繼承,這樣彼此就會產生矛盾,因此 final 不能修飾抽象類,以下圖所示,編輯器也會提示錯誤信息:
按功能來分:輸入流(input)、輸出流(output)。
按類型來分:字節流和字符流。
字節流和字符流的區別是:字節流按 8 位傳輸以字節爲單位輸入輸出數據,字符流按 16 位傳輸以字符爲單位輸入輸出數據。
經常使用容器的圖錄:
對於在Map中插入、刪除和定位元素這類操做,HashMap是最好的選擇。然而,假如你須要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基於你的collection的大小,也許向HashMap中添加元素會更快,將map換爲TreeMap進行有序key的遍歷。
HashMap概述: HashMap是基於哈希表的Map接口的非同步實現。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種,一個是數組,另一個是模擬指針(引用),全部的數據結構均可以用這兩個基本結構來構造的,HashMap也不例外。HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。
當咱們往Hashmap中put元素時,首先根據key的hashcode從新計算hash值,根絕hash值獲得這個元素在數組中的位置(下標),若是該數組在該位置上已經存放了其餘元素,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放入鏈尾.若是數組中該位置沒有元素,就直接將該元素放到數組的該位置上。
須要注意Jdk 1.8中對HashMap的實現作了優化,當鏈表中的節點數據超過八個以後,該鏈表會轉爲紅黑樹來提升查詢效率,從原來的O(n)到O(logn)
最明顯的區別是 ArrrayList底層的數據結構是數組,支持隨機訪問,而 LinkedList 的底層數據結構是雙向循環鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。
poll() 和 remove() 都是從隊列中取出一個元素,可是 poll() 在獲取元素失敗的時候會返回空,可是 remove() 失敗的時候會拋出異常。
迭代器是一種設計模式,它是一個對象,它能夠遍歷並選擇序列中的對象,而開發人員不須要了解該序列的底層結構。迭代器一般被稱爲「輕量級」對象,由於建立它的代價小。
Java中的Iterator功能比較簡單,而且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。
(2) 使用next()得到序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現,爲List設計的ListIterator具備更多的功能,它能夠從兩個方向遍歷List,也能夠從List中插入和刪除元素。
因此併發編程的目標是充分的利用處理器的每個核,以達到最高的處理性能。
簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存資源,減小切換次數,從而效率更高。線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位。同一進程中的多個線程之間能夠併發執行。
守護線程(即daemon thread),是個服務線程,準確地來講就是服務其餘的線程。
①. 繼承Thread類建立線程類
②. 經過Runnable接口建立線程類
③. 經過Callable和Future建立線程
有點深的問題了,也看出一個Java程序員學習知識的廣度。
線程一般都有五種狀態,建立、就緒、運行、阻塞和死亡。
sleep():方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其餘線程,等到休眠時間結束後,線程進入就緒狀態和其餘線程一塊兒競爭cpu的執行時間。由於sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,可是對象的機鎖沒有被釋放,其餘線程依然沒法訪問這個對象。
wait():wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其餘線程可以訪問,能夠經過notify,notifyAll方法來喚醒等待的線程。
每一個線程都是經過某個特定Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。經過調用Thread類的start()方法來啓動一個線程。
start()方法來啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼; 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。
run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。 若是直接調用run(),其實就至關因而調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,因此執行路徑仍是隻有一條,根本就沒有線程的特徵,因此在多線程執行時要使用start()方法而不是run()方法。
①. newFixedThreadPool(int nThreads)
建立一個固定長度的線程池,每當提交一個任務就建立一個線程,直到達到線程池的最大數量,這時線程規模將再也不變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。
②. newCachedThreadPool()
建立一個可緩存的線程池,若是線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增長時,則能夠自動添加新線程,線程池的規模不存在任何限制。
③. newSingleThreadExecutor()
這是一個單線程的Executor,它建立單個工做線程來執行任務,若是這個線程異常結束,會建立一個新的來替代它;它的特色是能確保依照任務在隊列中的順序來串行執行。
④. newScheduledThreadPool(int corePoolSize)
建立了一個固定長度的線程池,並且以延遲或定時的方式來執行任務,相似於Timer。
線程池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。
線程池各個狀態切換框架圖:
線程安全在三個方面體現:
在Java中,鎖共有4種狀態,級別從低到高依次爲:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級。
鎖升級的圖示過程:
死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。是操做系統層面的一個錯誤,是進程死鎖的簡稱,最先在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操做系統乃至整個併發程序設計領域最難處理的問題之一。
死鎖的四個必要條件:
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之 一不知足,就不會發生死鎖。
理解了死鎖的緣由,尤爲是產生死鎖的四個必要條件,就能夠最大可能地避免、預防和 解除死鎖。
因此,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配算法,避免進程永久佔據系統資源。
此外,也要防止進程在處於等待狀態的狀況下佔用資源。所以,對資源的分配要給予合理的規劃。
線程局部變量是侷限於線程內部的變量,屬於線程自身全部,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。可是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別當心,在這種狀況下,工做線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工做完成後沒有釋放,Java 應用就存在內存泄露的風險。
synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性。
Java中每個對象均可以做爲鎖,這是synchronized實現同步的基礎:
synchronized是和if、else、for、while同樣的關鍵字,ReentrantLock是類,這是兩者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,能夠被繼承、能夠有方法、能夠有各類各樣的類變量,ReentrantLock比synchronized的擴展性體如今幾點上:
另外,兩者的鎖機制其實也是不同的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操做的應該是對象頭中mark word。
Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操做時,具備排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程能夠向自旋鎖同樣,繼續嘗試,一直等到執行成功。
Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。咱們須要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操做,包括不少直接內存分配以及原子操做的調用,而它之因此標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,須要當心使用,不然會致使嚴重的後果,例如在經過unsafe分配內存的時候,若是本身指定某些區域可能會致使一些相似C++同樣的指針越界到其餘進程的問題。
反射主要是指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力
Java反射:
在Java運行時環境中,對於任意一個類,可否知道這個類有哪些屬性和方法?對於任意一個對象,可否調用它的任意一個方法
Java反射機制主要提供瞭如下功能:
簡單說就是爲了保存在內存中的各類對象的狀態(也就是實例變量,不是方法),而且能夠把保存的對象狀態再讀出來。雖然你能夠用你本身的各類各樣的方法來保存object states,可是Java給你提供一種應該比你本身好的保存對象狀態的機制,那就是序列化。
什麼狀況下須要序列化:
a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想經過RMI傳輸對象的時候;
動態代理:
當想要給實現了某個接口的類中的方法,加一些額外的處理。好比說加日誌,加事務等。能夠給這個類建立一個代理,故名思議就是建立一個新的類,這個類不只包含原來類方法的功能,並且還在原來的基礎上添加了額外處理的新類。這個代理類並非定義好的,是動態生成的。具備解耦意義,靈活,擴展性強。
動態代理的應用:
首先必須定義一個接口,還要有一個InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱爲代理類,由於調用他的newInstance()能夠產生代理對象,其實他只是一個產生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最後返回。
想對一個對象進行處理,又想保留原有的數據進行接下來的操做,就須要克隆了,Java語言中克隆針對的是類的實例。
有兩種方式:
1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義
// 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放
}
}複製代碼
下面是測試代碼:
import java.io.Serializable;
/**
* 人類
* @author nnngu
*
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年齡
private Car car; // 座駕
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}/**
* 小汽車類
* @author nnngu
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高時速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}複製代碼
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person對象p2關聯的汽車對象的品牌屬性
// 原來的Person對象p1關聯的汽車不會受到任何影響
// 由於在克隆Person對象時其關聯的汽車對象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}複製代碼
注意:基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來老是好過把問題留到運行時。
JSP有9個內置對象:
JSP中的四種做用域包括page、request、session和application,具體來講:
其實session是一個存在服務器上的相似於一個散列表格的文件。裏面存有咱們須要的信息,在咱們須要用的時候能夠從裏面取出來。相似於一個大號的map吧,裏面的鍵存儲的是用戶的sessionid,用戶向服務器發送請求的時候會帶上這個sessionid。這時就能夠從中取出對應的值了。
Cookie與 Session,通常認爲是兩個獨立的東西,Session採用的是在服務器端保持狀態的方案,而Cookie採用的是在客戶端保持狀態的方案。但爲何禁用Cookie就不能獲得Session呢?由於Session是用Session ID來肯定當前對話所對應的服務器Session,而Session ID是經過Cookie來傳遞的,禁用Cookie至關於失去了Session ID,也就得不到Session了。
假定用戶關閉Cookie的狀況下使用Session,其實現途徑有如下幾種:
Struts2是類級別的攔截,每次請求就會建立一個Action,和Spring整合時Struts2的ActionBean注入做用域是原型模式prototype,而後經過setter,getter吧request數據注入到屬性。Struts2中,一個Action對應一個request,response上下文,在接收參數時,能夠經過屬性接收,這說明屬性參數是讓多個方法共享的。Struts2中Action的一個方法能夠對應一個url,而其類屬性卻被全部方法共享,這也就沒法用註解或其餘方式標識其所屬方法了,只能設計爲多例。
SpringMVC是方法級別的攔截,一個方法對應一個Request上下文,因此方法直接基本上是獨立的,獨享request,response數據。而每一個方法同時又何一個url對應,參數的傳遞是直接注入到方法中的,是方法所獨有的。處理結果經過ModeMap返回給框架。在Spring整合時,SpringMVC的Controller Bean默認單例模式Singleton,因此默認對全部的請求,只會建立一個Controller,有應爲沒有共享的屬性,因此是線程安全的,若是要改變默認的做用域,須要添加@Scope註解修改。
Struts2有本身的攔截Interceptor機制,SpringMVC這是用的是獨立的Aop方式,這樣致使Struts2的配置文件量仍是比SpringMVC大。
Struts2採用Filter(StrutsPrepareAndExecuteFilter)實現,SpringMVC(DispatcherServlet)則採用Servlet實現。Filter在容器啓動以後即初始化;服務中止之後墜毀,晚於Servlet。Servlet在是在調用時初始化,先於Filter調用,服務中止後銷燬。
Struts2是類級別的攔截,每次請求對應實例一個新的Action,須要加載全部的屬性值注入,SpringMVC實現了零配置,因爲SpringMVC基於方法的攔截,有加載一次單例模式bean注入。因此,SpringMVC開發效率和性能高於Struts2。
spring MVC和Spring是無縫的。從這個項目的管理和安全上也比Struts2高。
XSS攻擊又稱CSS,全稱Cross Site Script (跨站腳本攻擊),其原理是攻擊者向有XSS漏洞的網站中輸入惡意的 HTML 代碼,當用戶瀏覽該網站時,這段 HTML 代碼會自動執行,從而達到攻擊的目的。XSS 攻擊相似於 SQL 注入攻擊,SQL注入攻擊中以SQL語句做爲用戶輸入,從而達到查詢/修改/刪除數據的目的,而在xss攻擊中,經過插入惡意腳本,實現對用戶遊覽器的控制,獲取用戶的一些信息。 XSS是 Web 程序中常見的漏洞,XSS 屬於被動式且用於客戶端的攻擊方式。
XSS防範的整體思路是:對輸入(和URL參數)進行過濾,對輸出進行編碼。
CSRF(Cross-site request forgery)也被稱爲 one-click attack或者 session riding,中文全稱是叫跨站請求僞造。通常來講,攻擊者經過僞造用戶的瀏覽器的請求,向訪問一個用戶本身曾經認證訪問過的網站發送出去,使目標網站接收並誤覺得是用戶的真實操做而去執行命令。經常使用於盜取帳號、轉帳、發送虛假消息等。攻擊者利用網站對請求的驗證漏洞而實現這樣的攻擊行爲,網站可以確認請求來源於用戶的瀏覽器,卻不能驗證請求是否源於用戶的真實意願下的操做行爲。
如何避免:
1. 驗證 HTTP Referer 字段
HTTP頭中的Referer字段記錄了該 HTTP 請求的來源地址。在一般狀況下,訪問一個安全受限頁面的請求來自於同一個網站,而若是黑客要對其實施 CSRF攻擊,他通常只能在他本身的網站構造請求。所以,能夠經過驗證Referer值來防護CSRF 攻擊。
2. 使用驗證碼
關鍵操做頁面加上驗證碼,後臺收到請求後經過判斷驗證碼能夠防護CSRF。但這種方法對用戶不太友好。
3. 在請求地址中添加token並驗證
CSRF 攻擊之因此可以成功,是由於黑客能夠徹底僞造用戶的請求,該請求中全部的用戶驗證信息都是存在於cookie中,所以黑客能夠在不知道這些驗證信息的狀況下直接利用用戶本身的cookie 來經過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,而且該信息不存在於 cookie 之中。能夠在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有token或者 token 內容不正確,則認爲多是 CSRF 攻擊而拒絕該請求。這種方法要比檢查 Referer 要安全一些,token 能夠在用戶登錄後產生並放於session之中,而後在每次請求時把token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址以後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。而對於 POST 請求來講,要在 form 的最後加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,這樣就把token以參數的形式加入請求了。
4. 在HTTP 頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不一樣的是,這裏並非把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。經過 XMLHttpRequest 這個類,能夠一次性給全部該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,經過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂 token 會透過 Referer 泄露到其餘網站中去。
throws是用來聲明一個方法可能拋出的全部異常信息,throws是將異常聲明可是不處理,而是將異常往上傳,誰調用我就交給誰處理。而throw則是指拋出的一個具體的異常類型。
答:catch 能夠省略
緣由:
更爲嚴格的說法實際上是:try只適合處理運行時異常,try+catch適合處理運行時異常+普通異常。也就是說,若是你只用try去處理普通異常卻不加以catch處理,編譯是通不過的,由於編譯器硬性規定,普通異常若是選擇捕獲,則必須用catch顯示聲明以便進一步處理。而運行時異常在編譯時沒有如此規定,因此catch能夠省略,你加上catch編譯器也以爲無可厚非。
理論上,編譯器看任何代碼都不順眼,都以爲可能有潛在的問題,因此你即便對全部代碼加上try,代碼在運行期時也只不過是在正常運行的基礎上加一層皮。可是你一旦對一段代碼加上try,就等於顯示地承諾編譯器,對這段代碼可能拋出的異常進行捕獲而非向上拋出處理。若是是普通異常,編譯器要求必須用catch捕獲以便進一步處理;若是運行時異常,捕獲而後丟棄而且+finally掃尾處理,或者加上catch捕獲以便進一步處理。
至於加上finally,則是在無論有沒捕獲異常,都要進行的「掃尾」處理。
答:會執行,在 return 前執行。
代碼示例1:
/* * java面試題--若是catch裏面有return語句,finally裏面的代碼還會執行嗎? */public class FinallyDemo2 { public static void main(String[] args) { System.out.println(getInt()); } public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a 在程序執行到這一步的時候,這裏不是return a 而是 return 30;這個返回路徑就造成了 * 可是呢,它發現後面還有finally,因此繼續執行finally的內容,a=40 * 再次回到之前的路徑,繼續走return 30,造成返回路徑以後,這裏的a就不是a變量了,而是常量30 */ } finally { a = 40; }// return a; }}複製代碼
執行結果:30
代碼示例2:
package com.java_02;/* * java面試題--若是catch裏面有return語句,finally裏面的代碼還會執行嗎? */public class FinallyDemo2 { public static void main(String[] args) { System.out.println(getInt()); } public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a 在程序執行到這一步的時候,這裏不是return a 而是 return 30;這個返回路徑就造成了 * 可是呢,它發現後面還有finally,因此繼續執行finally的內容,a=40 * 再次回到之前的路徑,繼續走return 30,造成返回路徑以後,這裏的a就不是a變量了,而是常量30 */ } finally { a = 40; return a; //若是這樣,就又從新造成了一條返回路徑,因爲只能經過1個return返回,因此這裏直接返回40 }// return a; }}複製代碼
執行結果:40
答:301,302 都是HTTP狀態的編碼,都表明着某個URL發生了轉移。
區別:
Forward和Redirect表明了兩種請求轉發方式:直接轉發和間接轉發。
直接轉發方式(Forward),客戶端和瀏覽器只發出一次請求,Servlet、HTML、JSP或其它信息資源,由第二個信息資源響應該請求,在請求對象request中,保存的對象對於每一個信息資源是共享的。
間接轉發方式(Redirect)實際是兩次HTTP請求,服務器端在響應第一次請求的時候,讓瀏覽器再向另一個URL發出請求,從而達到轉發的目的。
舉個通俗的例子:
直接轉發就至關於:「A找B借錢,B說沒有,B去找C借,借到借不到都會把消息傳遞給A」;
間接轉發就至關於:"A找B借錢,B說沒有,讓A去找C借"。
爲了實現可靠數據傳輸, TCP 協議的通訊雙方, 都必須維護一個序列號, 以標識發送出去的數據包中, 哪些是已經被對方收到的。 三次握手的過程便是通訊雙方相互告知序列號起始值, 並確認對方已經收到了序列號起始值的必經步驟。
若是隻是兩次握手, 至多隻有鏈接發起方的起始序列號能被確認, 另外一方選擇的序列號則得不到確認。
①. 發送方產生粘包
採用TCP協議傳輸數據的客戶端與服務器常常是保持一個長鏈接的狀態(一次鏈接發一次數據不存在粘包),雙方在鏈接不斷開的狀況下,能夠一直傳輸數據;但當發送的數據包過於的小時,那麼TCP協議默認的會啓用Nagle算法,將這些較小的數據包進行合併發送(緩衝區數據發送是一個堆壓的過程);這個合併過程就是在發送緩衝區中進行的,也就是說數據發送出來它已是粘包的狀態了。
②. 接收方產生粘包
接收方採用TCP協議接收數據時的過程是這樣的:數據到底接收方,從網絡模型的下方傳遞至傳輸層,傳輸層的TCP協議處理是將其放置接收緩衝區,而後由應用層來主動獲取(C語言用recv、read等函數);這時會出現一個問題,就是咱們在程序中調用的讀取數據函數不能及時的把緩衝區中的數據拿出來,而下一個數據又到來並有一部分放入的緩衝區末尾,等咱們讀取數據時就是一個粘包。(放數據的速度 > 應用層拿數據速度)
方式一:圖片ping或script標籤跨域
圖片ping經常使用於跟蹤用戶點擊頁面或動態廣告曝光次數。
script標籤能夠獲得從其餘來源數據,這也是JSONP依賴的根據。
方式二:JSONP跨域
JSONP(JSON with Padding)是數據格式JSON的一種「使用模式」,可讓網頁從別的網域要數據。根據 XmlHttpRequest 對象受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁能夠獲得從其餘來源動態產生的JSON數據,而這種使用模式就是所謂的 JSONP。用JSONP抓到的數據並非JSON,而是任意的JavaScript,用 JavaScript解釋器運行而不是用JSON解析器解析。全部,經過Chrome查看全部JSONP發送的Get請求都是js類型,而非XHR。
缺點:
方式三:CORS
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不一樣域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,確保安全的跨域數據傳輸。現代瀏覽器使用CORS在API容器如XMLHttpRequest來減小HTTP請求的風險來源。與 JSONP 不一樣,CORS 除了 GET 要求方法之外也支持其餘的 HTTP 要求。服務器通常須要增長以下響應頭的一種或幾種:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400複製代碼
跨域請求默認不會攜帶Cookie信息,若是須要攜帶,請配置下述參數:
"Access-Control-Allow-Credentials": true// Ajax設置"withCredentials": true複製代碼
方式四:window.name+iframe
window.name經過在iframe(通常動態建立i)中加載跨域HTML文件來起做用。而後,HTML文件將傳遞給請求者的字符串內容賦值給window.name。而後,請求者能夠檢索window.name值做爲響應。
每一個iframe都有包裹它的window,而這個window是top window的子窗口。contentWindow屬性返回<iframe>元素的Window對象。你能夠使用這個Window對象來訪問iframe的文檔及其內部DOM。
<!-- 下述用端口 10000表示:domainA 10001表示:domainB--><!-- localhost:10000 --><script> var iframe = document.createElement('iframe'); iframe.style.display = 'none'; // 隱藏 var state = 0; // 防止頁面無限刷新 iframe.onload = function() { if(state === 1) { console.log(JSON.parse(iframe.contentWindow.name)); // 清除建立的iframe iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { state = 1; // 加載完成,指向當前域,防止錯誤(proxy.html爲空白頁面) // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame. iframe.contentWindow.location = 'http://localhost:10000/proxy.html'; } }; iframe.src = 'http://localhost:10001'; document.body.appendChild(iframe);</script><!-- localhost:10001 --><!DOCTYPE html>...<script>window.name = JSON.stringify({a: 1, b: 2});</script></html>複製代碼
方式五:window.postMessage()
HTML5新特性,能夠用來向其餘全部的 window 對象發送消息。須要注意的是咱們必需要保證全部的腳本執行完才發送 MessageEvent,若是在函數執行的過程當中調用了它,就會讓後面的函數超時沒法執行。
下述代碼實現了跨域存儲localStorage
<!-- 下述用端口 10000表示:domainA 10001表示:domainB--><!-- localhost:10000 --><iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;"></iframe><script> function main() { LSsetItem('test', 'Test: ' + new Date()); LSgetItem('test', function(value) { console.log('value: ' + value); }); LSremoveItem('test'); } var callbacks = {}; window.addEventListener('message', function(event) { if (event.source === frames['myPostMessage']) { console.log(event) var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data); if (data) { if (callbacks[data[1]]) { callbacks[data[1]](data[2] === 'null' ? null : data[3]); } delete callbacks[data[1]]; } } }, false); var domain = '*'; // 增長 function LSsetItem(key, value) { var obj = { setItem: key, value: value }; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); } // 獲取 function LSgetItem(key, callback) { var identifier = new Date().getTime(); var obj = { identifier: identifier, getItem: key }; callbacks[identifier] = callback; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); } // 刪除 function LSremoveItem(key) { var obj = { removeItem: key }; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); }</script><!-- localhost:10001 --><script> window.addEventListener('message', function(event) { console.log('Receiver debugging', event); if (event.origin == 'http://localhost:10000') { var data = JSON.parse(event.data); if ('setItem' in data) { localStorage.setItem(data.setItem, data.value); } else if ('getItem' in data) { var gotItem = localStorage.getItem(data.getItem); event.source.postMessage( '#localStorage#' + data.identifier + (gotItem === null ? 'null#' : '#' + gotItem), event.origin ); } else if ('removeItem' in data) { localStorage.removeItem(data.removeItem); } } }, false);</script>複製代碼
注意Safari一下,會報錯:
Blocked a frame with origin 「http://localhost:10001」 from accessing a frame with origin 「http://localhost:10000「. Protocols, domains, and ports must match.
避免該錯誤,能夠在Safari瀏覽器中勾選開發菜單==>停用跨域限制。或者只能使用服務器端轉存的方式實現,由於Safari瀏覽器默認只支持CORS跨域請求。
方式六:修改document.domain跨子域
前提條件:這兩個域名必須屬於同一個基礎域名!並且所用的協議,端口都要一致,不然沒法利用document.domain進行跨域,因此只能跨子域
在根域範圍內,容許把domain屬性的值設置爲它的上一級域。例如,在」aaa.xxx.com」域內,能夠把domain設置爲 「xxx.com」 但不能設置爲 「XXX.com Porn - Free PORNO videos」 或者」com」。
如今存在兩個域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的頁面,因爲其document.name不一致,沒法在aaa下操做bbb的js。能夠在aaa和bbb下經過js將document.name = 'xxx.com';設置一致,來達到互相訪問的做用。
方式七:WebSocket
WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很棒的實現。相關文章,請查看:WebSocket、WebSocket-SockJS
須要注意:WebSocket對象不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。
方式八:代理
同源策略是針對瀏覽器端進行的限制,能夠經過服務器端來解決該問題
DomainA客戶端(瀏覽器) ==> DomainA服務器 ==> DomainB服務器 ==> DomainA客戶端(瀏覽器)
來源:blog.csdn.net/ligang2585116/article/details/73072868
jsonp 即 json+padding,動態建立script標籤,利用script標籤的src屬性能夠獲取任何域下的js腳本,經過這個特性(也能夠說漏洞),服務器端不在返貨json格式,而是返回一段調用某個函數的js代碼,在src中進行了調用,這樣實現了跨域。
參考:經常使用的設計模式彙總,超詳細!
簡單工廠模式:
這個模式自己很簡單並且使用在業務較簡單的狀況下。通常用於小項目或者具體產品不多擴展的狀況(這樣工廠類纔不用常常更改)。
它由三種角色組成:
來用類圖來清晰的表示下的它們之間的關係:
抽象工廠模式:
先來認識下什麼是產品族: 位於不一樣產品等級結構中,功能相關聯的產品組成的家族。
圖中的BmwCar和BenzCar就是兩個產品樹(產品層次結構);而如圖所示的BenzSportsCar和BmwSportsCar就是一個產品族。他們均可以放到跑車家族中,所以功能有所關聯。同理BmwBussinessCar和BenzBusinessCar也是一個產品族。
能夠這麼說,它和工廠方法模式的區別就在於須要建立對象的複雜程度上。並且抽象工廠模式是三個裏面最爲抽象、最具通常性的。抽象工廠模式的用意爲:給客戶端提供一個接口,能夠建立多個產品族中的產品對象。
並且使用抽象工廠模式還要知足一下條件:
來看看抽象工廠模式的各個角色(和工廠方法的一模一樣):
1.簡介
簡單來講,Spring是一個輕量級的控制反轉(IoC)和麪向切面(AOP)的容器框架。
2.輕量
從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架能夠在一個大小隻有1MB多的JAR文件裏發佈。而且Spring所需的處理開銷也是微不足道的。此外,Spring是非侵入式的:典型地,Spring應用中的對象不依賴於Spring的特定類。
3.控制反轉
Spring經過一種稱做控制反轉(IoC)的技術促進了鬆耦合。當應用了IoC,一個對象依賴的其它對象會經過被動的方式傳遞進來,而不是這個對象本身建立或者查找依賴對象。你能夠認爲IoC與JNDI相反——不是對象從容器中查找依賴,而是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。
4.面向切面
Spring提供了面向切面編程的豐富支持,容許經過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用對象只實現它們應該作的——完成業務邏輯——僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如日誌或事務支持。
5.容器
Spring包含並管理應用對象的配置和生命週期,在這個意義上它是一種容器,你能夠配置你的每一個bean如何被建立——基於一個可配置原型(prototype),你的bean能夠建立一個單獨的實例或者每次須要時都生成一個新的實例——以及它們是如何相互關聯的。然而,Spring不該該被混同於傳統的重量級的EJB容器,它們常常是龐大與笨重的,難以使用。
6.框架
Spring能夠將簡單的組件配置、組合成爲複雜的應用。在Spring中,應用對象被聲明式地組合,典型地是在一個XML文件裏。Spring也提供了不少基礎功能(事務管理、持久化框架集成等等),將應用邏輯的開發留給了你。
全部Spring的這些特徵使你可以編寫更乾淨、更可管理、而且更易於測試的代碼。它們也爲Spring中的各類模塊提供了基礎支持。
AOP(Aspect-Oriented Programming,面向方面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP容許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。
而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。AOP表明的是一個橫向的關係,若是說「對象」是一個空心的圓柱體,其中封裝的是對象的屬性和行爲;那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以得到其內部的消息。而剖開的切面,也就是所謂的「方面」了。而後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。
使用「橫切」技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處都基本類似。好比權限認證、日誌、事務處理。Aop 的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是「將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。」
IOC是Inversion of Control的縮寫,多數書籍翻譯成「控制反轉」。
1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IOC 這個概念。對於面向對象設計及編程的基本思想,前面咱們已經講了不少了,再也不贅述,簡單來講就是把複雜系統分解成相互合做的對象,這些對象類經過封裝之後,內部實現對外部是透明的,從而下降了解決問題的複雜度,並且能夠靈活地被重用和擴展。
IOC理論提出的觀點大致是這樣的:藉助於「第三方」實現具備依賴關係的對象之間的解耦。以下圖:
你們看到了吧,因爲引進了中間位置的「第三方」,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關係,齒輪之間的傳動所有依靠「第三方」了,所有對象的控制權所有上繳給「第三方」IOC容器,因此,IOC容器成了整個系統的關鍵核心,它起到了一種相似「粘合劑」的做用,把系統中的全部對象粘合在一塊兒發揮做用,若是沒有這個「粘合劑」,對象與對象之間會彼此失去聯繫,這就是有人把IOC容器比喻成「粘合劑」的由來。
咱們再來作個試驗:把上圖中間的IOC容器拿掉,而後再來看看這套系統:
咱們如今看到的畫面,就是咱們要實現整個系統所須要完成的所有內容。這時候,A、B、C、D這4個對象之間已經沒有了耦合關係,彼此毫無聯繫,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,對象之間的依賴關係已經下降到了最低程度。因此,若是真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現本身的類就能夠了,跟別人沒有任何關係!
咱們再來看看,控制反轉(IOC)到底爲何要起這麼個名字?咱們來對比一下:
軟件系統在沒有引入IOC容器以前,如圖1所示,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,本身必須主動去建立對象B或者使用已經建立的對象B。不管是建立仍是使用對象B,控制權都在本身手上。
軟件系統在引入IOC容器以後,這種情形就徹底改變了,如圖3所示,因爲IOC容器的加入,對象A與對象B之間失去了直接聯繫,因此,當對象A運行到須要對象B的時候,IOC容器會主動建立一個對象B注入到對象A須要的地方。
經過先後的對比,咱們不難看出來:對象A得到依賴對象B的過程,由主動行爲變爲了被動行爲,控制權顛倒過來了,這就是「控制反轉」這個名稱的由來。
Spring框架至今已集成了20多個模塊。這些模塊主要被分以下圖所示的核心容器、數據訪問/集成,、Web、AOP(面向切面編程)、工具、消息和測試模塊。
更多信息:Spring Tutorials - HowToDoInJava
Spring經過DI(依賴注入)實現IOC(控制反轉),經常使用的注入方式主要有三種:
Spring容器中的Bean是否線程安全,容器自己並無提供Bean的線程安全策略,所以能夠說spring容器中的Bean自己不具有線程安全的特性,可是具體仍是要結合具體scope的Bean去研究。
當經過spring容器建立一個Bean實例時,不只能夠完成Bean實例的實例化,還能夠爲Bean指定特定的做用域。Spring支持以下5種做用域:
其中比較經常使用的是singleton和prototype兩種做用域。對於singleton做用域的Bean,每次請求該Bean都將得到相同的實例。容器負責跟蹤Bean實例的狀態,負責維護Bean實例的生命週期行爲;若是一個Bean被設置成prototype做用域,程序每次請求該id的Bean,Spring都會新建一個Bean實例,而後返回給程序。在這種狀況下,Spring容器僅僅使用new 關鍵字建立Bean實例,一旦建立成功,容器不在跟蹤實例,也不會維護Bean實例的狀態。
若是不指定Bean的做用域,Spring默認使用singleton做用域。Java在建立Java實例時,須要進行內存申請;銷燬實例時,須要完成垃圾回收,這些工做都會致使系統開銷的增長。所以,prototype做用域Bean的建立、銷燬代價比較大。而singleton做用域的Bean實例一旦建立成功,能夠重複使用。所以,除非必要,不然儘可能避免將Bean被設置成prototype做用域。
Spring容器負責建立應用程序中的bean同時經過ID來協調這些對象之間的關係。做爲開發人員,咱們須要告訴Spring要建立哪些bean而且如何將其裝配到一塊兒。
spring中bean裝配有兩種方式:
固然這些方式也能夠配合使用。
事務隔離級別指的是一個事務對數據的修改與另外一個並行的事務的隔離程度,當多個事務同時訪問相同數據時,若是沒有采起必要的隔離機制,就可能發生如下問題:
Spring MVC運行流程圖:
Spring運行流程描述:
1. 用戶向服務器發送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
2. DispatcherServlet對請求URL進行解析,獲得請求資源標識符(URI)。而後根據該URI,調用HandlerMapping得到該Handler配置的全部相關的對象(包括Handler對象以及Handler對象對應的攔截器),最後以HandlerExecutionChain對象的形式返回;
3. DispatcherServlet 根據得到的Handler,選擇一個合適的HandlerAdapter;(附註:若是成功得到HandlerAdapter後,此時將開始執行攔截器的preHandler(...)方法)
4. 提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller)。 在填充Handler的入參過程當中,根據你的配置,Spring將幫你作一些額外的工做:
5. Handler執行完成後,向DispatcherServlet 返回一個ModelAndView對象;
6. 根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經註冊到Spring容器中的ViewResolver)返回給DispatcherServlet ;
7. ViewResolver 結合Model和View,來渲染視圖;
8. 將渲染結果返回給客戶端。
Spring MVC的核心組件:
RequestMapping是一個用來處理請求地址映射的註解,可用於類或方法上。用於類上,表示類中的全部響應請求的方法都是以該地址做爲父路徑。
RequestMapping註解有六個屬性,下面咱們把她分紅三類進行說明。
value, method:
consumes,produces
params,headers
《@Autowired用法詳解》
對於2019年100道經典面試真題的總結和解析,給你們在未來面試路上一點幫助,更多的精彩文章和麪試真題請關注:微信公衆號:java程序員彙集地