java面試題之“反射和動(dòng)態(tài)代理

前言:

正文之前别渔,容我小小的矯情一下附迷。我知道每個(gè)人的生活有很多意外、有很多迷茫哎媚、但是“我相信”一件事堅(jiān)持下去喇伯,就不會(huì)有太多時(shí)間去和迷茫、焦慮去較勁拨与。從而愛(ài)情和面包都會(huì)隨之而來(lái)的稻据!

如果你正年輕,請(qǐng)奮斗买喧!如果你正值中年捻悯,請(qǐng)你保持激情!

好了話不多說(shuō)淤毛,咱們進(jìn)入正題今缚。

反射

反射機(jī)制是 Java 語(yǔ)言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自实偷(introspect)的能力姓言。簡(jiǎn)單來(lái)說(shuō)就是通過(guò)反射瞬项,可以在運(yùn)行期間獲取、檢測(cè)和調(diào)用對(duì)象的屬性和方法何荚。

反射的使用場(chǎng)景

1.編程工具 IDEA 或 Eclipse 等囱淋,在寫(xiě)代碼時(shí)會(huì)有代碼(屬性或方法名)提示,就是因?yàn)槭褂昧朔瓷洌?/p>

2.很多知名的框架兽泣,為了讓程序更優(yōu)雅更簡(jiǎn)潔绎橘,也會(huì)使用到反射。

例如唠倦,Spring 可以通過(guò)配置來(lái)加載不同的類(lèi)称鳞,調(diào)用不同的方法,代碼如下所示:

<bean id="person" class="com.spring.beans.Person" init-method="initPerson">

</bean>

例如稠鼻,MyBatis 在 Mapper 使用外部類(lèi)的 Sql 構(gòu)建查詢時(shí)冈止,代碼如下所示:

@SelectProvider(type = PersonSql.class, method = "getListSql")

List<Person> getList();

class PersonSql {

public String getListSql() {

String sql = new SQL() {{

SELECT("*");

FROM("person");

}}.toString();

return sql;

}

}

數(shù)據(jù)庫(kù)連接池,也會(huì)使用反射調(diào)用不同類(lèi)型的數(shù)據(jù)庫(kù)驅(qū)動(dòng)候齿,代碼如下所示:

String url = "jdbc:mysql://127.0.0.1:3306/mydb";

String username = "root";

String password = "root";

Class.forName("com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection(url, username, password);

反射的基本使用

使用反射調(diào)用類(lèi)中的方法熙暴,分為三種情況:

調(diào)用靜態(tài)方法

調(diào)用公共方法

調(diào)用私有方法

假設(shè)有一個(gè)實(shí)體類(lèi) MyReflect 包含了以上三種方法,代碼如下:

package com.interview.chapter4;

class MyReflect {

// 靜態(tài)方法

public static void staticMd() {

System.out.println("Static Method");

}

// 公共方法

public void publicMd() {

System.out.println("Public Method");

}

// 私有方法

private void privateMd() {

System.out.println("Private Method");

}

}

① 反射調(diào)用靜態(tài)方法

Class myClass = Class.forName("com.interview.chapter4.MyReflect");

Method method = myClass.getMethod("staticMd");

method.invoke(myClass);

② 反射調(diào)用公共方法

Class myClass = Class.forName("com.interview.chapter4.MyReflect");

// 創(chuàng)建實(shí)例對(duì)象(相當(dāng)于 new )

Object instance = myClass.newInstance();

Method method2 = myClass.getMethod("publicMd");

method2.invoke(instance);

③ 反射調(diào)用私有方法

Class myClass = Class.forName("com.interview.chapter4.MyReflect");

// 創(chuàng)建實(shí)例對(duì)象(相當(dāng)于 new )

Object object = myClass.newInstance();

Method method3 = myClass.getDeclaredMethod("privateMd");

method3.setAccessible(true);

method3.invoke(object);

反射總結(jié)

反射獲取調(diào)用類(lèi)可以通過(guò) Class.forName()慌盯,反射獲取類(lèi)實(shí)例要通過(guò) newInstance()周霉,相當(dāng)于 new 一個(gè)新對(duì)象,反射獲取方法要通過(guò) getMethod()亚皂,獲取到類(lèi)方法之后使用 invoke() 對(duì)類(lèi)方法進(jìn)行調(diào)用俱箱。如果是類(lèi)方法為私有方法的話,則需要通過(guò) setAccessible(true) 來(lái)修改方法的訪問(wèn)限制灭必。

動(dòng)態(tài)代理

動(dòng)態(tài)代理可以理解為狞谱,本來(lái)應(yīng)該自己做的事情,卻交給別人代為處理禁漓,這個(gè)過(guò)程就叫做動(dòng)態(tài)代理跟衅。

動(dòng)態(tài)代理的使用場(chǎng)景

動(dòng)態(tài)代理被廣為人知的使用場(chǎng)景是 Spring 中的面向切面編程(AOP)。例如播歼,依賴注入 @Autowired 和事務(wù)注解 @Transactional 等伶跷,都是利用動(dòng)態(tài)代理實(shí)現(xiàn)的。動(dòng)態(tài)代理還可以封裝一些 RPC 調(diào)用荚恶,也可以通過(guò)代理實(shí)現(xiàn)一個(gè)全局?jǐn)r截器等撩穿。

