整理自:
Java面試題全集(上)https://blog.csdn.net/jackfrued/article/details/44921941
Java面試題全集(中)https://blog.csdn.net/jackfrued/article/details/44931137
Java面試題全集(下)https://blog.csdn.net/jackfrued/article/details/44931161
1.訪問修飾符public,private,protected,以及不寫(默認(rèn))時(shí)的區(qū)別磕瓷?
修飾符 | 當(dāng)前類 | 同包 | 子類 | 其它包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
類的成員不寫訪問修飾時(shí)默認(rèn)為default撼泛。默認(rèn)對(duì)于同一個(gè)包中的其他類相當(dāng)于公開(public)量承,對(duì)于不是同一個(gè)包中的其他類相當(dāng)于私有(private)刽沾。受保護(hù)(protected)對(duì)子類相當(dāng)于公開,對(duì)不是同一包中的沒有父子關(guān)系的類相當(dāng)于私有食茎。Java中班套,外部類的修飾符只能是public或默認(rèn),類的成員(包括內(nèi)部類)的修飾符可以是以上四種。
- 訪問修飾符public,private,protected,以及不寫(默認(rèn))時(shí)的區(qū)別巍虫?
Java是一個(gè)近乎純潔的面向?qū)ο缶幊陶Z(yǔ)言彭则,但是為了編程的方便還是引入了基本數(shù)據(jù)類型,但是為了能夠?qū)⑦@些基本數(shù)據(jù)類型當(dāng)成對(duì)象操作占遥,Java為每一個(gè)基本數(shù)據(jù)類型都引入了對(duì)應(yīng)的包裝類型(wrapper class)俯抖,int的包裝類就是Integer,從Java 5開始引入了自動(dòng)裝箱/拆箱機(jī)制瓦胎,使得二者可以相互轉(zhuǎn)換芬萍。
Java 為每個(gè)原始類型提供了包裝類型:
- 原始類型: boolean,char搔啊,byte柬祠,short,int负芋,long漫蛔,float,double
- 包裝類型:Boolean示罗,Character惩猫,Byte芝硬,Short蚜点,Integer,Long拌阴,F(xiàn)loat绍绘,Double
class AutoUnboxingTest {
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 將3自動(dòng)裝箱成Integer類型
int c = 3;
System.out.println(a == b); // false 兩個(gè)引用沒有引用同一對(duì)象
System.out.println(a == c); // true a自動(dòng)拆箱成int類型再和c比較
}
}
最近還遇到一個(gè)面試題,也是和自動(dòng)裝箱和拆箱有點(diǎn)關(guān)系的迟赃,代碼如下所示:
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
如果不明就里很容易認(rèn)為兩個(gè)輸出要么都是true要么都是false陪拘。首先需要注意的是f1、f2纤壁、f3左刽、f4四個(gè)變量都是Integer對(duì)象引用,所以下面的==運(yùn)算比較的不是值而是引用酌媒。裝箱的本質(zhì)是什么呢欠痴?當(dāng)我們給一個(gè)Integer對(duì)象賦一個(gè)int值的時(shí)候,會(huì)調(diào)用Integer類的靜態(tài)方法valueOf秒咨,如果看看valueOf的源代碼就知道發(fā)生了什么喇辽。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Integer的內(nèi)部類,其代碼如下所示:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
簡(jiǎn)單的說雨席,如果整型字面量的值在-128到127之間菩咨,那么不會(huì)new新的Integer對(duì)象,而是直接引用常量池中的Integer對(duì)象,所以上面的面試題中f1==f2的結(jié)果是true抽米,而f3==f4的結(jié)果是false特占。
- 解釋內(nèi)存中的棧(stack)、堆(heap)和方法區(qū)(method area)的用法缨硝。
通常我們定義一個(gè)基本數(shù)據(jù)類型的變量摩钙,一個(gè)對(duì)象的引用,還有就是函數(shù)調(diào)用的現(xiàn)場(chǎng)保存都使用JVM中的棽楸纾空間胖笛;而通過new關(guān)鍵字和構(gòu)造器創(chuàng)建的對(duì)象則放在堆空間,堆是垃圾收集器管理的主要區(qū)域宜岛,由于現(xiàn)在的垃圾收集器都采用分代收集算法长踊,所以堆空間還可以細(xì)分為新生代和老生代,再具體一點(diǎn)可以分為Eden萍倡、Survivor(又可分為From Survivor和To Survivor)身弊、Tenured;方法區(qū)和堆都是各個(gè)線程共享的內(nèi)存區(qū)域列敲,用于存儲(chǔ)已經(jīng)被JVM加載的類信息阱佛、常量、靜態(tài)變量戴而、JIT編譯器編譯后的代碼等數(shù)據(jù)凑术;程序中的字面量(literal)如直接書寫的100、"hello"和常量都是放在常量池中所意,常量池是方法區(qū)的一部分淮逊。棧空間操作起來最快但是棧很小扶踊,通常大量的對(duì)象都是放在堆空間泄鹏,棧和堆的大小都可以通過JVM的啟動(dòng)參數(shù)來進(jìn)行調(diào)整,椦砗模空間用光了會(huì)引發(fā)StackOverflowError备籽,而堆和常量池空間不足則會(huì)引發(fā)OutOfMemoryError。
String str = new String("hello");
上面的語(yǔ)句中變量str放在棧上分井,用new創(chuàng)建出來的字符串對(duì)象放在堆上车猬,而"hello"這個(gè)字面量是放在方法區(qū)的。
補(bǔ)充1:較新版本的Java(從Java 6的某個(gè)更新開始)中杂抽,由于JIT編譯器的發(fā)展和"逃逸分析"技術(shù)的逐漸成熟诈唬,棧上分配、標(biāo)量替換等優(yōu)化技術(shù)使得對(duì)象一定分配在堆上這件事情已經(jīng)變得不那么絕對(duì)了缩麸。
補(bǔ)充2:運(yùn)行時(shí)常量池相當(dāng)于Class文件常量池具有動(dòng)態(tài)性铸磅,Java語(yǔ)言并不要求常量一定只有編譯期間才能產(chǎn)生,運(yùn)行期間也可以將新的常量放入池中,String類的intern()方法就是這樣的阅仔。
- 當(dāng)一個(gè)對(duì)象被當(dāng)作參數(shù)傳遞到一個(gè)方法后吹散,此方法可改變這個(gè)對(duì)象的屬性,并可返回變化后的結(jié)果八酒,那么這里到底是值傳遞還是引用傳遞空民?
是值傳遞。Java語(yǔ)言的方法調(diào)用只支持參數(shù)的值傳遞羞迷。當(dāng)一個(gè)對(duì)象實(shí)例作為一個(gè)參數(shù)被傳遞到方法中時(shí)界轩,參數(shù)的值就是對(duì)該對(duì)象的引用。對(duì)象的屬性可以在被調(diào)用過程中被改變衔瓮,但對(duì)對(duì)象引用的改變是不會(huì)影響到調(diào)用者的浊猾。
說明:Java中沒有傳引用實(shí)在是非常的不方便,這一點(diǎn)在Java 8中仍然沒有得到改進(jìn)热鞍,正是如此在Java編寫的代碼中才會(huì)出現(xiàn)大量的Wrapper類(將需要通過方法調(diào)用修改的引用置于一個(gè)Wrapper類中葫慎,再將Wrapper對(duì)象傳入方法),這樣的做法只會(huì)讓代碼變得臃腫薇宠,尤其是讓從C和C++轉(zhuǎn)型為Java程序員的開發(fā)者無法容忍偷办。
- 描述一下JVM加載class文件的原理機(jī)制?
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實(shí)現(xiàn)的澄港,Java中的類加載器是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件椒涯,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類。
由于Java的跨平臺(tái)性慢睡,經(jīng)過編譯的Java源程序并不是一個(gè)可執(zhí)行程序逐工,而是一個(gè)或多個(gè)類文件铡溪。當(dāng)Java程序需要使用某個(gè)類時(shí)漂辐,JVM會(huì)確保這個(gè)類已經(jīng)被加載、連接(驗(yàn)證棕硫、準(zhǔn)備和解析)和初始化髓涯。
類的加載是指把類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中,通常是創(chuàng)建一個(gè)字節(jié)數(shù)組讀入.class文件哈扮,然后產(chǎn)生與所加載類對(duì)應(yīng)的Class對(duì)象纬纪。加載完成后,Class對(duì)象還不完整滑肉,所以此時(shí)的類還不可用包各。
當(dāng)類被加載后就進(jìn)入連接階段,這一階段包括驗(yàn)證靶庙、準(zhǔn)備(為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值)和解析(將符號(hào)引用替換為直接引用)三個(gè)步驟问畅。
最后JVM對(duì)類進(jìn)行初始化,包括:1)如果類存在直接的父類并且這個(gè)類還沒有被初始化,那么就先初始化父類护姆;2)如果類中存在初始化語(yǔ)句矾端,就依次執(zhí)行這些初始化語(yǔ)句。
類的加載是由類加載器完成的卵皂,類加載器包括:根加載器(BootStrap)秩铆、擴(kuò)展加載器(Extension)、系統(tǒng)加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)灯变。從Java 2(JDK 1.2)開始殴玛,類加載過程采取了父親委托機(jī)制(PDM)。PDM更好的保證了Java平臺(tái)的安全性添祸,在該機(jī)制中族阅,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個(gè)父類加載器膝捞。類的加載首先請(qǐng)求父類加載器加載坦刀,父類加載器無能為力時(shí)才由其子類加載器自行加載。JVM不會(huì)向Java程序提供對(duì)Bootstrap的引用蔬咬。下面是關(guān)于幾個(gè)類加載器的說明:
- Bootstrap:一般用本地代碼實(shí)現(xiàn)鲤遥,負(fù)責(zé)加載JVM基礎(chǔ)核心類庫(kù)(rt.jar);
- Extension:從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù)林艘,它的父加載器是Bootstrap盖奈;
- System:又叫應(yīng)用類加載器,其父類是Extension狐援。它是應(yīng)用最廣泛的類加載器钢坦。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認(rèn)父加載器啥酱。
- 抽象類(abstract class)和接口(interface)有什么異同?
抽象類和接口都不能夠?qū)嵗迹梢远x抽象類和接口類型的引用。一個(gè)類如果繼承了某個(gè)抽象類或者實(shí)現(xiàn)了某個(gè)接口都需要對(duì)其中的抽象方法全部進(jìn)行實(shí)現(xiàn)镶殷,否則該類仍然需要被聲明為抽象類禾酱。接口比抽象類更加抽象,因?yàn)槌橄箢愔锌梢远x構(gòu)造器绘趋,可以有抽象方法和具體方法颤陶,而接口中不能定義構(gòu)造器而且其中的方法全部都是抽象方法。抽象類中的成員可以是private陷遮、默認(rèn)滓走、protected、public的帽馋,而接口中的成員全都是public的搅方。抽象類中可以定義成員變量障斋,而接口中定義的成員變量實(shí)際上都是常量症革。有抽象方法的類必須被聲明為抽象類,而抽象類未必要有抽象方法。
- 闡述靜態(tài)變量和實(shí)例變量的區(qū)別谤碳。
靜態(tài)變量是被static修飾符修飾的變量宛渐,也稱為類變量牍戚,它屬于類伦仍,不屬于類的任何一個(gè)對(duì)象,一個(gè)類不管創(chuàng)建多少個(gè)對(duì)象怖喻,靜態(tài)變量在內(nèi)存中有且僅有一個(gè)拷貝底哗;實(shí)例變量必須依存于某一實(shí)例,需要先創(chuàng)建對(duì)象然后通過對(duì)象才能訪問到它锚沸。靜態(tài)變量可以實(shí)現(xiàn)讓多個(gè)對(duì)象共享內(nèi)存跋选。
- 如何實(shí)現(xiàn)對(duì)象克隆哗蜈?
有兩種方式:
??1). 實(shí)現(xiàn)Cloneable接口并重寫Object類中的clone()方法前标;
??2). 實(shí)現(xiàn)Serializable接口,通過對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆距潘,可以實(shí)現(xiàn)真正的深度克隆炼列,代碼如下。
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();
// 說明:調(diào)用ByteArrayInputStream或ByteArrayOutputStream對(duì)象的close方法沒有任何意義
// 這兩個(gè)基于內(nèi)存的流只要垃圾回收器清理對(duì)象就能夠釋放資源音比,這一點(diǎn)不同于對(duì)外部資源(如文件流)的釋放
}
}
注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆俭尖,更重要的是通過泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化洞翩,這項(xiàng)檢查是編譯器完成的稽犁,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對(duì)象骚亿。讓問題在編譯的時(shí)候暴露出來總是好過把問題留到運(yùn)行時(shí)已亥。
- GC是什么?為什么要有GC循未?
GC是垃圾收集的意思陷猫,內(nèi)存處理是編程人員容易出現(xiàn)問題的地方秫舌,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰的妖,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語(yǔ)言沒有提供釋放已分配內(nèi)存的顯示操作方法足陨。
Java程序員不用擔(dān)心內(nèi)存管理嫂粟,因?yàn)槔占鲿?huì)自動(dòng)進(jìn)行管理。要請(qǐng)求垃圾收集墨缘,可以調(diào)用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() 星虹,但JVM可以屏蔽掉顯示的垃圾回收調(diào)用零抬。
垃圾回收可以有效的防止內(nèi)存泄露,有效的使用可以使用的內(nèi)存宽涌。垃圾回收器通常是作為一個(gè)單獨(dú)的低優(yōu)先級(jí)的線程運(yùn)行平夜,不可預(yù)知的情況下對(duì)內(nèi)存堆中已經(jīng)死亡的或者長(zhǎng)時(shí)間沒有使用的對(duì)象進(jìn)行清除和回收,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對(duì)某個(gè)對(duì)象或所有對(duì)象進(jìn)行垃圾回收卸亮。
在Java誕生初期忽妒,垃圾回收是Java最大的亮點(diǎn)之一,因?yàn)榉?wù)器端的編程需要有效的防止內(nèi)存泄露問題兼贸,然而時(shí)過境遷段直,如今Java的垃圾回收機(jī)制已經(jīng)成為被詬病的東西。移動(dòng)智能終端用戶通常覺得IOS的系統(tǒng)比Android系統(tǒng)有更好的用戶體驗(yàn)溶诞,其中一個(gè)深層次的原因就在于Android系統(tǒng)中垃圾回收的不可預(yù)知性鸯檬。
補(bǔ)充:垃圾回收機(jī)制有很多種,包括:分代復(fù)制垃圾回收螺垢、標(biāo)記垃圾回收喧务、增量垃圾回收等方式。標(biāo)準(zhǔn)的Java進(jìn)程既有棧又有堆枉圃。棧保存了原始型局部變量蹂楣,堆保存了要?jiǎng)?chuàng)建的對(duì)象。Java平臺(tái)對(duì)堆內(nèi)存回收和再利用的基本算法被稱為標(biāo)記和清除讯蒲,但是Java對(duì)其進(jìn)行了改進(jìn)痊土,采用“分代式垃圾收集”。這種方法會(huì)跟Java對(duì)象的生命周期將堆內(nèi)存劃分為不同的區(qū)域墨林,在垃圾收集過程中赁酝,可能會(huì)將對(duì)象移動(dòng)到不同區(qū)域:
- 伊甸園(Eden):這是對(duì)象最初誕生的區(qū)域,并且對(duì)大多數(shù)對(duì)象來說旭等,這里是它們唯一存在過的區(qū)域酌呆。
- 幸存者樂園(Survivor):從伊甸園幸存下來的對(duì)象會(huì)被挪到這里。
- 終身頤養(yǎng)園(Tenured):這是足夠老的幸存對(duì)象的歸宿搔耕。年輕代收集(Minor-GC)過程是不會(huì)觸及這個(gè)地方的隙袁。當(dāng)年輕代收集不能把對(duì)象放進(jìn)終身頤養(yǎng)園時(shí),就會(huì)觸發(fā)一次完全收集(Major-GC)弃榨,這里可能還會(huì)牽扯到壓縮菩收,以便為大對(duì)象騰出足夠的空間。
與垃圾回收相關(guān)的JVM參數(shù):
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年輕代的大小
-XX:-DisableExplicitGC — 讓System.gc()不產(chǎn)生任何作用
-XX:+PrintGCDetails — 打印GC的細(xì)節(jié)
-XX:+PrintGCDateStamps — 打印GC操作的時(shí)間戳
-XX:NewSize / XX:MaxNewSize — 設(shè)置新生代大小/新生代最大大小
-XX:NewRatio — 可以設(shè)置老生代和新生代的比例
-XX:PrintTenuringDistribution — 設(shè)置每次新生代GC后輸出幸存者樂園中對(duì)象年齡的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設(shè)置老年代閥值的初始值和最大值
-XX:TargetSurvivorRatio:設(shè)置幸存區(qū)的目標(biāo)使用率
- String s = new String("xyz");創(chuàng)建了幾個(gè)字符串對(duì)象鲸睛?
兩個(gè)對(duì)象娜饵,一個(gè)是靜態(tài)區(qū)的"xyz",一個(gè)是用new創(chuàng)建在堆上的對(duì)象官辈。
- 指出下面程序的運(yùn)行結(jié)果箱舞。
class A {
static {
System.out.print("1");
}
public A() {
System.out.print("2");
}
}
class B extends A{
static {
System.out.print("a");
}
public B() {
System.out.print("b");
}
}
public class Hello {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
執(zhí)行結(jié)果:1a2b2b遍坟。創(chuàng)建對(duì)象時(shí)構(gòu)造器的調(diào)用順序是:先初始化靜態(tài)成員,然后調(diào)用父類構(gòu)造器晴股,再初始化非靜態(tài)成員愿伴,最后調(diào)用自身構(gòu)造器。
- try{}里有一個(gè)return語(yǔ)句电湘,那么緊跟在這個(gè)try后的finally{}里的代碼會(huì)不會(huì)被執(zhí)行公般,什么時(shí)候被執(zhí)行,在return前還是后?
會(huì)執(zhí)行胡桨,在方法返回調(diào)用者前執(zhí)行官帘。
注意:在finally中改變返回值的做法是不好的,因?yàn)槿绻嬖趂inally代碼塊昧谊,try中的return語(yǔ)句不會(huì)立馬返回調(diào)用者刽虹,而是記錄下返回值待finally代碼塊執(zhí)行完畢之后再向調(diào)用者返回其值,然后如果在finally中修改了返回值呢诬,就會(huì)返回修改后的值涌哲。顯然,在finally中返回或者修改返回值會(huì)對(duì)程序造成很大的困擾尚镰,C#中直接用編譯錯(cuò)誤的方式來阻止程序員干這種齷齪的事情阀圾,Java中也可以通過提升編譯器的語(yǔ)法檢查級(jí)別來產(chǎn)生警告或錯(cuò)誤,Eclipse中可以在如圖所示的地方進(jìn)行設(shè)置狗唉,強(qiáng)烈建議將此項(xiàng)設(shè)置為編譯錯(cuò)誤初烘。
- 闡述final、finally分俯、finalize的區(qū)別
- final:修飾符(關(guān)鍵字)有三種用法:如果一個(gè)類被聲明為final肾筐,意味著它不能再派生出新的子類,即不能被繼承缸剪,因此它和abstract是反義詞吗铐。將變量聲明為final,可以保證它們?cè)谑褂弥胁槐桓淖冃咏冢宦暶鳛閒inal的變量必須在聲明時(shí)給定初值唬渗,而在以后的引用中只能讀取不可修改。被聲明為final的方法也同樣只能使用奋渔,不能在子類中被重寫镊逝。
- finally:通常放在try…catch…的后面構(gòu)造總是執(zhí)行代碼塊,這就意味著程序無論正常執(zhí)行還是發(fā)生異常卒稳,這里的代碼只要JVM不關(guān)閉都能執(zhí)行蹋半,可以將釋放外部資源的代碼寫在finally塊中。
- finalize:Object類中定義的方法充坑,Java中允許使用finalize()方法在垃圾收集器將對(duì)象從內(nèi)存中清除出去之前做必要的清理工作减江。這個(gè)方法是由垃圾收集器在銷毀對(duì)象時(shí)調(diào)用的,通過重寫finalize()方法可以整理系統(tǒng)資源或者執(zhí)行其他清理工作捻爷。
- 闡述ArrayList辈灼、Vector、LinkedList的存儲(chǔ)性能和特性
ArrayList 和Vector都是使用數(shù)組方式存儲(chǔ)數(shù)據(jù)也榄,此數(shù)組元素?cái)?shù)大于實(shí)際存儲(chǔ)的數(shù)據(jù)以便增加和插入元素巡莹,它們都允許直接按序號(hào)索引元素,但是插入元素要涉及數(shù)組元素移動(dòng)等內(nèi)存操作甜紫,所以索引數(shù)據(jù)快而插入數(shù)據(jù)慢降宅,Vector中的方法由于添加了synchronized修飾,因此Vector是線程安全的容器囚霸,但性能上較ArrayList差腰根,因此已經(jīng)是Java中的遺留容器。
LinkedList使用雙向鏈表實(shí)現(xiàn)存儲(chǔ)(將內(nèi)存中零散的內(nèi)存單元通過附加的引用關(guān)聯(lián)起來拓型,形成一個(gè)可以按序號(hào)索引的線性結(jié)構(gòu)额嘿,這種鏈?zhǔn)酱鎯?chǔ)方式與數(shù)組的連續(xù)存儲(chǔ)方式相比,內(nèi)存的利用率更高)劣挫,按序號(hào)索引數(shù)據(jù)需要進(jìn)行前向或后向遍歷册养,但是插入數(shù)據(jù)時(shí)只需要記錄本項(xiàng)的前后項(xiàng)即可,所以插入速度較快压固。
Vector屬于遺留容器(Java早期的版本中提供的容器球拦,除此之外,Hashtable帐我、Dictionary刘莹、BitSet、Stack焚刚、Properties都是遺留容器)点弯,已經(jīng)不推薦使用,但是由于ArrayList和LinkedListed都是非線程安全的矿咕,如果遇到多個(gè)線程操作同一個(gè)容器的場(chǎng)景抢肛,則可以通過工具類Collections中的synchronizedList方法將其轉(zhuǎn)換成線程安全的容器后再使用(這是對(duì)裝潢模式的應(yīng)用,將已有對(duì)象傳入另一個(gè)類的構(gòu)造器中創(chuàng)建新的對(duì)象來增強(qiáng)實(shí)現(xiàn))碳柱。
遺留容器中的Properties類和Stack類在設(shè)計(jì)上有嚴(yán)重的問題捡絮,Properties是一個(gè)鍵和值都是字符串的特殊的鍵值對(duì)映射,在設(shè)計(jì)上應(yīng)該是關(guān)聯(lián)一個(gè)Hashtable并將其兩個(gè)泛型參數(shù)設(shè)置為String類型莲镣,但是Java API中的Properties直接繼承了Hashtable福稳,這很明顯是對(duì)繼承的濫用。這里復(fù)用代碼的方式應(yīng)該是Has-A關(guān)系而不是Is-A關(guān)系瑞侮,另一方面容器都屬于工具類的圆,繼承工具類本身就是一個(gè)錯(cuò)誤的做法鼓拧,使用工具類最好的方式是Has-A關(guān)系(關(guān)聯(lián))或Use-A關(guān)系(依賴)。同理越妈,Stack類繼承Vector也是不正確的季俩。Sun公司的工程師們也會(huì)犯這種低級(jí)錯(cuò)誤,讓人唏噓不已梅掠。
- Collection和Collections的區(qū)別?
Collection是一個(gè)接口酌住,它是Set、List等容器的父接口阎抒;Collections是個(gè)一個(gè)工具類酪我,提供了一系列的靜態(tài)方法來輔助容器操作,這些方法包括對(duì)容器的搜索且叁、排序都哭、線程安全化等。
- Thread類的sleep()方法和對(duì)象的wait()方法都可以讓線程暫停執(zhí)行谴古,它們有什么區(qū)別?
sleep()方法(休眠)是線程類(Thread)的靜態(tài)方法质涛,調(diào)用此方法會(huì)讓當(dāng)前線程暫停執(zhí)行指定的時(shí)間舅锄,將執(zhí)行機(jī)會(huì)(CPU)讓給其他線程泌绣,但是對(duì)象的鎖依然保持,因此休眠時(shí)間結(jié)束后會(huì)自動(dòng)恢復(fù)(線程回到就緒狀態(tài))宦搬。
wait()是Object類的方法带饱,調(diào)用對(duì)象的wait()方法導(dǎo)致當(dāng)前線程放棄對(duì)象的鎖(線程暫停執(zhí)行)毡代,進(jìn)入對(duì)象的等待池(wait pool),只有調(diào)用對(duì)象的notify()方法(或notifyAll()方法)時(shí)才能喚醒等待池中的線程進(jìn)入等鎖池(lock pool)勺疼,如果線程重新獲得對(duì)象的鎖就可以進(jìn)入就緒狀態(tài)教寂。
可能不少人對(duì)什么是進(jìn)程,什么是線程還比較模糊执庐,對(duì)于為什么需要多線程編程也不是特別理解酪耕。簡(jiǎn)單的說:進(jìn)程是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位轨淌;線程是進(jìn)程的一個(gè)實(shí)體迂烁,是CPU調(diào)度和分派的基本單位,是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位递鹉。線程的劃分尺度小于進(jìn)程盟步,這使得多線程程序的并發(fā)性高;進(jìn)程在執(zhí)行時(shí)通常擁有獨(dú)立的內(nèi)存單元躏结,而線程之間可以共享內(nèi)存却盘。使用多線程的編程通常能夠帶來更好的性能和用戶體驗(yàn),但是多線程的程序?qū)τ谄渌绦蚴遣挥押玫模驗(yàn)樗赡苷加昧烁嗟腃PU資源黄橘。當(dāng)然兆览,也不是線程越多,程序的性能就越好旬陡,因?yàn)榫€程之間的調(diào)度和切換也會(huì)浪費(fèi)CPU時(shí)間拓颓。時(shí)下很時(shí)髦的Node.js就采用了單線程異步I/O的工作模式语婴。
- 線程的sleep()方法和yield()方法有什么區(qū)別描孟?
① sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì)砰左;yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì)匿醒;
② 線程執(zhí)行sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài)缠导;
③ sleep()方法聲明拋出InterruptedException廉羔,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性僻造。
- 編寫多線程程序有幾種實(shí)現(xiàn)方式憋他?
Java 5以前實(shí)現(xiàn)多線程有兩種實(shí)現(xiàn)方法:一種是繼承Thread類;另一種是實(shí)現(xiàn)Runnable接口髓削。兩種方式都要通過重寫run()方法來定義線程的行為竹挡,推薦使用后者,因?yàn)镴ava中的繼承是單繼承立膛,一個(gè)類有一個(gè)父類揪罕,如果繼承了Thread類就無法再繼承其他類了,顯然使用Runnable接口更為靈活宝泵。
補(bǔ)充:Java 5以后創(chuàng)建線程還有第三種方式:實(shí)現(xiàn)Callable接口好啰,該接口中的call方法可以在線程執(zhí)行結(jié)束時(shí)產(chǎn)生一個(gè)返回值,代碼如下所示:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyTask implements Callable<Integer> {
private int upperBounds;
public MyTask(int upperBounds) {
this.upperBounds = upperBounds;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= upperBounds; i++) {
sum += i;
}
return sum;
}
}
class Test {
public static void main(String[] args) throws Exception {
List<Future<Integer>> list = new ArrayList<>();
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++) {
list.add(service.submit(new MyTask((int) (Math.random() * 100))));
}
int sum = 0;
for(Future<Integer> future : list) {
// while(!future.isDone()) ;
sum += future.get();
}
System.out.println(sum);
}
}
-
什么是線程池(thread pool)儿奶?
在面向?qū)ο缶幊讨锌蛲瑒?chuàng)建和銷毀對(duì)象是很費(fèi)時(shí)間的,因?yàn)閯?chuàng)建一個(gè)對(duì)象要獲取內(nèi)存資源或者其它更多資源闯捎。在Java中更是如此椰弊,虛擬機(jī)將試圖跟蹤每一個(gè)對(duì)象,以便能夠在對(duì)象銷毀后進(jìn)行垃圾回收隙券。所以提高服務(wù)程序效率的一個(gè)手段就是盡可能減少創(chuàng)建和銷毀對(duì)象的次數(shù)男应,特別是一些很耗資源的對(duì)象創(chuàng)建和銷毀,這就是“池化資源”技術(shù)產(chǎn)生的原因娱仔。
線程池顧名思義就是事先創(chuàng)建若干個(gè)可執(zhí)行的線程放入一個(gè)池(容器)中沐飘,需要的時(shí)候從池中獲取線程不用自行創(chuàng)建,使用完畢不需要銷毀線程而是放回池中,從而減少創(chuàng)建和銷毀線程對(duì)象的開銷耐朴。
Java 5+中的Executor接口定義一個(gè)執(zhí)行線程的工具借卧。它的子類型即線程池接口是ExecutorService。要配置一個(gè)線程池是比較復(fù)雜的筛峭,尤其是對(duì)于線程池的原理不是很清楚的情況下铐刘,因此在工具類Executors面提供了一些靜態(tài)工廠方法,生成一些常用的線程池影晓,如下所示:
- newSingleThreadExecutor:創(chuàng)建一個(gè)單線程的線程池镰吵。這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)挂签。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束疤祭,那么會(huì)有一個(gè)新的線程來替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行饵婆。
- newFixedThreadPool:創(chuàng)建固定大小的線程池勺馆。每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小侨核。線程池的大小一旦達(dá)到最大值就會(huì)保持不變草穆,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程搓译。
- newCachedThreadPool:創(chuàng)建一個(gè)可緩存的線程池悲柱。如果線程池的大小超過了處理任務(wù)所需要的線程,那么就會(huì)回收部分空閑(60秒不執(zhí)行任務(wù))的線程侥衬,當(dāng)任務(wù)數(shù)增加時(shí)诗祸,此線程池又可以智能的添加新線程來處理任務(wù)。此線程池不會(huì)對(duì)線程池大小做限制轴总,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小直颅。
- newScheduledThreadPool:創(chuàng)建一個(gè)大小無限的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求怀樟。
- newSingleThreadExecutor:創(chuàng)建一個(gè)單線程的線程池功偿。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。
如果希望在服務(wù)器上使用線程池往堡,強(qiáng)烈建議使用newFixedThreadPool方法來創(chuàng)建線程池械荷,這樣能獲得更好的性能。
- 線程的基本狀態(tài)以及狀態(tài)之間的關(guān)系虑灰?
說明:其中Running表示運(yùn)行狀態(tài)吨瞎,Runnable表示就緒狀態(tài)(萬事俱備,只欠CPU)穆咐,Blocked表示阻塞狀態(tài)颤诀,阻塞狀態(tài)又有多種情況字旭,可能是因?yàn)檎{(diào)用wait()方法進(jìn)入等待池,也可能是執(zhí)行同步方法或同步代碼塊進(jìn)入等鎖池崖叫,或者是調(diào)用了sleep()方法或join()方法等待休眠或其他線程結(jié)束遗淳,或是因?yàn)榘l(fā)生了I/O中斷。
- 簡(jiǎn)述synchronized 和java.util.concurrent.locks.Lock的異同心傀?
Lock是Java 5以后引入的新的API屈暗,和關(guān)鍵字synchronized相比主要相同點(diǎn):Lock 能完成synchronized所實(shí)現(xiàn)的所有功能;主要不同點(diǎn):Lock有比synchronized更精確的線程語(yǔ)義和更好的性能脂男,而且不強(qiáng)制性的要求一定要獲得鎖养叛。synchronized會(huì)自動(dòng)釋放鎖,而Lock一定要求程序員手工釋放疆液,并且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)一铅。
- Java中如何實(shí)現(xiàn)序列化陕贮,有什么意義堕油?
序列化就是一種用來處理對(duì)象流的機(jī)制,所謂對(duì)象流也就是將對(duì)象的內(nèi)容進(jìn)行流化肮之〉羧保可以對(duì)流化后的對(duì)象進(jìn)行讀寫操作,也可將流化后的對(duì)象傳輸于網(wǎng)絡(luò)之間戈擒。序列化是為了解決對(duì)象流讀寫操作時(shí)可能引發(fā)的問題(如果不進(jìn)行序列化可能會(huì)存在數(shù)據(jù)亂序的問題)眶明。
要實(shí)現(xiàn)序列化,需要讓一個(gè)類實(shí)現(xiàn)Serializable接口筐高,該接口是一個(gè)標(biāo)識(shí)性接口搜囱,標(biāo)注該類對(duì)象是可被序列化的,然后使用一個(gè)輸出流來構(gòu)造一個(gè)對(duì)象輸出流并通過writeObject(Object)方法就可以將實(shí)現(xiàn)對(duì)象寫出(即保存其狀態(tài))柑土;如果需要反序列化則可以用一個(gè)輸入流建立對(duì)象輸入流蜀肘,然后通過readObject方法從流中讀取對(duì)象。序列化除了能夠?qū)崿F(xiàn)對(duì)象的持久化之外稽屏,還能夠用于對(duì)象的深度克隆扮宠。
- Java中有幾種類型的流?
字節(jié)流和字符流狐榔。字節(jié)流繼承于InputStream坛增、OutputStream,字符流繼承于Reader薄腻、Writer收捣。在java.io 包中還有許多其他的流,主要是為了提高性能和使用方便庵楷。關(guān)于Java的I/O需要注意的有兩點(diǎn):一是兩種對(duì)稱性(輸入和輸出的對(duì)稱性罢艾,字節(jié)和字符的對(duì)稱性)萝玷;二是兩種設(shè)計(jì)模式(適配器模式和裝潢模式)。
面試題 - 編程實(shí)現(xiàn)文件拷貝昆婿。(這個(gè)題目在筆試的時(shí)候經(jīng)常出現(xiàn)球碉,下面的代碼給出了兩種實(shí)現(xiàn)方案)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public final class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static void fileCopy(String source, String target) throws IOException {
try (InputStream in = new FileInputStream(source)) {
try (OutputStream out = new FileOutputStream(target)) {
byte[] buffer = new byte[4096];
int bytesToRead;
while((bytesToRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
}
}
}
public static void fileCopyNIO(String source, String target) throws IOException {
try (FileInputStream in = new FileInputStream(source)) {
try (FileOutputStream out = new FileOutputStream(target)) {
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
while(inChannel.read(buffer) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
}
}
}
}
注意:上面用到Java 7的TWR,使用TWR后可以不用在finally中釋放外部資源 仓蛆,從而讓代碼更加優(yōu)雅睁冬。
- 寫一個(gè)方法,輸入一個(gè)文件名和一個(gè)字符串看疙,統(tǒng)計(jì)這個(gè)字符串在這個(gè)文件中出現(xiàn)的次數(shù)豆拨。
import java.io.BufferedReader;
import java.io.FileReader;
public final class MyUtil {
// 工具類中的方法都是靜態(tài)方式訪問的因此將構(gòu)造器私有不允許創(chuàng)建對(duì)象(絕對(duì)好習(xí)慣)
private MyUtil() {
throw new AssertionError();
}
/**
* 統(tǒng)計(jì)給定文件中給定字符串的出現(xiàn)次數(shù)
*
* @param filename 文件名
* @param word 字符串
* @return 字符串在文件中出現(xiàn)的次數(shù)
*/
public static int countWordInFile(String filename, String word) {
int counter = 0;
try (FileReader fr = new FileReader(filename)) {
try (BufferedReader br = new BufferedReader(fr)) {
String line = null;
while ((line = br.readLine()) != null) {
int index = -1;
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;
line = line.substring(index + word.length());
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return counter;
}
}
- 如何用Java代碼列出一個(gè)目錄下所有的文件?
import java.io.File;
class Test12 {
public static void main(String[] args) {
File f = new File("/Users/Hao/Downloads");
for(File temp : f.listFiles()) {
if(temp.isFile()) {
System.out.println(temp.getName());
}
}
}
}
如果需要對(duì)文件夾繼續(xù)展開施禾,代碼如下所示:
import java.io.File;
class Test12 {
public static void main(String[] args) {
showDirectory(new File("/Users/Hao/Downloads"));
}
public static void showDirectory(File f) {
_walkDirectory(f, 0);
}
private static void _walkDirectory(File f, int level) {
if(f.isDirectory()) {
for(File temp : f.listFiles()) {
_walkDirectory(temp, level + 1);
}
}
else {
for(int i = 0; i < level - 1; i++) {
System.out.print("\t");
}
System.out.println(f.getName());
}
}
}
在Java 7中可以使用NIO.2的API來做同樣的事情,代碼如下所示:
class ShowFileTest {
public static void main(String[] args) throws IOException {
Path initPath = Paths.get("/Users/Hao/Downloads");
Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
}
}
- 在進(jìn)行數(shù)據(jù)庫(kù)編程時(shí)搁胆,連接池有什么作用弥搞?
由于創(chuàng)建連接和釋放連接都有很大的開銷(尤其是數(shù)據(jù)庫(kù)服務(wù)器不在本地時(shí),每次建立連接都需要進(jìn)行TCP的三次握手渠旁,釋放連接需要進(jìn)行TCP四次握手攀例,造成的開銷是不可忽視的),為了提升系統(tǒng)訪問數(shù)據(jù)庫(kù)的性能顾腊,可以事先創(chuàng)建若干連接置于連接池中粤铭,需要時(shí)直接從連接池獲取,使用結(jié)束時(shí)歸還連接池而不必關(guān)閉連接杂靶,從而避免頻繁創(chuàng)建和釋放連接所造成的開銷梆惯,這是典型的用空間換取時(shí)間的策略(浪費(fèi)了空間存儲(chǔ)連接,但節(jié)省了創(chuàng)建和釋放連接的時(shí)間)吗垮。
池化技術(shù)在Java開發(fā)中是很常見的垛吗,在使用線程時(shí)創(chuàng)建線程池的道理與此相同”Ъ龋基于Java的開源數(shù)據(jù)庫(kù)連接池主要有:C3P0职烧、Proxool、DBCP防泵、BoneCP蚀之、Druid等。
在計(jì)算機(jī)系統(tǒng)中時(shí)間和空間是不可調(diào)和的矛盾捷泞,理解這一點(diǎn)對(duì)設(shè)計(jì)滿足性能要求的算法是至關(guān)重要的足删。大型網(wǎng)站性能優(yōu)化的一個(gè)關(guān)鍵就是使用緩存,而緩存跟上面講的連接池道理非常類似锁右,也是使用空間換時(shí)間的策略失受⊙忍可以將熱點(diǎn)數(shù)據(jù)置于緩存中,當(dāng)用戶查詢這些數(shù)據(jù)時(shí)可以直接從緩存中得到拂到,這無論如何也快過去數(shù)據(jù)庫(kù)中查詢痪署。當(dāng)然,緩存的置換策略等也會(huì)對(duì)系統(tǒng)性能產(chǎn)生重要影響兄旬,對(duì)于這個(gè)問題的討論已經(jīng)超出了這里要闡述的范圍妹孙。
- 事務(wù)的ACID是指什么总珠?
- 原子性(Atomic):事務(wù)中各項(xiàng)操作永罚,要么全做要么全不做荐糜,任何一項(xiàng)操作的失敗都會(huì)導(dǎo)致整個(gè)事務(wù)的失敗绪撵;
- 一致性(Consistent):事務(wù)結(jié)束后系統(tǒng)狀態(tài)是一致的瓢姻;
- 隔離性(Isolated):并發(fā)執(zhí)行的事務(wù)彼此無法看到對(duì)方的中間狀態(tài);
- 持久性(Durable):事務(wù)完成后所做的改動(dòng)都會(huì)被持久化音诈,即使發(fā)生災(zāi)難性的失敗幻碱。通過日志和同步備份可以在故障發(fā)生后重建數(shù)據(jù)。
補(bǔ)充:關(guān)于事務(wù)改艇,在面試中被問到的概率是很高的收班,可以問的問題也是很多的。首先需要知道的是谒兄,只有存在并發(fā)數(shù)據(jù)訪問時(shí)才需要事務(wù)。當(dāng)多個(gè)事務(wù)訪問同一數(shù)據(jù)時(shí)社付,可能會(huì)存在5類問題承疲,包括3類數(shù)據(jù)讀取問題(臟讀、不可重復(fù)讀和幻讀)和2類數(shù)據(jù)更新問題(第1類丟失更新和第2類丟失更新)鸥咖。
臟讀(Dirty Read):A事務(wù)讀取B事務(wù)尚未提交的數(shù)據(jù)并在此基礎(chǔ)上操作燕鸽,而B事務(wù)執(zhí)行回滾,那么A讀取到的數(shù)據(jù)就是臟數(shù)據(jù)啼辣。
時(shí)間 | 轉(zhuǎn)賬事務(wù)A | 取款事務(wù)B |
---|---|---|
T1 | 開始事務(wù) | |
T2 | 開始事務(wù) | |
T3 | 查詢賬戶余額為1000元 | |
T4 | 取出500元余額修改為500元 | |
T5 | 查詢賬戶余額為500元(臟讀) | |
T6 | 撤銷事務(wù)余額恢復(fù)為1000元 | |
T7 | 匯入100元把余額修改為600元 | |
T8 | 提交事務(wù) |
不可重復(fù)讀(Unrepeatable Read):事務(wù)A重新讀取前面讀取過的數(shù)據(jù)啊研,發(fā)現(xiàn)該數(shù)據(jù)已經(jīng)被另一個(gè)已提交的事務(wù)B修改過了。
時(shí)間 | 轉(zhuǎn)賬事務(wù)A | 取款事務(wù)B |
---|---|---|
T1 | 開始事務(wù) | |
T2 | 開始事務(wù) | |
T3 | 查詢賬戶余額為1000元 | |
T4 | 查詢賬戶余額為1000元 | |
T5 | 取出100元修改余額為900元 | |
T6 | 提交事務(wù) | |
T7 | 查詢賬戶余額為900元(不可重復(fù)讀) |
幻讀(Phantom Read):事務(wù)A重新執(zhí)行一個(gè)查詢鸥拧,返回一系列符合查詢條件的行党远,發(fā)現(xiàn)其中插入了被事務(wù)B提交的行。
時(shí)間 | 統(tǒng)計(jì)金額事務(wù)A | 轉(zhuǎn)賬事務(wù)B |
---|---|---|
T1 | 開始事務(wù) | |
T2 | 開始事務(wù) | |
T3 | 統(tǒng)計(jì)總存款為10000元 | |
T4 | 新增一個(gè)存款賬戶存入100元 | |
T5 | 提交事務(wù) | |
T6 | 再次統(tǒng)計(jì)總存款為10100元(幻讀) |
第1類丟失更新:事務(wù)A撤銷時(shí)富弦,把已經(jīng)提交的事務(wù)B的更新數(shù)據(jù)覆蓋了沟娱。
時(shí)間 | 取款事務(wù)A | 轉(zhuǎn)賬事務(wù)B |
---|---|---|
T1 | 開始事務(wù) | |
T2 | 開始事務(wù) | |
T3 | 查詢賬戶余額為1000元 | |
T4 | 查詢賬戶余額為1000元 | |
T5 | 匯入100元修改余額為1100元 | |
T6 | 提交事務(wù) | |
T7 | 取出100元將余額修改為900元 | |
T8 | 撤銷事務(wù) | |
T9 | 余額恢復(fù)為1000元(丟失更新) |
第2類丟失更新:事務(wù)A覆蓋事務(wù)B已經(jīng)提交的數(shù)據(jù),造成事務(wù)B所做的操作丟失腕柜。
時(shí)間 | 轉(zhuǎn)賬事務(wù)A | 取款事務(wù)B |
---|---|---|
T1 | 開始事務(wù) | |
T2 | 開始事務(wù) | |
T3 | 查詢賬戶余額為1000元 | |
T4 | 查詢賬戶余額為1000元 | |
T5 | 取出100元將余額修改為900元 | |
T6 | 提交事務(wù) | |
T7 | 匯入100元將余額修改為1100元 | |
T8 | 提交事務(wù) | |
T9 | 查詢賬戶余額為1100元(丟失更新) |
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 | 第一類丟失更新 | 第二類丟失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允許 | 允許 | 允許 | 不允許 | 允許 |
READ COMMITTED | 不允許 | 允許 | 允許 | 不允許 | 允許 |
REPEATABLE READ | 不允許 | 不允許 | 允許 | 不允許 | 不允許 |
SERIALIZABLE | 不允許 | 不允許 | 不允許 | 不允許 | 不允許 |
數(shù)據(jù)并發(fā)訪問所產(chǎn)生的問題济似,在有些場(chǎng)景下可能是允許的矫废,但是有些場(chǎng)景下可能就是致命的,數(shù)據(jù)庫(kù)通常會(huì)通過鎖機(jī)制來解決數(shù)據(jù)并發(fā)訪問問題砰蠢,按鎖定對(duì)象不同可以分為表級(jí)鎖和行級(jí)鎖蓖扑;按并發(fā)事務(wù)鎖定關(guān)系可以分為共享鎖和獨(dú)占鎖,具體的內(nèi)容大家可以自行查閱資料進(jìn)行了解台舱。
直接使用鎖是非常麻煩的赵誓,為此數(shù)據(jù)庫(kù)為用戶提供了自動(dòng)鎖機(jī)制,只要用戶指定會(huì)話的事務(wù)隔離級(jí)別柿赊,數(shù)據(jù)庫(kù)就會(huì)通過分析SQL語(yǔ)句然后為事務(wù)訪問的資源加上合適的鎖俩功,此外,數(shù)據(jù)庫(kù)還會(huì)維護(hù)這些鎖通過各種手段提高系統(tǒng)的性能碰声,這些對(duì)用戶來說都是透明的(就是說你不用理解诡蜓,事實(shí)上我確實(shí)也不知道)胰挑。ANSI/ISO SQL 92標(biāo)準(zhǔn)定義了4個(gè)等級(jí)的事務(wù)隔離級(jí)別蔓罚,如下表所示:
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 | 第一類丟失更新 | 第二類丟失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允許 | 允許 | 允許 | 不允許 | 允許 |
READ COMMITTED | 不允許 | 允許 | 允許 | 不允許 | 允許 |
REPEATABLE READ | 不允許 | 不允許 | 允許 | 不允許 | 不允許 |
SERIALIZABLE | 不允許 | 不允許 | 不允許 | 不允許 | 不允許 |
需要說明的是,事務(wù)隔離級(jí)別和數(shù)據(jù)訪問的并發(fā)性是對(duì)立的瞻颂,事務(wù)隔離級(jí)別越高并發(fā)性就越差豺谈。所以要根據(jù)具體的應(yīng)用來確定合適的事務(wù)隔離級(jí)別,這個(gè)地方?jīng)]有萬能的原則贡这。
-
Java中是如何支持正則表達(dá)式操作的茬末?
Java中的String類提供了支持正則表達(dá)式操作的方法,包括:matches()盖矫、replaceAll()丽惭、replaceFirst()、split()辈双。此外责掏,Java中可以用Pattern類表示正則表達(dá)式對(duì)象,它提供了豐富的API進(jìn)行各種正則表達(dá)式操作湃望,請(qǐng)參考下面面試題的代碼换衬。
面試題: - 如果要從字符串中截取第一個(gè)英文左括號(hào)之前的字符串,例如:北京市(朝陽(yáng)區(qū))(西城區(qū))(海淀區(qū))证芭,截取結(jié)果為:北京市瞳浦,那么正則表達(dá)式怎么寫?
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpTest {
public static void main(String[] args) {
String str = "北京市(朝陽(yáng)區(qū))(西城區(qū))(海淀區(qū))";
Pattern p = Pattern.compile(".*?(?=\\()");
Matcher m = p.matcher(str);
if(m.find()) {
System.out.println(m.group());
}
}
}
說明:上面的正則表達(dá)式中使用了懶惰匹配和前瞻檩帐,如果不清楚這些內(nèi)容术幔,推薦讀一下網(wǎng)上很有名的《正則表達(dá)式30分鐘入門教程》。
- 獲得一個(gè)類的類對(duì)象有哪些方式湃密?
- 方法1:類型.class诅挑,例如:String.class 四敞;
- 方法2:對(duì)象.getClass(),例如:"hello".getClass() 拔妥;
- 方法3:Class.forName()忿危,例如:Class.forName("java.lang.String")。
- 如何通過反射創(chuàng)建對(duì)象没龙?
- 方法1:通過類對(duì)象調(diào)用newInstance()方法铺厨,例如:String.class.newInstance() ;
- 方法2:通過類對(duì)象的getConstructor()或getDeclaredConstructor()方法獲得構(gòu)造器(Constructor)對(duì)象并調(diào)用其newInstance()方法創(chuàng)建對(duì)象硬纤,例如:String.class.getConstructor(String.class).newInstance("Hello")解滓。
-
如何通過反射獲取和設(shè)置對(duì)象私有字段的值?
可以通過類對(duì)象的getDeclaredField()方法字段(Field)對(duì)象筝家,然后再通過字段對(duì)象的setAccessible(true)將其設(shè)置為可以訪問洼裤,接下來就可以通過get/set方法來獲取/設(shè)置字段的值了。下面的代碼實(shí)現(xiàn)了一個(gè)反射的工具類溪王,其中的兩個(gè)靜態(tài)方法分別用于獲取和設(shè)置私有字段的值腮鞍,字段可以是基本類型也可以是對(duì)象類型且支持多級(jí)對(duì)象操作,例如ReflectionUtil.get(dog, "owner.car.engine.id");可以獲得dog對(duì)象的主人的汽車的引擎的ID號(hào)莹菱。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/**
* 反射工具類
* @author 駱昊
*
*/
public class ReflectionUtil {
private ReflectionUtil() {
throw new AssertionError();
}
/**
* 通過反射取對(duì)象指定字段(屬性)的值
* @param target 目標(biāo)對(duì)象
* @param fieldName 字段的名字
* @throws 如果取不到對(duì)象指定字段的值則拋出異常
* @return 字段的值
*/
public static Object getValue(Object target, String fieldName) {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for(int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
f.setAccessible(true);
target = f.get(target);
clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]);
f.setAccessible(true);
return f.get(target);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 通過反射給對(duì)象的指定字段賦值
* @param target 目標(biāo)對(duì)象
* @param fieldName 字段的名稱
* @param value 值
*/
public static void setValue(Object target, String fieldName, Object value) {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for(int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
f.setAccessible(true);
Object val = f.get(target);
if(val == null) {
Constructor<?> c = f.getType().getDeclaredConstructor();
c.setAccessible(true);
val = c.newInstance();
f.set(target, val);
}
target = val;
clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]);
f.setAccessible(true);
f.set(target, value);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
-
如何通過反射調(diào)用對(duì)象的方法移国?
請(qǐng)看下面的代碼:
import java.lang.reflect.Method;
class MethodInvokeTest {
public static void main(String[] args) throws Exception {
String str = "hello";
Method m = str.getClass().getMethod("toUpperCase");
System.out.println(m.invoke(str)); // HELLO
}
}
-
用Java寫一個(gè)冒泡排序
冒泡排序幾乎是個(gè)程序員都寫得出來,但是面試的時(shí)候如何寫一個(gè)逼格高的冒泡排序卻不是每個(gè)人都能做到道伟,下面提供一個(gè)參考代碼:
import java.util.Comparator;
/**
* 排序器接口(策略模式: 將算法封裝到具有共同接口的獨(dú)立的類中使得它們可以相互替換)
* @author駱昊
*
*/
public interface Sorter {
/**
* 排序
* @param list 待排序的數(shù)組
*/
public <T extends Comparable<T>> void sort(T[] list);
/**
* 排序
* @param list 待排序的數(shù)組
* @param comp 比較兩個(gè)對(duì)象的比較器
*/
public <T> void sort(T[] list, Comparator<T> comp);
}
import java.util.Comparator;
/**
* 冒泡排序
*
* @author駱昊
*
*/
public class BubbleSorter implements Sorter {
@Override
public <T extends Comparable<T>> void sort(T[] list) {
boolean swapped = true;
for (int i = 1, len = list.length; i < len && swapped; ++i) {
swapped = false;
for (int j = 0; j < len - i; ++j) {
if (list[j].compareTo(list[j + 1]) > 0) {
T temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
swapped = true;
}
}
}
}
@Override
public <T> void sort(T[] list, Comparator<T> comp) {
boolean swapped = true;
for (int i = 1, len = list.length; i < len && swapped; ++i) {
swapped = false;
for (int j = 0; j < len - i; ++j) {
if (comp.compare(list[j], list[j + 1]) > 0) {
T temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
swapped = true;
}
}
}
}
}
-
用Java寫一個(gè)折半查找
折半查找迹缀,也稱二分查找、二分搜索皱卓,是一種在有序數(shù)組中查找某一特定元素的搜索算法裹芝。搜素過程從數(shù)組的中間元素開始,如果中間元素正好是要查找的元素娜汁,則搜素過程結(jié)束;如果某一特定元素大于或者小于中間元素兄朋,則在數(shù)組大于或小于中間元素的那一半中查找掐禁,而且跟開始一樣從中間元素開始比較。如果在某一步驟數(shù)組已經(jīng)為空颅和,則表示找不到指定的元素傅事。這種搜索算法每一次比較都使搜索范圍縮小一半,其時(shí)間復(fù)雜度是O(logN)峡扩。
import java.util.Comparator;
public class MyUtil {
public static <T extends Comparable<T>> int binarySearch(T[] x, T key) {
return binarySearch(x, 0, x.length- 1, key);
}
// 使用循環(huán)實(shí)現(xiàn)的二分查找
public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
int low = 0;
int high = x.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int cmp = comp.compare(x[mid], key);
if (cmp < 0) {
low= mid + 1;
}
else if (cmp > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -1;
}
// 使用遞歸實(shí)現(xiàn)的二分查找
private static<T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) {
if(low <= high) {
int mid = low + ((high -low) >> 1);
if(key.compareTo(x[mid])== 0) {
return mid;
}
else if(key.compareTo(x[mid])< 0) {
return binarySearch(x,low, mid - 1, key);
}
else {
return binarySearch(x,mid + 1, high, key);
}
}
return -1;
}
}
說明:上面的代碼中給出了折半查找的兩個(gè)版本蹭越,一個(gè)用遞歸實(shí)現(xiàn),一個(gè)用循環(huán)實(shí)現(xiàn)教届。需要注意的是計(jì)算中間位置時(shí)不應(yīng)該使用(high+ low) / 2的方式响鹃,因?yàn)榧臃ㄟ\(yùn)算可能導(dǎo)致整數(shù)越界驾霜,這里應(yīng)該使用以下三種方式之一:low + (high - low) / 2或low + (high – low) >> 1或(low + high) >>> 1(>>>是邏輯右移,是不帶符號(hào)位的右移)
-
Servlet接口中有哪些方法买置?
Servlet接口定義了5個(gè)方法粪糙,其中前三個(gè)方法與Servlet生命周期相關(guān):
- void init(ServletConfig config) throws ServletException
- void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
- void destory()
- java.lang.String getServletInfo()
- ServletConfig getServletConfig()
Web容器加載Servlet并將其實(shí)例化后,Servlet生命周期開始忿项,容器運(yùn)行其init()方法進(jìn)行Servlet的初始化蓉冈;請(qǐng)求到達(dá)時(shí)調(diào)用Servlet的service()方法,service()方法會(huì)根據(jù)需要調(diào)用與請(qǐng)求對(duì)應(yīng)的doGet或doPost等方法轩触;當(dāng)服務(wù)器關(guān)閉或項(xiàng)目被卸載時(shí)服務(wù)器會(huì)將Servlet實(shí)例銷毀寞酿,此時(shí)會(huì)調(diào)用Servlet的destroy()方法。
- 轉(zhuǎn)發(fā)(forward)和重定向(redirect)的區(qū)別脱柱?
forward是容器中控制權(quán)的轉(zhuǎn)向伐弹,是服務(wù)器請(qǐng)求資源,服務(wù)器直接訪問目標(biāo)地址的URL褐捻,把那個(gè)URL 的響應(yīng)內(nèi)容讀取過來掸茅,然后把這些內(nèi)容再發(fā)給瀏覽器,瀏覽器根本不知道服務(wù)器發(fā)送的內(nèi)容是從哪兒來的柠逞,所以它的地址欄中還是原來的地址昧狮。
redirect就是服務(wù)器端根據(jù)邏輯,發(fā)送一個(gè)狀態(tài)碼板壮,告訴瀏覽器重新去請(qǐng)求那個(gè)地址逗鸣,因此從瀏覽器的地址欄中可以看到跳轉(zhuǎn)后的鏈接地址,很明顯redirect無法訪問到服務(wù)器保護(hù)起來資源绰精,但是可以從一個(gè)網(wǎng)站redirect到其他網(wǎng)站撒璧。
forward更加高效,所以在滿足需要時(shí)盡量使用forward(通過調(diào)用RequestDispatcher對(duì)象的forward()方法笨使,該對(duì)象可以通過ServletRequest對(duì)象的getRequestDispatcher()方法獲得)卿樱,并且這樣也有助于隱藏實(shí)際的鏈接;在有些情況下硫椰,比如需要訪問一個(gè)其它服務(wù)器上的資源繁调,則必須使用重定向(通過HttpServletResponse對(duì)象調(diào)用其sendRedirect()方法實(shí)現(xiàn))。
- get和post請(qǐng)求的區(qū)別靶草?
①get請(qǐng)求用來從服務(wù)器上獲得資源蹄胰,而post是用來向服務(wù)器提交數(shù)據(jù);
②get將表單中數(shù)據(jù)按照name=value的形式奕翔,添加到action 所指向的URL 后面裕寨,并且兩者使用"?"連接,而各個(gè)變量之間使用"&"連接;post是將表單中的數(shù)據(jù)放在HTTP協(xié)議的請(qǐng)求頭或消息體中宾袜,傳遞到action所指向URL捻艳;
③get傳輸?shù)臄?shù)據(jù)要受到URL長(zhǎng)度限制(1024字節(jié));而post可以傳輸大量的數(shù)據(jù)试和,上傳文件通常要使用post方式讯泣;
④使用get時(shí)參數(shù)會(huì)顯示在地址欄上,如果這些數(shù)據(jù)不是敏感數(shù)據(jù)阅悍,那么可以使用get好渠;對(duì)于敏感數(shù)據(jù)還是應(yīng)用使用post;
⑤get使用MIME類型application/x-www-form-urlencoded的URL編碼(也叫百分號(hào)編碼)文本的格式傳遞參數(shù)节视,保證被傳送的參數(shù)由遵循規(guī)范的文本組成拳锚,例如一個(gè)空格的編碼是"%20"。
- 實(shí)現(xiàn)會(huì)話跟蹤的技術(shù)有哪些寻行?
由于HTTP協(xié)議本身是無狀態(tài)的霍掺,服務(wù)器為了區(qū)分不同的用戶,就需要對(duì)用戶會(huì)話進(jìn)行跟蹤拌蜘,簡(jiǎn)單的說就是為用戶進(jìn)行登記杆烁,為用戶分配唯一的ID,下一次用戶在請(qǐng)求中包含此ID简卧,服務(wù)器據(jù)此判斷到底是哪一個(gè)用戶兔魂。
①URL 重寫:在URL中添加用戶會(huì)話的信息作為請(qǐng)求的參數(shù),或者將唯一的會(huì)話ID添加到URL結(jié)尾以標(biāo)識(shí)一個(gè)會(huì)話举娩。
②設(shè)置表單隱藏域:將和會(huì)話跟蹤相關(guān)的字段添加到隱式表單域中析校,這些信息不會(huì)在瀏覽器中顯示但是提交表單時(shí)會(huì)提交給服務(wù)器。
這兩種方式很難處理跨越多個(gè)頁(yè)面的信息傳遞铜涉,因?yàn)槿绻看味家薷腢RL或在頁(yè)面中添加隱式表單域來存儲(chǔ)用戶會(huì)話相關(guān)信息智玻,事情將變得非常麻煩。
③cookie:cookie有兩種芙代,一種是基于窗口的吊奢,瀏覽器窗口關(guān)閉后,cookie就沒有了纹烹;另一種是將信息存儲(chǔ)在一個(gè)臨時(shí)文件中事甜,并設(shè)置存在的時(shí)間。當(dāng)用戶通過瀏覽器和服務(wù)器建立一次會(huì)話后滔韵,會(huì)話ID就會(huì)隨響應(yīng)信息返回存儲(chǔ)在基于窗口的cookie中,那就意味著只要瀏覽器沒有關(guān)閉掌实,會(huì)話沒有超時(shí)陪蜻,下一次請(qǐng)求時(shí)這個(gè)會(huì)話ID又會(huì)提交給服務(wù)器讓服務(wù)器識(shí)別用戶身份。會(huì)話中可以為用戶保存信息贱鼻。會(huì)話對(duì)象是在服務(wù)器內(nèi)存中的宴卖,而基于窗口的cookie是在客戶端內(nèi)存中的滋将。如果瀏覽器禁用了cookie,那么就需要通過下面兩種方式進(jìn)行會(huì)話跟蹤症昏。當(dāng)然随闽,在使用cookie時(shí)要注意幾點(diǎn):首先不要在cookie中存放敏感信息;其次cookie存儲(chǔ)的數(shù)據(jù)量有限(4k)肝谭,不能將過多的內(nèi)容存儲(chǔ)cookie中掘宪;再者瀏覽器通常只允許一個(gè)站點(diǎn)最多存放20個(gè)cookie。當(dāng)然攘烛,和用戶會(huì)話相關(guān)的其他信息(除了會(huì)話ID)也可以存在cookie方便進(jìn)行會(huì)話跟蹤魏滚。
④HttpSession:在所有會(huì)話跟蹤技術(shù)中,HttpSession對(duì)象是最強(qiáng)大也是功能最多的坟漱。當(dāng)一個(gè)用戶第一次訪問某個(gè)網(wǎng)站時(shí)會(huì)自動(dòng)創(chuàng)建HttpSession鼠次,每個(gè)用戶可以訪問他自己的HttpSession∮蟪荩可以通過HttpServletRequest對(duì)象的getSession方法獲得HttpSession腥寇,通過HttpSession的setAttribute方法可以將一個(gè)值放在HttpSession中,通過調(diào)用HttpSession對(duì)象的getAttribute方法觅捆,同時(shí)傳入屬性名就可以獲取保存在HttpSession中的對(duì)象赦役。與上面三種方式不同的是,HttpSession放在服務(wù)器的內(nèi)存中惠拭,因此不要將過大的對(duì)象放在里面扩劝,即使目前的Servlet容器可以在內(nèi)存將滿時(shí)將HttpSession中的對(duì)象移到其他存儲(chǔ)設(shè)備中炕檩,但是這樣勢(shì)必影響性能年栓。添加到HttpSession中的值可以是任意Java對(duì)象走搁,這個(gè)對(duì)象最好實(shí)現(xiàn)了Serializable接口淌喻,這樣Servlet容器在必要的時(shí)候可以將其序列化到文件中腥光,否則在序列化時(shí)就會(huì)出現(xiàn)異常蛋叼。
補(bǔ)充:HTML5中可以使用Web Storage技術(shù)通過JavaScript來保存數(shù)據(jù)溯泣,例如可以使用localStorage和sessionStorage來保存用戶會(huì)話的信息擒悬,也能夠?qū)崿F(xiàn)會(huì)話跟蹤秀鞭。
- 過濾器有哪些作用和用法趋观?
Java Web開發(fā)中的過濾器(filter)是從Servlet 2.3規(guī)范開始增加的功能,并在Servlet 2.4規(guī)范中得到增強(qiáng)锋边。對(duì)Web應(yīng)用來說皱坛,過濾器是一個(gè)駐留在服務(wù)器端的Web組件,它可以截取客戶端和服務(wù)器之間的請(qǐng)求與響應(yīng)信息豆巨,并對(duì)這些信息進(jìn)行過濾剩辟。當(dāng)Web容器接受到一個(gè)對(duì)資源的請(qǐng)求時(shí),它將判斷是否有過濾器與這個(gè)資源相關(guān)聯(lián)。如果有贩猎,那么容器將把請(qǐng)求交給過濾器進(jìn)行處理熊户。在過濾器中,你可以改變請(qǐng)求的內(nèi)容吭服,或者重新設(shè)置請(qǐng)求的報(bào)頭信息嚷堡,然后再將請(qǐng)求發(fā)送給目標(biāo)資源。當(dāng)目標(biāo)資源對(duì)請(qǐng)求作出響應(yīng)時(shí)候艇棕,容器同樣會(huì)將響應(yīng)先轉(zhuǎn)發(fā)給過濾器蝌戒,在過濾器中你可以對(duì)響應(yīng)的內(nèi)容進(jìn)行轉(zhuǎn)換,然后再將響應(yīng)發(fā)送到客戶端欠肾。
常見的過濾器用途主要包括:對(duì)用戶請(qǐng)求進(jìn)行統(tǒng)一認(rèn)證瓶颠、對(duì)用戶的訪問請(qǐng)求進(jìn)行記錄和審核、對(duì)用戶發(fā)送的數(shù)據(jù)進(jìn)行過濾或替換刺桃、轉(zhuǎn)換圖象格式粹淋、對(duì)響應(yīng)內(nèi)容進(jìn)行壓縮以減少傳輸量、對(duì)請(qǐng)求或響應(yīng)進(jìn)行加解密處理瑟慈、觸發(fā)資源訪問事件桃移、對(duì)XML的輸出應(yīng)用XSLT等。
和過濾器相關(guān)的接口主要有:Filter葛碧、FilterConfig和FilterChain借杰。
編碼過濾器的例子:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
@WebFilter(urlPatterns = { "*" },
initParams = {@WebInitParam(name="encoding", value="utf-8")})
public class CodingFilter implements Filter {
private String defaultEncoding = "utf-8";
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding(defaultEncoding);
resp.setCharacterEncoding(defaultEncoding);
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
String encoding = config.getInitParameter("encoding");
if (encoding != null) {
defaultEncoding = encoding;
}
}
}
下載計(jì)數(shù)過濾器的例子:
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(urlPatterns = {"/*"})
public class DownloadCounterFilter implements Filter {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private Properties downloadLog;
private File logFile;
@Override
public void destroy() {
executorService.shutdown();
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
final String uri = request.getRequestURI();
executorService.execute(new Runnable() {
@Override
public void run() {
String value = downloadLog.getProperty(uri);
if(value == null) {
downloadLog.setProperty(uri, "1");
}
else {
int count = Integer.parseInt(value);
downloadLog.setProperty(uri, String.valueOf(++count));
}
try {
downloadLog.store(new FileWriter(logFile), "");
}
catch (IOException e) {
e.printStackTrace();
}
}
});
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
String appPath = config.getServletContext().getRealPath("/");
logFile = new File(appPath, "downloadLog.txt");
if(!logFile.exists()) {
try {
logFile.createNewFile();
}
catch(IOException e) {
e.printStackTrace();
}
}
downloadLog = new Properties();
try {
downloadLog.load(new FileReader(logFile));
} catch (IOException e) {
e.printStackTrace();
}
}
}
說明:這里使用了Servlet 3規(guī)范中的注解來部署過濾器,當(dāng)然也可以在web.xml中使用<filter>和<filter-mapping>標(biāo)簽部署過濾器进泼。
- 監(jiān)聽器有哪些作用和用法蔗衡?
Java Web開發(fā)中的監(jiān)聽器(listener)就是application、session乳绕、request三個(gè)對(duì)象創(chuàng)建绞惦、銷毀或者往其中添加修改刪除屬性時(shí)自動(dòng)執(zhí)行代碼的功能組件,如下所示:
①ServletContextListener:對(duì)Servlet上下文的創(chuàng)建和銷毀進(jìn)行監(jiān)聽洋措。
②ServletContextAttributeListener:監(jiān)聽Servlet上下文屬性的添加济蝉、刪除和替換。
③HttpSessionListener:對(duì)Session的創(chuàng)建和銷毀進(jìn)行監(jiān)聽菠发。
補(bǔ)充:session的銷毀有兩種情況:1). session超時(shí)(可以在web.xml中通過<session-config>/<session-timeout>標(biāo)簽配置超時(shí)時(shí)間)王滤;2). 通過調(diào)用session對(duì)象的invalidate()方法使session失效。
④HttpSessionAttributeListener:對(duì)Session對(duì)象中屬性的添加滓鸠、刪除和替換進(jìn)行監(jiān)聽雁乡。
⑤ServletRequestListener:對(duì)請(qǐng)求對(duì)象的初始化和銷毀進(jìn)行監(jiān)聽。
⑥ServletRequestAttributeListener:對(duì)請(qǐng)求對(duì)象屬性的添加糜俗、刪除和替換進(jìn)行監(jiān)聽蔗怠。
下面是一個(gè)統(tǒng)計(jì)網(wǎng)站最多在線人數(shù)監(jiān)聽器的例子墩弯。
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
上下文監(jiān)聽器,在服務(wù)器啟動(dòng)時(shí)初始化onLineCount和maxOnLineCount兩個(gè)變量
并將其置于服務(wù)器上下文(ServletContext)中寞射,其初始值都是0
*/
@WebListener
public class InitListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent evt) {
}
@Override
public void contextInitialized(ServletContextEvent evt) {
evt.getServletContext().setAttribute("onLineCount", 0);
evt.getServletContext().setAttribute("maxOnLineCount", 0);
}
}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
會(huì)話監(jiān)聽器,在用戶會(huì)話創(chuàng)建和銷毀的時(shí)候根據(jù)情況
修改onLineCount和maxOnLineCount的值
*/
@WebListener
public class MaxCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
ServletContext ctx = event.getSession().getServletContext();
int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
count++;
ctx.setAttribute("onLineCount", count);
int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
if (count > maxOnLineCount) {
ctx.setAttribute("maxOnLineCount", count);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ctx.setAttribute("date", df.format(new Date()));
}
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
ServletContext app = event.getSession().getServletContext();
int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
count--;
app.setAttribute("onLineCount", count);
}
}
- Servlet 3中的異步處理指的是什么锌钮?
在Servlet 3中引入了一項(xiàng)新的技術(shù)可以讓Servlet異步處理請(qǐng)求桥温。有人可能會(huì)質(zhì)疑,既然都有多線程了梁丘,還需要異步處理請(qǐng)求嗎侵浸?答案是肯定的,因?yàn)槿绻粋€(gè)任務(wù)處理時(shí)間相當(dāng)長(zhǎng)氛谜,那么Servlet或Filter會(huì)一直占用著請(qǐng)求處理線程直到任務(wù)結(jié)束掏觉,隨著并發(fā)用戶的增加,容器將會(huì)遭遇線程超出的風(fēng)險(xiǎn)值漫,這這種情況下很多的請(qǐng)求將會(huì)被堆積起來而后續(xù)的請(qǐng)求可能會(huì)遭遇拒絕服務(wù)澳腹,直到有資源可以處理請(qǐng)求為止。異步特性可以幫助應(yīng)用節(jié)省容器中的線程杨何,特別適合執(zhí)行時(shí)間長(zhǎng)而且用戶需要得到結(jié)果的任務(wù)酱塔,如果用戶不需要得到結(jié)果則直接將一個(gè)Runnable對(duì)象交給Executor并立即返回即可。
下面是一個(gè)支持異步處理請(qǐng)求的Servlet的例子危虱。
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = {"/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 開啟Tomcat異步Servlet支持
req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
final AsyncContext ctx = req.startAsync(); // 啟動(dòng)異步處理的上下文
// ctx.setTimeout(30000);
ctx.start(new Runnable() {
@Override
public void run() {
// 在此處添加異步處理的代碼
ctx.complete();
}
});
}
}
- 如何在基于Java的Web項(xiàng)目中實(shí)現(xiàn)文件上傳和下載羊娃?
在Sevlet 3 以前,Servlet API中沒有支持上傳功能的API埃跷,因此要實(shí)現(xiàn)上傳功能需要引入第三方工具從POST請(qǐng)求中獲得上傳的附件或者通過自行處理輸入流來獲得上傳的文件蕊玷,我們推薦使用Apache的commons-fileupload。
從Servlet 3開始弥雹,文件上傳變得無比簡(jiǎn)單垃帅,相信看看下面的例子一切都清楚了。
上傳頁(yè)面index.jsp:
<%@ page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Photo Upload</title>
</head>
<body>
<h1>Select your photo and upload</h1>
<hr/>
<div style="color:red;font-size:14px;">${hint}</div>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
Photo file: <input type="file" name="photo" />
<input type="submit" value="Upload" />
</form>
</body>
</html>
支持上傳的Servlet:
package com.jackfrued.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 可以用request.getPart()方法獲得名為photo的上傳附件
// 也可以用request.getParts()獲得所有上傳附件(多文件上傳)
// 然后通過循環(huán)分別處理每一個(gè)上傳的文件
Part part = request.getPart("photo");
if (part != null && part.getSubmittedFileName().length() > 0) {
// 用ServletContext對(duì)象的getRealPath()方法獲得上傳文件夾的絕對(duì)路徑
String savePath = request.getServletContext().getRealPath("/upload");
// Servlet 3.1規(guī)范中可以用Part對(duì)象的getSubmittedFileName()方法獲得上傳的文件名
// 更好的做法是為上傳的文件進(jìn)行重命名(避免同名文件的相互覆蓋)
part.write(savePath + "/" + part.getSubmittedFileName());
request.setAttribute("hint", "Upload Successfully!");
} else {
request.setAttribute("hint", "Upload failed!");
}
// 跳轉(zhuǎn)回到上傳頁(yè)面
request.getRequestDispatcher("index.jsp").forward(request, response);
}
}
- 如何設(shè)置請(qǐng)求的編碼以及響應(yīng)內(nèi)容的類型缅糟?
通過請(qǐng)求對(duì)象(ServletRequest)的setCharacterEncoding(String)方法可以設(shè)置請(qǐng)求的編碼挺智,其實(shí)要徹底解決亂碼問題就應(yīng)該讓頁(yè)面、服務(wù)器窗宦、請(qǐng)求和響應(yīng)赦颇、Java程序都使用統(tǒng)一的編碼,最好的選擇當(dāng)然是UTF-8赴涵;
通過響應(yīng)對(duì)象(ServletResponse)的setContentType(String)方法可以設(shè)置響應(yīng)內(nèi)容的類型媒怯,當(dāng)然也可以通過HttpServletResponsed對(duì)象的setHeader(String, String)方法來設(shè)置。
- 什么是Web Service(Web服務(wù))髓窜?
從表面上看扇苞,Web Service就是一個(gè)應(yīng)用程序欺殿,它向外界暴露出一個(gè)能夠通過Web進(jìn)行調(diào)用的API。這就是說鳖敷,你能夠用編程的方法透明的調(diào)用這個(gè)應(yīng)用程序脖苏,不需要了解它的任何細(xì)節(jié),跟你使用的編程語(yǔ)言也沒有關(guān)系定踱。例如可以創(chuàng)建一個(gè)提供天氣預(yù)報(bào)的Web Service棍潘,那么無論你用哪種編程語(yǔ)言開發(fā)的應(yīng)用都可以通過調(diào)用它的API并傳入城市信息來獲得該城市的天氣預(yù)報(bào)。
之所以稱之為Web Service崖媚,是因?yàn)樗贖TTP協(xié)議傳輸數(shù)據(jù)亦歉,這使得運(yùn)行在不同機(jī)器上的不同應(yīng)用無須借助附加的、專門的第三方軟件或硬件畅哑,就可相互交換數(shù)據(jù)或集成肴楷。
補(bǔ)充:這里必須要提及的一個(gè)概念是SOA(Service-Oriented Architecture,面向服務(wù)的架構(gòu))荠呐,SOA是一種思想赛蔫,它將應(yīng)用程序的不同功能單元通過中立的契約聯(lián)系起來,獨(dú)立于硬件平臺(tái)直秆、操作系統(tǒng)和編程語(yǔ)言濒募,使得各種形式的功能單元能夠更好的集成。顯然圾结,Web Service是SOA的一種較好的解決方案瑰剃,它更多的是一種標(biāo)準(zhǔn),而不是一種具體的技術(shù)筝野。
- MyBatis中使用#和$書寫占位符有什么區(qū)別晌姚?
#
將傳入的數(shù)據(jù)都當(dāng)成一個(gè)字符串,會(huì)對(duì)傳入的數(shù)據(jù)自動(dòng)加上引號(hào)歇竟;$
將傳入的數(shù)據(jù)直接顯示生成在SQL中挥唠。注意:使用$
占位符可能會(huì)導(dǎo)致SQL注射攻擊,能用#
的地方就不要使用$
焕议,寫order by子句的時(shí)候應(yīng)該用$
而不是#
宝磨。
- MyBatis中的動(dòng)態(tài)SQL是什么意思?
對(duì)于一些復(fù)雜的查詢盅安,我們可能會(huì)指定多個(gè)查詢條件唤锉,但是這些條件可能存在也可能不存在,例如在58同城上面找房子别瞭,我們可能會(huì)指定面積窿祥、樓層和所在位置來查找房源,也可能會(huì)指定面積蝙寨、價(jià)格晒衩、戶型和所在位置來查找房源嗤瞎,此時(shí)就需要根據(jù)用戶指定的條件動(dòng)態(tài)生成SQL語(yǔ)句。如果不使用持久層框架我們可能需要自己拼裝SQL語(yǔ)句听系,還好MyBatis提供了動(dòng)態(tài)SQL的功能來解決這個(gè)問題贝奇。MyBatis中用于實(shí)現(xiàn)動(dòng)態(tài)SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach
下面是映射文件的片段。
<select id="foo" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select>
- 什么是IoC和DI跛锌?DI是如何實(shí)現(xiàn)的弃秆?
IoC叫控制反轉(zhuǎn),是Inversion of Control的縮寫髓帽,DI(Dependency Injection)叫依賴注入,是對(duì)IoC更簡(jiǎn)單的詮釋脑豹≈2兀控制反轉(zhuǎn)是把傳統(tǒng)上由程序代碼直接操控的對(duì)象的調(diào)用權(quán)交給容器,通過容器來實(shí)現(xiàn)對(duì)象組件的裝配和管理瘩欺。所謂的"控制反轉(zhuǎn)"就是對(duì)組件對(duì)象控制權(quán)的轉(zhuǎn)移必盖,從程序代碼本身轉(zhuǎn)移到了外部容器枣购,由容器來創(chuàng)建對(duì)象并管理對(duì)象之間的依賴關(guān)系德召。IoC體現(xiàn)了好萊塢原則 - "Don’t call me, we will call you"。依賴注入的基本原則是應(yīng)用組件不應(yīng)該負(fù)責(zé)查找資源或者其他依賴的協(xié)作對(duì)象始绍。配置對(duì)象的工作應(yīng)該由容器負(fù)責(zé)译蒂,查找資源的邏輯應(yīng)該從應(yīng)用組件的代碼中抽取出來曼月,交給容器來完成。DI是對(duì)IoC更準(zhǔn)確的描述柔昼,即組件之間的依賴關(guān)系由容器在運(yùn)行期決定哑芹,形象的來說,即由容器動(dòng)態(tài)的將某種依賴關(guān)系注入到組件之中捕透。
舉個(gè)例子:一個(gè)類A需要用到接口B中的方法聪姿,那么就需要為類A和接口B建立關(guān)聯(lián)或依賴關(guān)系,最原始的方法是在類A中創(chuàng)建一個(gè)接口B的實(shí)現(xiàn)類C的實(shí)例乙嘀,但這種方法需要開發(fā)人員自行維護(hù)二者的依賴關(guān)系末购,也就是說當(dāng)依賴關(guān)系發(fā)生變動(dòng)的時(shí)候需要修改代碼并重新構(gòu)建整個(gè)系統(tǒng)。如果通過一個(gè)容器來管理這些對(duì)象以及對(duì)象的依賴關(guān)系虎谢,則只需要在類A中定義好用于關(guān)聯(lián)接口B的方法(構(gòu)造器或setter方法)盟榴,將類A和接口B的實(shí)現(xiàn)類C放入容器中,通過對(duì)容器的配置來實(shí)現(xiàn)二者的關(guān)聯(lián)婴噩。
依賴注入可以通過setter方法注入(設(shè)值注入)擎场、構(gòu)造器注入和接口注入三種方式來實(shí)現(xiàn)羽德,Spring支持setter注入和構(gòu)造器注入,通常使用構(gòu)造器注入來注入必須的依賴關(guān)系迅办,對(duì)于可選的依賴關(guān)系宅静,則setter注入是更好的選擇,setter注入需要類提供無參構(gòu)造器或者無參的靜態(tài)工廠方法來創(chuàng)建對(duì)象站欺。
- Spring中Bean的作用域有哪些姨夹?
在Spring的早期版本中,僅有兩個(gè)作用域:singleton和prototype矾策,前者表示Bean以單例的方式存在磷账;后者表示每次從容器中調(diào)用Bean時(shí),都會(huì)返回一個(gè)新的實(shí)例贾虽,prototype通常翻譯為原型够颠。
補(bǔ)充:設(shè)計(jì)模式中的創(chuàng)建型模式中也有一個(gè)原型模式,原型模式也是一個(gè)常用的模式榄鉴,例如做一個(gè)室內(nèi)設(shè)計(jì)軟件,所有的素材都在工具箱中蛉抓,而每次從工具箱中取出的都是素材對(duì)象的一個(gè)原型庆尘,可以通過對(duì)象克隆來實(shí)現(xiàn)原型模式。
Spring 2.x中針對(duì)WebApplicationContext新增了3個(gè)作用域巷送,分別是:request(每次HTTP請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的Bean)驶忌、session(同一個(gè)HttpSession共享同一個(gè)Bean,不同的HttpSession使用不同的Bean)和globalSession(同一個(gè)全局Session共享一個(gè)Bean)笑跛。
說明:單例模式和原型模式都是重要的設(shè)計(jì)模式付魔。一般情況下,無狀態(tài)或狀態(tài)不可變的類適合使用單例模式飞蹂。在傳統(tǒng)開發(fā)中几苍,由于DAO持有Connection這個(gè)非線程安全對(duì)象因而沒有使用單例模式;但在Spring環(huán)境下陈哑,所有DAO類對(duì)可以采用單例模式妻坝,因?yàn)镾pring利用AOP和Java API中的ThreadLocal對(duì)非線程安全的對(duì)象進(jìn)行了特殊處理。
ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路惊窖。ThreadLocal刽宪,顧名思義是線程的一個(gè)本地化對(duì)象,當(dāng)工作于多線程中的對(duì)象使用ThreadLocal維護(hù)變量時(shí)界酒,ThreadLocal為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本圣拄,所以每一個(gè)線程都可以獨(dú)立的改變自己的副本,而不影響其他線程所對(duì)應(yīng)的副本毁欣。從線程的角度看庇谆,這個(gè)變量就像是線程的本地變量岳掐。
ThreadLocal類非常簡(jiǎn)單好用,只有四個(gè)方法族铆,能用上的也就是下面三個(gè)方法:
- void set(T value):設(shè)置當(dāng)前線程的線程局部變量的值岩四。
- T get():獲得當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值。
- void remove():刪除當(dāng)前線程中線程局部變量的值哥攘。
ThreadLocal是如何做到為每一個(gè)線程維護(hù)一份獨(dú)立的變量副本的呢剖煌?在ThreadLocal類中有一個(gè)Map,鍵為線程對(duì)象逝淹,值是其線程對(duì)應(yīng)的變量的副本耕姊,自己要模擬實(shí)現(xiàn)一個(gè)ThreadLocal類其實(shí)并不困難,代碼如下所示:
補(bǔ)充:ThreadLocal的本質(zhì)不是維護(hù)一個(gè)Map栅葡,并且key也不是當(dāng)前線程對(duì)象茉兰。
正確應(yīng)該是:
1.每個(gè)Thread類對(duì)象都有一個(gè)字段:ThreadLocalMap 對(duì)象
2.ThreadLocal的set()方法,是把當(dāng)前線程的數(shù)據(jù)副本保存在當(dāng)前線程的ThreadLocalMap字段里面欣簇,ThreadLocalMap這個(gè)字段類似HashMap规脸,存進(jìn)去的時(shí)候,key是ThreadLocal對(duì)象熊咽,
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());
public void set(T newValue) {
map.put(Thread.currentThread(), newValue);
}
public T get() {
return map.get(Thread.currentThread());
}
public void remove() {
map.remove(Thread.currentThread());
}
}
- 解釋一下什么叫AOP(面向切面編程)莫鸭?
AOP(Aspect-Oriented Programming)指一種程序設(shè)計(jì)范型,該范型以一種稱為切面(aspect)的語(yǔ)言構(gòu)造為基礎(chǔ)横殴,切面是一種新的模塊化機(jī)制被因,用來描述分散在對(duì)象、類或方法中的橫切關(guān)注點(diǎn)(crosscutting concern)衫仑。
- 你如何理解AOP中的連接點(diǎn)(Joinpoint)梨与、切點(diǎn)(Pointcut)惋耙、增強(qiáng)(Advice)统台、引介(Introduction)、織入(Weaving)裕膀、切面(Aspect)這些概念如贷?
a. 連接點(diǎn)(Joinpoint):程序執(zhí)行的某個(gè)特定位置(如:某個(gè)方法調(diào)用前陷虎、調(diào)用后,方法拋出異常后)杠袱。一個(gè)類或一段程序代碼擁有一些具有邊界性質(zhì)的特定點(diǎn)尚猿,這些代碼中的特定點(diǎn)就是連接點(diǎn)。Spring僅支持方法的連接點(diǎn)楣富。
b. 切點(diǎn)(Pointcut):如果連接點(diǎn)相當(dāng)于數(shù)據(jù)中的記錄凿掂,那么切點(diǎn)相當(dāng)于查詢條件,一個(gè)切點(diǎn)可以匹配多個(gè)連接點(diǎn)。Spring AOP的規(guī)則解析引擎負(fù)責(zé)解析切點(diǎn)所設(shè)定的查詢條件庄萎,找到對(duì)應(yīng)的連接點(diǎn)踪少。
c. 增強(qiáng)(Advice):增強(qiáng)是織入到目標(biāo)類連接點(diǎn)上的一段程序代碼。Spring提供的增強(qiáng)接口都是帶方位名的糠涛,如:BeforeAdvice援奢、AfterReturningAdvice、ThrowsAdvice等忍捡。很多資料上將增強(qiáng)譯為“通知”集漾,這明顯是個(gè)詞不達(dá)意的翻譯,讓很多程序員困惑了許久砸脊。
說明: Advice在國(guó)內(nèi)的很多書面資料中都被翻譯成"通知"具篇,但是很顯然這個(gè)翻譯無法表達(dá)其本質(zhì),有少量的讀物上將這個(gè)詞翻譯為"增強(qiáng)"凌埂,這個(gè)翻譯是對(duì)Advice較為準(zhǔn)確的詮釋驱显,我們通過AOP將橫切關(guān)注功能加到原有的業(yè)務(wù)邏輯上,這就是對(duì)原有業(yè)務(wù)邏輯的一種增強(qiáng)瞳抓,這種增強(qiáng)可以是前置增強(qiáng)埃疫、后置增強(qiáng)、返回后增強(qiáng)孩哑、拋異常時(shí)增強(qiáng)和包圍型增強(qiáng)熔恢。
d. 引介(Introduction):引介是一種特殊的增強(qiáng),它為類添加一些屬性和方法臭笆。這樣,即使一個(gè)業(yè)務(wù)類原本沒有實(shí)現(xiàn)某個(gè)接口秤掌,通過引介功能愁铺,可以動(dòng)態(tài)的未該業(yè)務(wù)類添加接口的實(shí)現(xiàn)邏輯,讓業(yè)務(wù)類成為這個(gè)接口的實(shí)現(xiàn)類闻鉴。
e. 織入(Weaving):織入是將增強(qiáng)添加到目標(biāo)類具體連接點(diǎn)上的過程茵乱,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器孟岛,在裝載類的時(shí)候?qū)︻愡M(jìn)行增強(qiáng)瓶竭;③運(yùn)行時(shí)織入:在運(yùn)行時(shí)為目標(biāo)類生成代理實(shí)現(xiàn)增強(qiáng)。Spring采用了動(dòng)態(tài)代理的方式實(shí)現(xiàn)了運(yùn)行時(shí)織入渠羞,而AspectJ采用了編譯期織入和裝載期織入的方式斤贰。
f. 切面(Aspect):切面是由切點(diǎn)和增強(qiáng)(引介)組成的,它包括了對(duì)橫切關(guān)注功能的定義次询,也包括了對(duì)連接點(diǎn)的定義荧恍。
補(bǔ)充:代理模式是GoF提出的23種設(shè)計(jì)模式中最為經(jīng)典的模式之一,代理模式是對(duì)象的結(jié)構(gòu)模式,它給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象送巡,并由代理對(duì)象控制對(duì)原對(duì)象的引用摹菠。簡(jiǎn)單的說,代理對(duì)象可以完成比原對(duì)象更多的職責(zé)骗爆,當(dāng)需要為原對(duì)象添加橫切關(guān)注功能時(shí)次氨,就可以使用原對(duì)象的代理對(duì)象。我們?cè)诖蜷_Office系列的Word文檔時(shí)摘投,如果文檔中有插圖煮寡,當(dāng)文檔剛加載時(shí),文檔中的插圖都只是一個(gè)虛框占位符谷朝,等用戶真正翻到某頁(yè)要查看該圖片時(shí)洲押,才會(huì)真正加載這張圖,這其實(shí)就是對(duì)代理模式的使用圆凰,代替真正圖片的虛框就是一個(gè)虛擬代理杈帐;Hibernate的load方法也是返回一個(gè)虛擬代理對(duì)象,等用戶真正需要訪問對(duì)象的屬性時(shí)专钉,才向數(shù)據(jù)庫(kù)發(fā)出SQL語(yǔ)句獲得真實(shí)對(duì)象挑童。
下面用一個(gè)找槍手代考的例子演示代理模式的使用:
/**
* 參考人員接口
* @author 駱昊
*
*/
public interface Candidate {
/**
* 答題
*/
public void answerTheQuestions();
}
/**
* 懶學(xué)生
* @author 駱昊
*
*/
public class LazyStudent implements Candidate {
private String name; // 姓名
public LazyStudent(String name) {
this.name = name;
}
@Override
public void answerTheQuestions() {
// 懶學(xué)生只能寫出自己的名字不會(huì)答題
System.out.println("姓名: " + name);
}
}
/**
* 槍手
* @author 駱昊
*
*/
public class Gunman implements Candidate {
private Candidate target; // 被代理對(duì)象
public Gunman(Candidate target) {
this.target = target;
}
@Override
public void answerTheQuestions() {
// 槍手要寫上代考的學(xué)生的姓名
target.answerTheQuestions();
// 槍手要幫助懶學(xué)生答題并交卷
System.out.println("奮筆疾書正確答案");
System.out.println("交卷");
}
}
public class ProxyTest1 {
public static void main(String[] args) {
Candidate c = new Gunman(new LazyStudent("王小二"));
c.answerTheQuestions();
}
}
說明:從JDK 1.3開始,Java提供了動(dòng)態(tài)代理技術(shù)跃须,允許開發(fā)者在運(yùn)行時(shí)創(chuàng)建接口的代理實(shí)例站叼,主要包括Proxy類和InvocationHandler接口。下面的例子使用動(dòng)態(tài)代理為ArrayList編寫一個(gè)代理菇民,在添加和刪除元素時(shí)尽楔,在控制臺(tái)打印添加或刪除的元素以及ArrayList的大小:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
public class ListProxy<T> implements InvocationHandler {
private List<T> target;
public ListProxy(List<T> target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = null;
System.out.println("[" + method.getName() + ": " + args[0] + "]");
retVal = method.invoke(target, args);
System.out.println("[size=" + target.size() + "]");
return retVal;
}
}
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public class ProxyTest2 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
Class<?> clazz = list.getClass();
ListProxy<String> myProxy = new ListProxy<String>(list);
List<String> newList = (List<String>)
Proxy.newProxyInstance(clazz.getClassLoader(),
clazz.getInterfaces(), myProxy);
newList.add("apple");
newList.add("banana");
newList.add("orange");
newList.remove("banana");
}
}
說明:使用Java的動(dòng)態(tài)代理有一個(gè)局限性就是代理的類必須要實(shí)現(xiàn)接口第练,雖然面向接口編程是每個(gè)優(yōu)秀的Java程序都知道的規(guī)則阔馋,但現(xiàn)實(shí)往往不盡如人意,對(duì)于沒有實(shí)現(xiàn)接口的類如何為其生成代理呢娇掏?繼承呕寝!繼承是最經(jīng)典的擴(kuò)展已有代碼能力的手段,雖然繼承常常被初學(xué)者濫用婴梧,但繼承也常常被進(jìn)階的程序員忽視下梢。CGLib采用非常底層的字節(jié)碼生成技術(shù),通過為一個(gè)類創(chuàng)建子類來生成代理塞蹭,它彌補(bǔ)了Java動(dòng)態(tài)代理的不足孽江,因此Spring中動(dòng)態(tài)代理和CGLib都是創(chuàng)建代理的重要手段,對(duì)于實(shí)現(xiàn)了接口的類就用動(dòng)態(tài)代理為其生成代理類番电,而沒有實(shí)現(xiàn)接口的類就用CGLib通過繼承的方式為其創(chuàng)建代理竟坛。
- Spring MVC的工作原理是怎樣的?
Spring MVC的工作原理如下圖所示:
① 客戶端的所有請(qǐng)求都交給前端控制器DispatcherServlet來處理,它會(huì)負(fù)責(zé)調(diào)用系統(tǒng)的其他模塊來真正處理用戶的請(qǐng)求担汤。
② DispatcherServlet收到請(qǐng)求后涎跨,將根據(jù)請(qǐng)求的信息(包括URL、HTTP協(xié)議方法崭歧、請(qǐng)求頭隅很、請(qǐng)求參數(shù)、Cookie等)以及HandlerMapping的配置找到處理該請(qǐng)求的Handler(任何一個(gè)對(duì)象都可以作為請(qǐng)求的Handler)率碾。
③在這個(gè)地方Spring會(huì)通過HandlerAdapter對(duì)該處理器進(jìn)行封裝叔营。
④ HandlerAdapter是一個(gè)適配器,它用統(tǒng)一的接口對(duì)各種Handler中的方法進(jìn)行調(diào)用所宰。
⑤ Handler完成對(duì)用戶請(qǐng)求的處理后绒尊,會(huì)返回一個(gè)ModelAndView對(duì)象給DispatcherServlet,ModelAndView顧名思義仔粥,包含了數(shù)據(jù)模型以及相應(yīng)的視圖的信息婴谱。
⑥ ModelAndView的視圖是邏輯視圖,DispatcherServlet還要借助ViewResolver完成從邏輯視圖到真實(shí)視圖對(duì)象的解析工作躯泰。
⑦ 當(dāng)?shù)玫秸嬲囊晥D對(duì)象后谭羔,DispatcherServlet會(huì)利用視圖對(duì)象對(duì)模型數(shù)據(jù)進(jìn)行渲染。
⑧ 客戶端得到響應(yīng)麦向,可能是一個(gè)普通的HTML頁(yè)面瘟裸,也可以是XML或JSON字符串,還可以是一張圖片或者一個(gè)PDF文件诵竭。
- 選擇使用Spring框架的原因(Spring框架為企業(yè)級(jí)開發(fā)帶來的好處有哪些)话告?
可以從以下幾個(gè)方面作答:
- 非侵入式:支持基于POJO的編程模式,不強(qiáng)制性的要求實(shí)現(xiàn)Spring框架中的接口或繼承Spring框架中的類卵慰。
- IoC容器:IoC容器幫助應(yīng)用程序管理對(duì)象以及對(duì)象之間的依賴關(guān)系超棺,對(duì)象之間的依賴關(guān)系如果發(fā)生了改變只需要修改配置文件而不是修改代碼,因?yàn)榇a的修改可能意味著項(xiàng)目的重新構(gòu)建和完整的回歸測(cè)試呵燕。有了IoC容器,程序員再也不需要自己編寫工廠件相、單例再扭,這一點(diǎn)特別符合Spring的精神"不要重復(fù)的發(fā)明輪子"。
- AOP(面向切面編程):將所有的橫切關(guān)注功能封裝到切面(aspect)中夜矗,通過配置的方式將橫切關(guān)注功能動(dòng)態(tài)添加到目標(biāo)代碼上泛范,進(jìn)一步實(shí)現(xiàn)了業(yè)務(wù)邏輯和系統(tǒng)服務(wù)之間的分離。另一方面紊撕,有了AOP程序員可以省去很多自己寫代理類的工作罢荡。
- MVC:Spring的MVC框架是非常優(yōu)秀的,從各個(gè)方面都可以甩Struts 2幾條街,為Web表示層提供了更好的解決方案区赵。
- 事務(wù)管理:Spring以寬廣的胸懷接納多種持久層技術(shù)惭缰,并且為其提供了聲明式的事務(wù)管理,在不需要任何一行代碼的情況下就能夠完成事務(wù)管理笼才。
- 其他:選擇Spring框架的原因還遠(yuǎn)不止于此漱受,Spring為Java企業(yè)級(jí)開發(fā)提供了一站式選擇,你可以在需要的時(shí)候使用它的部分和全部骡送,更重要的是昂羡,你甚至可以在感覺不到Spring存在的情況下,在你的項(xiàng)目中使用Spring提供的各種優(yōu)秀的功能摔踱。
- 闡述Spring框架中Bean的生命周期虐先?
① Spring IoC容器找到關(guān)于Bean的定義并實(shí)例化該Bean。
② Spring IoC容器對(duì)Bean進(jìn)行依賴注入派敷。
③ 如果Bean實(shí)現(xiàn)了BeanNameAware接口蛹批,則將該Bean的id傳給setBeanName方法。
④ 如果Bean實(shí)現(xiàn)了BeanFactoryAware接口膀息,則將BeanFactory對(duì)象傳給setBeanFactory方法般眉。
⑤ 如果Bean實(shí)現(xiàn)了BeanPostProcessor接口,則調(diào)用其postProcessBeforeInitialization方法潜支。
⑥ 如果Bean實(shí)現(xiàn)了InitializingBean接口甸赃,則調(diào)用其afterPropertySet方法。
⑦ 如果有和Bean關(guān)聯(lián)的BeanPostProcessors對(duì)象冗酿,則這些對(duì)象的postProcessAfterInitialization方法被調(diào)用埠对。
⑧ 當(dāng)銷毀Bean實(shí)例時(shí),如果Bean實(shí)現(xiàn)了DisposableBean接口裁替,則調(diào)用其destroy方法项玛。
- 在Web項(xiàng)目中如何獲得Spring的IoC容器?
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
55.大型網(wǎng)站在架構(gòu)上應(yīng)當(dāng)考慮哪些問題弱判?
- 分層:分層是處理任何復(fù)雜系統(tǒng)最常見的手段之一襟沮,將系統(tǒng)橫向切分成若干個(gè)層面,每個(gè)層面只承擔(dān)單一的職責(zé)昌腰,然后通過下層為上層提供的基礎(chǔ)設(shè)施和服務(wù)以及上層對(duì)下層的調(diào)用來形成一個(gè)完整的復(fù)雜的系統(tǒng)开伏。計(jì)算機(jī)網(wǎng)絡(luò)的開放系統(tǒng)互聯(lián)參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結(jié)構(gòu),大型網(wǎng)站的軟件系統(tǒng)也可以使用分層的理念將其分為持久層(提供數(shù)據(jù)存儲(chǔ)和訪問服務(wù))遭商、業(yè)務(wù)層(處理業(yè)務(wù)邏輯固灵,系統(tǒng)中最核心的部分)和表示層(系統(tǒng)交互、視圖展示)劫流。需要指出的是:(1)分層是邏輯上的劃分巫玻,在物理上可以位于同一設(shè)備上也可以在不同的設(shè)備上部署不同的功能模塊丛忆,這樣可以使用更多的計(jì)算資源來應(yīng)對(duì)用戶的并發(fā)訪問;(2)層與層之間應(yīng)當(dāng)有清晰的邊界仍秤,這樣分層才有意義熄诡,才更利于軟件的開發(fā)和維護(hù)。
- 分割:分割是對(duì)軟件的縱向切分徒扶。我們可以將大型網(wǎng)站的不同功能和服務(wù)分割開粮彤,形成高內(nèi)聚低耦合的功能模塊(單元)。在設(shè)計(jì)初期可以做一個(gè)粗粒度的分割姜骡,將網(wǎng)站分割為若干個(gè)功能模塊导坟,后期還可以進(jìn)一步對(duì)每個(gè)模塊進(jìn)行細(xì)粒度的分割,這樣一方面有助于軟件的開發(fā)和維護(hù)圈澈,另一方面有助于分布式的部署惫周,提供網(wǎng)站的并發(fā)處理能力和功能的擴(kuò)展。
- 分布式:除了上面提到的內(nèi)容康栈,網(wǎng)站的靜態(tài)資源(JavaScript递递、CSS、圖片等)也可以采用獨(dú)立分布式部署并采用獨(dú)立的域名啥么,這樣可以減輕應(yīng)用服務(wù)器的負(fù)載壓力登舞,也使得瀏覽器對(duì)資源的加載更快。數(shù)據(jù)的存取也應(yīng)該是分布式的悬荣,傳統(tǒng)的商業(yè)級(jí)關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品基本上都支持分布式部署菠秒,而新生的NoSQL產(chǎn)品幾乎都是分布式的。當(dāng)然氯迂,網(wǎng)站后臺(tái)的業(yè)務(wù)處理也要使用分布式技術(shù)践叠,例如查詢索引的構(gòu)建、數(shù)據(jù)分析等嚼蚀,這些業(yè)務(wù)計(jì)算規(guī)模龐大禁灼,可以使用Hadoop以及MapReduce分布式計(jì)算框架來處理。
- 集群:集群使得有更多的服務(wù)器提供相同的服務(wù)轿曙,可以更好的提供對(duì)并發(fā)的支持弄捕。
- 緩存:所謂緩存就是用空間換取時(shí)間的技術(shù),將數(shù)據(jù)盡可能放在距離計(jì)算最近的位置导帝。使用緩存是網(wǎng)站優(yōu)化的第一定律守谓。我們通常說的CDN、反向代理舟扎、熱點(diǎn)數(shù)據(jù)都是對(duì)緩存技術(shù)的使用。
- 異步:異步是實(shí)現(xiàn)軟件實(shí)體之間解耦合的又一重要手段悴务。異步架構(gòu)是典型的生產(chǎn)者消費(fèi)者模式睹限,二者之間沒有直接的調(diào)用關(guān)系譬猫,只要保持?jǐn)?shù)據(jù)結(jié)構(gòu)不變,彼此功能實(shí)現(xiàn)可以隨意變化而不互相影響羡疗,這對(duì)網(wǎng)站的擴(kuò)展非常有利染服。使用異步處理還可以提高系統(tǒng)可用性,加快網(wǎng)站的響應(yīng)速度(用Ajax加載數(shù)據(jù)就是一種異步技術(shù))叨恨,同時(shí)還可以起到削峰作用(應(yīng)對(duì)瞬時(shí)高并發(fā))柳刮。";能推遲處理的都要推遲處理"是網(wǎng)站優(yōu)化的第二定律痒钝,而異步是踐行網(wǎng)站優(yōu)化第二定律的重要手段秉颗。
- 冗余:各種服務(wù)器都要提供相應(yīng)的冗余服務(wù)器以便在某臺(tái)或某些服務(wù)器宕機(jī)時(shí)還能保證網(wǎng)站可以正常工作,同時(shí)也提供了災(zāi)難恢復(fù)的可能性送矩。冗余是網(wǎng)站高可用性的重要保證蚕甥。
- 你使用過的應(yīng)用服務(wù)器優(yōu)化技術(shù)有哪些?
① 分布式緩存:緩存的本質(zhì)就是內(nèi)存中的哈希表栋荸,如果設(shè)計(jì)一個(gè)優(yōu)質(zhì)的哈希函數(shù)菇怀,那么理論上哈希表讀寫的漸近時(shí)間復(fù)雜度為O(1)。緩存主要用來存放那些讀寫比很高晌块、變化很少的數(shù)據(jù)爱沟,這樣應(yīng)用程序讀取數(shù)據(jù)時(shí)先到緩存中讀取,如果沒有或者數(shù)據(jù)已經(jīng)失效再去訪問數(shù)據(jù)庫(kù)或文件系統(tǒng)匆背,并根據(jù)擬定的規(guī)則將數(shù)據(jù)寫入緩存呼伸。對(duì)網(wǎng)站數(shù)據(jù)的訪問也符合二八定律(Pareto分布,冪律分布)靠汁,即80%的訪問都集中在20%的數(shù)據(jù)上,如果能夠?qū)⑦@20%的數(shù)據(jù)緩存起來蝶怔,那么系統(tǒng)的性能將得到顯著的改善。當(dāng)然踢星,使用緩存需要解決以下幾個(gè)問題:
- 頻繁修改的數(shù)據(jù)澳叉;
- 數(shù)據(jù)不一致與臟讀;
- 緩存雪崩(可以采用分布式緩存服務(wù)器集群加以解決沐悦,memcached是廣泛采用的解決方案)成洗;
- 緩存預(yù)熱;
- 緩存穿透(惡意持續(xù)請(qǐng)求不存在的數(shù)據(jù))藏否。
② 異步操作:可以使用消息隊(duì)列將調(diào)用異步化瓶殃,通過異步處理將短時(shí)間高并發(fā)產(chǎn)生的事件消息存儲(chǔ)在消息隊(duì)列中,從而起到削峰作用副签。電商網(wǎng)站在進(jìn)行促銷活動(dòng)時(shí)遥椿,可以將用戶的訂單請(qǐng)求存入消息隊(duì)列基矮,這樣可以抵御大量的并發(fā)訂單請(qǐng)求對(duì)系統(tǒng)和數(shù)據(jù)庫(kù)的沖擊。目前冠场,絕大多數(shù)的電商網(wǎng)站即便不進(jìn)行促銷活動(dòng)家浇,訂單系統(tǒng)都采用了消息隊(duì)列來處理。
③ 使用集群碴裙。
④ 代碼優(yōu)化:
- 多線程:基于Java的Web開發(fā)基本上都通過多線程的方式響應(yīng)用戶的并發(fā)請(qǐng)求钢悲,使用多線程技術(shù)在編程上要解決線程安全問題,主要可以考慮以下幾個(gè)方面:A. 將對(duì)象設(shè)計(jì)為無狀態(tài)對(duì)象(這和面向?qū)ο蟮木幊逃^點(diǎn)是矛盾的舔株,在面向?qū)ο蟮氖澜缰斜灰暈椴涣荚O(shè)計(jì))莺琳,這樣就不會(huì)存在并發(fā)訪問時(shí)對(duì)象狀態(tài)不一致的問題。B. 在方法內(nèi)部創(chuàng)建對(duì)象督笆,這樣對(duì)象由進(jìn)入方法的線程創(chuàng)建芦昔,不會(huì)出現(xiàn)多個(gè)線程訪問同一對(duì)象的問題。使用ThreadLocal將對(duì)象與線程綁定也是很好的做法娃肿,這一點(diǎn)在前面已經(jīng)探討過了蕾域。C. 對(duì)資源進(jìn)行并發(fā)訪問時(shí)應(yīng)當(dāng)使用合理的鎖機(jī)制滞欠。
- 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認(rèn)的比多線程的方式更能充分發(fā)揮服務(wù)器性能的應(yīng)用模式,基于Node.js構(gòu)建的服務(wù)器就采用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規(guī)范中又引入了異步Servlet的概念升筏,這些都為在服務(wù)器端采用非阻塞I/O提供了必要的基礎(chǔ)旁振。
- 資源復(fù)用:資源復(fù)用主要有兩種方式论悴,一是單例方椎,二是對(duì)象池,我們使用的數(shù)據(jù)庫(kù)連接池拯钻、線程池都是對(duì)象池化技術(shù)帖努,這是典型的用空間換取時(shí)間的策略,另一方面也實(shí)現(xiàn)對(duì)資源的復(fù)用粪般,從而避免了不必要的創(chuàng)建和釋放資源所帶來的開銷拼余。
- 什么是XSS攻擊?什么是SQL注入攻擊亩歹?什么是CSRF攻擊匙监?
- XSS(Cross Site Script,跨站腳本攻擊)是向網(wǎng)頁(yè)中注入惡意腳本在用戶瀏覽網(wǎng)頁(yè)時(shí)在用戶瀏覽器中執(zhí)行惡意腳本的攻擊方式小作⊥だ眩跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點(diǎn)擊一個(gè)嵌入惡意腳本的鏈接以達(dá)到攻擊的目標(biāo),目前有很多攻擊者利用論壇顾稀、微博發(fā)布含有惡意腳本的URL就屬于這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網(wǎng)站的數(shù)據(jù)庫(kù)中达罗,用戶瀏覽網(wǎng)頁(yè)時(shí),惡意腳本從數(shù)據(jù)庫(kù)中被加載到頁(yè)面執(zhí)行静秆,QQ郵箱的早期版本就曾經(jīng)被利用作為持久型跨站腳本攻擊的平臺(tái))粮揉。XSS雖然不是什么新鮮玩意绍载,但是攻擊的手法卻不斷翻新,防范XSS主要有兩方面:消毒(對(duì)危險(xiǎn)字符進(jìn)行轉(zhuǎn)義)和HttpOnly(防范XSS攻擊者竊取Cookie數(shù)據(jù))滔蝉。
- SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實(shí)施OS注入攻擊導(dǎo)致的)),當(dāng)服務(wù)器使用請(qǐng)求參數(shù)構(gòu)造SQL語(yǔ)句時(shí)塔沃,惡意的SQL被嵌入到SQL中交給數(shù)據(jù)庫(kù)執(zhí)行蝠引。SQL注入攻擊需要攻擊者對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)有所了解才能進(jìn)行,攻擊者想要獲得表結(jié)構(gòu)有多種方式:(1)如果使用開源系統(tǒng)搭建網(wǎng)站蛀柴,數(shù)據(jù)庫(kù)結(jié)構(gòu)也是公開的(目前有很多現(xiàn)成的系統(tǒng)可以直接搭建論壇螃概,電商網(wǎng)站,雖然方便快捷但是風(fēng)險(xiǎn)是必須要認(rèn)真評(píng)估的)鸽疾;(2)錯(cuò)誤回顯(如果將服務(wù)器的錯(cuò)誤信息直接顯示在頁(yè)面上吊洼,攻擊者可以通過非法參數(shù)引發(fā)頁(yè)面錯(cuò)誤從而通過錯(cuò)誤信息了解數(shù)據(jù)庫(kù)結(jié)構(gòu),Web應(yīng)用應(yīng)當(dāng)設(shè)置友好的錯(cuò)誤頁(yè)制肮,一方面符合最小驚訝原則冒窍,一方面屏蔽掉可能給系統(tǒng)帶來危險(xiǎn)的錯(cuò)誤回顯信息);(3)盲注豺鼻。防范SQL注入攻擊也可以采用消毒的方式综液,通過正則表達(dá)式對(duì)請(qǐng)求參數(shù)進(jìn)行驗(yàn)證,此外儒飒,參數(shù)綁定也是很好的手段谬莹,這樣惡意的SQL會(huì)被當(dāng)做SQL的參數(shù)而不是命令被執(zhí)行,JDBC中的PreparedStatement就是支持參數(shù)綁定的語(yǔ)句對(duì)象桩了,從性能和安全性上都明顯優(yōu)于Statement附帽。
-
CSRF攻擊(Cross Site Request Forgery,跨站請(qǐng)求偽造)是攻擊者通過跨站請(qǐng)求井誉,以合法的用戶身份進(jìn)行非法操作(如轉(zhuǎn)賬或發(fā)帖等)蕉扮。CSRF的原理是利用瀏覽器的Cookie或服務(wù)器的Session,盜取用戶身份送悔,其原理如下圖所示慢显。防范CSRF的主要手段是識(shí)別請(qǐng)求者的身份,主要有以下幾種方式:(1)在表單中添加令牌(token)欠啤;(2)驗(yàn)證碼荚藻;(3)檢查請(qǐng)求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)。令牌和驗(yàn)證都具有一次消費(fèi)性的特征洁段,因此在原理上一致的应狱,但是驗(yàn)證碼是一種糟糕的用戶體驗(yàn),不是必要的情況下不要輕易使用驗(yàn)證碼祠丝,目前很多網(wǎng)站的做法是如果在短時(shí)間內(nèi)多次提交一個(gè)表單未獲得成功后才要求提供驗(yàn)證碼疾呻,這樣會(huì)獲得較好的用戶體驗(yàn)除嘹。
補(bǔ)充:防火墻的架設(shè)是Web安全的重要保障,ModSecurity是開源的Web防火墻中的佼佼者岸蜗。企業(yè)級(jí)防火墻的架設(shè)應(yīng)當(dāng)有兩級(jí)防火墻尉咕,Web服務(wù)器和部分應(yīng)用服務(wù)器可以架設(shè)在兩級(jí)防火墻之間的DMZ,而數(shù)據(jù)和資源服務(wù)器應(yīng)當(dāng)架設(shè)在第二級(jí)防火墻之后璃岳。