動(dòng)態(tài)代理和反射的關(guān)系

JDK 原生提供的動(dòng)態(tài)代理就是通過(guò)反射實(shí)現(xiàn)的,但動(dòng)態(tài)代理的實(shí)現(xiàn)方式還可以是 ASM(一個(gè)短小精悍的字節(jié)碼操作框架)谒撼、cglib(基于 ASM)等食寡,并不局限于反射。

JDK 原生動(dòng)態(tài)代理和 cglib 的實(shí)現(xiàn)

1.JDK 原生動(dòng)態(tài)代理

interface Animal {

void eat();

}

class Dog implements Animal {

@Override

public void eat() {

System.out.println("The dog is eating");

}

}

class Cat implements Animal {

@Override

public void eat() {

System.out.println("The cat is eating");

}

}

// JDK 代理類(lèi)

class AnimalProxy implements InvocationHandler {

private Object target; // 代理對(duì)象

public Object getInstance(Object target) {

this.target = target;

// 取得代理對(duì)象

return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("調(diào)用前");

Object result = method.invoke(target, args); // 方法調(diào)用

System.out.println("調(diào)用后");

return result;

}

}

public static void main(String[] args) {

// JDK 動(dòng)態(tài)代理調(diào)用

AnimalProxy proxy = new AnimalProxy();

Animal dogProxy = (Animal) proxy.getInstance(new Dog());

dogProxy.eat();

}

注意:?JDK Proxy 只能代理實(shí)現(xiàn)接口的類(lèi)(即使是 extends 繼承類(lèi)也是不可以代理的)廓潜!

2.cglib 動(dòng)態(tài)代理

要是用 cglib 實(shí)現(xiàn)要添加對(duì) cglib 的引用抵皱,如果是 maven 項(xiàng)目的話善榛,直接添加以下代碼:

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.2.12</version>

</dependency>

cglib 的具體實(shí)現(xiàn),請(qǐng)參考以下代碼:

class Panda {

public void eat() {

System.out.println("The panda is eating");

}

}

class CglibProxy implements MethodInterceptor {

private Object target; // 代理對(duì)象

public Object getInstance(Object target) {

this.target = target;

Enhancer enhancer = new Enhancer();

// 設(shè)置父類(lèi)為實(shí)例類(lèi)

enhancer.setSuperclass(this.target.getClass());

// 回調(diào)方法

enhancer.setCallback(this);

// 創(chuàng)建代理對(duì)象

return enhancer.create();

}

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println("調(diào)用前");

Object result = methodProxy.invokeSuper(o, objects); // 執(zhí)行方法調(diào)用

System.out.println("調(diào)用后");

return result;

}

}

public static void main(String[] args) {

// cglib 動(dòng)態(tài)代理調(diào)用

CglibProxy proxy = new CglibProxy();

Panda panda = (Panda)proxy.getInstance(new Panda());

panda.eat();

}

以上程序執(zhí)行的結(jié)果:

調(diào)用前

The panda is eating

調(diào)用后

由以上代碼可以知道呻畸,cglib 的調(diào)用通過(guò)實(shí)現(xiàn) MethodInterceptor 接口的 intercept 方法移盆,調(diào)用 invokeSuper 進(jìn)行動(dòng)態(tài)代理的。它可以直接對(duì)普通類(lèi)進(jìn)行動(dòng)態(tài)代理伤为,并不需要像 JDK 代理那樣咒循,需要通過(guò)接口來(lái)完成,值得一提的是 Spring 的動(dòng)態(tài)代理也是通過(guò) cglib 實(shí)現(xiàn)的绞愚。

注意:cglib 底層是通過(guò)子類(lèi)繼承被代理對(duì)象的方式實(shí)現(xiàn)動(dòng)態(tài)代理的叙甸,因此代理類(lèi)不能是最終類(lèi)(final),否則就會(huì)報(bào)錯(cuò) java.lang.IllegalArgumentException: Cannot subclass final class xxx位衩。

面試筆試題

1.動(dòng)態(tài)代理解決了什么問(wèn)題裆蒸?

答:首先它是一個(gè)代理機(jī)制,代理可以看作是對(duì)調(diào)用目標(biāo)的一個(gè)包裝糖驴,這樣我們對(duì)目標(biāo)代碼的調(diào)用不是直接發(fā)生的僚祷,而是通過(guò)代理完成,通過(guò)代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦贮缕。比如進(jìn)行 RPC 調(diào)用辙谜,通過(guò)代理,可以提供更加友善的界面感昼;還可以通過(guò)代理筷弦,做一個(gè)全局的攔截器。

2.動(dòng)態(tài)代理和反射的關(guān)系是什么抑诸?

答:反射可以用來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理,但動(dòng)態(tài)代理還有其他的實(shí)現(xiàn)方式爹殊,比如 ASM(一個(gè)短小精悍的字節(jié)碼操作框架)蜕乡、cglib 等。

3.以下描述錯(cuò)誤的是梗夸?

A:cglib 的性能更高

B:Spring 中有使用 cglib 來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理

C:Spring 中有使用 JDK 原生的動(dòng)態(tài)代理

D:JDK 原生動(dòng)態(tài)代理性能更高

答:D

題目解析:Spring 動(dòng)態(tài)代理的實(shí)現(xiàn)方式有兩種:cglib 和 JDK 原生動(dòng)態(tài)代理层玲。

4.請(qǐng)補(bǔ)全以下代碼?

class MyReflect {

// 私有方法

private void privateMd() {

System.out.println("Private Method");

}

}

class ReflectTest {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {

Class myClass = Class.forName("MyReflect");

Object object = myClass.newInstance();

// 補(bǔ)充此行代碼

method.setAccessible(true);

method.invoke(object);

}

}

答:Method method = myClass.getDeclaredMethod("privateMd");

題目解析:此題主要考的是私有方法的獲取反症,私有方法的獲取并不是通過(guò) getMethod() 方式辛块,而是通過(guò) getDeclaredMethod() 獲取的。

5.cglib 可以代理任何類(lèi)這句話對(duì)嗎铅碍?為什么润绵?

答:不完全對(duì),因?yàn)?cglib 只能代理可以有子類(lèi)的普通類(lèi)胞谈,對(duì)于像最終類(lèi)(final)尘盼,cglib 是不能實(shí)現(xiàn)動(dòng)態(tài)代理的憨愉,因?yàn)?cglib 的底層是通過(guò)繼承代理類(lèi)的子類(lèi)來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理的,所以不能被繼承類(lèi)無(wú)法使用 cglib卿捎。

6.JDK 原生動(dòng)態(tài)代理和 cglib 有什么區(qū)別配紫?

答:JDK 原生動(dòng)態(tài)代理和 cglib 區(qū)別如下:

JDK 原生動(dòng)態(tài)代理是基于接口實(shí)現(xiàn)的,不需要添加任何依賴午阵,可以平滑的支持 JDK 版本的升級(jí)躺孝;

cglib 不需要實(shí)現(xiàn)接口,可以直接代理普通類(lèi)底桂,需要添加依賴包植袍,性能更高。

7.為什么 JDK 原生的動(dòng)態(tài)代理必須要通過(guò)接口來(lái)完成戚啥?

答:這是由于 JDK 原生設(shè)計(jì)的原因奋单,動(dòng)態(tài)代理的實(shí)現(xiàn)方法 newProxyInstance() 的源碼如下:

/**

* ......

* @param loader the class loader to define the proxy class

* @param interfaces the list of interfaces for the proxy class to implement

* ......

*/

@CallerSensitive

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

throws IllegalArgumentException

{

// 省略其他代碼

可以看到,前兩個(gè)參數(shù)的聲明:

loader:為類(lèi)加載器猫十,也就是 target.getClass().getClassLoader()

interfaces:接口代理類(lèi)的接口實(shí)現(xiàn)列表

因此览濒,要使用 JDK 原生的動(dòng)態(tài)只能通過(guò)實(shí)現(xiàn)接口來(lái)完成。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拖云,一起剝皮案震驚了整個(gè)濱河市贷笛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宙项,老刑警劉巖乏苦,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尤筐,居然都是意外死亡汇荐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)盆繁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掀淘,“玉大人,你說(shuō)我怎么就攤上這事油昂「锫Γ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵冕碟,是天一觀的道長(zhǎng)拦惋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)安寺,這世上最難降的妖魔是什么厕妖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮我衬,結(jié)果婚禮上叹放,老公的妹妹穿的比我還像新娘饰恕。我一直安慰自己,他們只是感情好井仰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布埋嵌。 她就那樣靜靜地躺著,像睡著了一般俱恶。 火紅的嫁衣襯著肌膚如雪雹嗦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天合是,我揣著相機(jī)與錄音了罪,去河邊找鬼。 笑死聪全,一個(gè)胖子當(dāng)著我的面吹牛泊藕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播难礼,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼娃圆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蛾茉?” 一聲冷哼從身側(cè)響起讼呢,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谦炬,沒(méi)想到半個(gè)月后悦屏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡键思,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年础爬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼鳞。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幕帆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赖条,到底是詐尸還是另有隱情,我是刑警寧澤常熙,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布纬乍,位于F島的核電站,受9級(jí)特大地震影響裸卫,放射性物質(zhì)發(fā)生泄漏仿贬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一墓贿、第九天 我趴在偏房一處隱蔽的房頂上張望茧泪。 院中可真熱鬧蜓氨,春花似錦、人聲如沸队伟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嗜侮。三九已至港令,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锈颗,已是汗流浹背顷霹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留击吱,地道東北人淋淀。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像覆醇,于是被迫代替她去往敵國(guó)和親朵纷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容