終於有人把 JDK動態代理講清楚了,萬字詳解

什麼是代理

代理模式是常用的java設計模式,它的特徵是代理類與委託類有同樣的接口,代理類主要負責為委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。

代理其實不僅僅是在軟件開發領域,在我們的日常生活中也是時常可見。比如某p2p老闆突然攜款帶著小姨子跑路了,可憐了下面一堆的程序員揹負一身房貸,上有老下有小,程序員只能被迫去申請勞動仲裁,勞動局就會為其指派一位代理律師全權負責程序員的仲裁事宜(PS:p2p跑路仲裁拿回工資的可能性非常低,沒讓你把工資退回就算好的了)。那這裡面就是使用了代理模式,因為在勞動仲裁這個活動中,代理律師會全權代理程序員。比如:房東要將房子出售,於是到房地產中介公司找一箇中介(代理),由他來幫房東完成銷售房屋,簽訂合同、網籤、貸款過戶等等事宜。

代理模式

終於有人把 JDK動態代理講清楚了,萬字詳解

這是常見代理模式常見的 UML 示意圖。 需要注意的有下面幾點:

  1. 用戶只關心接口功能,而不在乎誰提供了功能。上圖中接口是 Subject 。
  2. 接口真正實現者是上圖的 RealSubject ,但是它不與用戶直接接觸,而是通過代理。
  3. 代理就是上圖中的 Proxy ,由於它實現了 Subject 接口,所以它能夠直接與用戶接觸。
  4. Proxy Proxy RealSubject Proxy RealSubject
  • 代理又可以分為靜態代理和動態代理兩種。我們先來看下靜態代理。

靜態代理

電影是電影公司委託給影院進行播放的,但是影院可以在播放電影的時候,產生一些自己的經濟收益,比如提供按摩椅,娃娃機(這個每次去電影院都會嘗試下,基本上是夾不起來,有木有大神可以傳授下訣竅),賣爆米花、飲料(貴的要死,反正吃不起)等。我們平常去電影院看電影的時候,在電影開始的階段是不是經常會放廣告呢?然後在影片開始結束時播放一些廣告。 下面我們通過代碼來模擬下電影院這一系列的賺錢操作。 首先得有一個接口,通用的接口是代理模式實現的基礎。這個接口我們命名為 Movie ,代表電影播放的能力。

<code>

package

com.workit.demo.proxy;

public

interface

Movie

{

void

play

()

; } /<code>
  • 接下來我們要創建一個真正的實現這個 Movie 接口的類,和一個實現該接口的代理類。 真正的類 《美國隊長》 電影:
<code>

package

com.workit.demo.proxy;

public

class

CaptainAmericaMovie

implements

Movie

{

public

void

play

()

{ System.out.println(

"普通影廳正在播放的電影是《美國隊長》"

); } }/<code>

代理類:

<code>

package

com.workit.demo.proxy;

public

class

MovieStaticProxy

implements

Movie

{ Movie movie;

public

MovieStaticProxy

(Movie movie)

{

this

.movie = movie; }

public

void

play

()

{ playStart(); movie.play(); playEnd(); }

public

void

playStart

()

{ System.out.println(

"電影開始前正在播放廣告"

); }

public

void

playEnd

()

{ System.out.println(

"電影結束了,接續播放廣告"

); } } /<code>

測試類:

<code>

package

com.workit.demo.proxy;

package

com.workit.demo.proxy;

public

class

StaticProxyTest

{

public

static

void

main

(String[] args)

{ Movie captainAmericaMovie =

new

CaptainAmericaMovie(); Movie movieStaticProxy =

new

MovieStaticProxy(captainAmericaMovie); movieStaticProxy.play(); } }/<code>

運行結果:

<code>電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結束了,接續播放廣告/<code>

現在可以看到, 代理模式可以在不修改被代理對象的基礎上,通過擴展代理類,進行一些功能的附加與增強。值得注意的是,代理類和被代理類應該共同實現一個接口,或者是共同繼承某個類 。這個就是是靜態代理的內容,為什麼叫做靜態呢?因為它的類型是事先預定好的,比如上面代碼中的 MovieStaticProxy 這個類。

優點

  • 代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用
  • 代理對象可以擴展目標對象的功能
  • 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度。

缺點

  • 代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護。

jdk動態代理

與靜態代理類對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因為Java 反射機制可以生成任意類型的動態代理類。 java.lang.reflect 包中的Proxy類和 InvocationHandler 接口提供了生成動態代理類的能力。

  • 接著上面的例子,剛看完《美國隊長》不過癮,還想繼續去看一場《鋼鐵俠》。一直在普通影廳看電影覺得沒啥意思,那就趕緊去VIP影廳( 至今不知道長啥樣子 )體驗一把。既然 實體店沒體驗過那就用代碼來體驗一次吧。創建一個VIPMovie電影接口
<code>

package

com.workit.demo.proxy;

public

interface

VIPMovie

{

void

vipPlay

()

; }/<code>

緊接著創建一個VIP影廳的播放實現類

<code>

package

com.workit.demo.proxy;

public

class

IronManVIPMovie

implements

VIPMovie

{

public

void

vipPlay

()

{ System.out.println(

"VI影廳正在播放的電影是《鋼鐵俠》"

); } }/<code>

如果按照靜態代理我們是不是又要創建一個VIP影廳播放的代理實現類,這種方式我們就不演示了。下面我們來看看通過動態代理怎麼來實現吧。

<code>package com.workit.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public

class

MyInvocationHandler

implements

InvocationHandler

{

private

Object

object

;

public

MyInvocationHandler

(

Object

object

)

{

this

.

object

=

object

; } @

Override

public

Object

invoke

(

Object proxy, Method method, Object[] args

) throws Throwable

{ playStart(); Object invoke = method.invoke(

object

, args); playEnd();

return

invoke; }

public

void

playStart

(

)

{ System.

out

.println(

"電影開始前正在播放廣告"

); }

public

void

playEnd

(

)

{ System.

out

.println(

"電影結束了,接續播放廣告"

); } }/<code>

MyInvocationHandler 實現了 InvocationHandler 這個類,這個類是什麼意思呢?大家不要慌張,下面我會解釋。然後,我們就可以在VIP影廳看電影了。

<code>

package

com.workit.demo.proxy;

import

java.lang.reflect.InvocationHandler;

import

java.lang.reflect.Proxy;

public

class

DynamicProxyTest

{

public

static

void

main

(String[] args)

{ IronManVIPMovie ironManVIPMovie =

new

IronManVIPMovie(); InvocationHandler invocationHandler =

new

MyInvocationHandler(ironManVIPMovie); VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie

.

class

.

getClassLoader

(),

IronManVIPMovie

.

class

.

getInterfaces

(),

invocationHandler

)

; dynamicProxy.vipPlay(); } }/<code>

輸出結果:

<code>電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結束了,接續播放廣告/<code>

看到沒有,我們並沒有像靜態代理那樣為 VIPMovie 接口實現一個代理類,但最終它仍然實現了相同的功能,這其中的差別,就是之前討論的動態代理所謂“動態”的原因。 我們順帶把《美國隊長》也用動態代理實現下吧。

<code>

package

com.workit.demo.proxy;

import

java.lang.reflect.InvocationHandler;

import

java.lang.reflect.Proxy;

public

class

DynamicProxyTest

{

public

static

void

main

(String[] args)

{ IronManVIPMovie ironManVIPMovie =

new

IronManVIPMovie(); InvocationHandler invocationHandler =

new

MyInvocationHandler(ironManVIPMovie); VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie

.

class

.

getClassLoader

(),

IronManVIPMovie

.

class

.

getInterfaces

(),

invocationHandler

)

; dynamicProxy.vipPlay(); CaptainAmericaMovie captainAmericaMovie =

new

CaptainAmericaMovie(); InvocationHandler invocationHandler1 =

new

MyInvocationHandler(captainAmericaMovie); Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie

.

class

.

getClassLoader

(),

CaptainAmericaMovie

.

class

.

getInterfaces

(),

invocationHandler1

)

; dynamicProxy1.play(); } } /<code>

輸出結果:

<code>電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結束了,接續播放廣告
電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結束了,接續播放廣告/<code>

我們通過 Proxy.newProxyInstance() 方法,卻產生了 Movie 和 VIPMovie 兩種接口的實現類代理,這就是動態代理的魔力。

JDK動態代理到底是怎麼實現的呢

動態代碼涉及了一個非常重要的類 Proxy 。正是通過 Proxy 的靜態方法 newProxyInstance才會動態創建代理。具體怎麼去創建代理類就不分析了,感興趣的可以去看下源碼。我們直接看下生成的代理類。 如何查看生成的代理類? 在生成代理類之前加上以下代碼(我用的jdk1.8):

<code>  
 

System

.getProperties

()

.put

(

"jdk.proxy.ProxyGenerator.saveGeneratedFiles"

,

"true"

);/<code>

如果上述代碼加上不生效可以考慮加下下面的代碼:

<code> 

System

.getProperties

()

.put

(

"sun.misc.ProxyGenerator.saveGeneratedFiles"

,

"true"

);

System

.setProperty

(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,

"C:\\class"

); /<code>

代碼如下:

<code>  

public

static

void

main

(String[] args)

{ System.getProperties().put(

"jdk.proxy.ProxyGenerator.saveGeneratedFiles"

,

"true"

); IronManVIPMovie ironManVIPMovie =

new

IronManVIPMovie(); InvocationHandler invocationHandler =

new

MyInvocationHandler(ironManVIPMovie); VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie

.

class

.

getClassLoader

(),

IronManVIPMovie

.

class

.

getInterfaces

(),

invocationHandler

)

; dynamicProxy.vipPlay(); CaptainAmericaMovie captainAmericaMovie =

new

CaptainAmericaMovie(); InvocationHandler invocationHandler1 =

new

MyInvocationHandler(captainAmericaMovie); Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie

.

class

.

getClassLoader

(),

CaptainAmericaMovie

.

class

.

getInterfaces

(),

invocationHandler1

)

; dynamicProxy1.play(); System.out.println(

"VIP 影廳《鋼鐵俠》代理類:"

+dynamicProxy.getClass()); System.out.println(

"普通影廳《美國隊長》:"

+dynamicProxy1.getClass()); }/<code>

我們可以看到結果

<code>電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結束了,接續播放廣告
電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結束了,接續播放廣告
VIP 影廳《鋼鐵俠》代理類: 

class

com

.

sun

.

proxy

.

$Proxy0

普通影廳《美國隊長》:

class

com

.

sun

.

proxy

.

$Proxy1

/<code>

產生了兩個代理類分別是 $Proxy0 和 $Proxy1 。 下面們來看下"鋼鐵俠"的代理類 $Proxy0

<code> 
 
 
 

package

com.sun.proxy;

import

com.workit.demo.proxy.VIPMovie;

import

java.lang.reflect.InvocationHandler;

import

java.lang.reflect.Method;

import

java.lang.reflect.Proxy;

import

java.lang.reflect.UndeclaredThrowableException;

public

final

class

$

Proxy0

extends

Proxy

implements

VIPMovie

{

private

static

Method m1;

private

static

Method m3;

private

static

Method m2;

private

static

Method m0;

public

$Proxy0(InvocationHandler var1)

throws

{

super

(var1); }

public

final

boolean

equals

(Object var1)

throws

{

try

{

return

(Boolean)

super

.h.invoke(

this

, m1,

new

Object[]{var1}); }

catch

(RuntimeException | Error var3) {

throw

var3; }

catch

(Throwable var4) {

throw

new

UndeclaredThrowableException(var4); } }

public

final

void

vipPlay

()

throws

{

try

{

super

.h.invoke(

this

, m3, (Object[])

null

); }

catch

(RuntimeException | Error var2) {

throw

var2; }

catch

(Throwable var3) {

throw

new

UndeclaredThrowableException(var3); } }

public

final

String

toString

()

throws

{

try

{

return

(String)

super

.h.invoke(

this

, m2, (Object[])

null

); }

catch

(RuntimeException | Error var2) {

throw

var2; }

catch

(Throwable var3) {

throw

new

UndeclaredThrowableException(var3); } }

public

final

int

hashCode

()

throws

{

try

{

return

(Integer)

super

.h.invoke(

this

, m0, (Object[])

null

); }

catch

(RuntimeException | Error var2) {

throw

var2; }

catch

(Throwable var3) {

throw

new

UndeclaredThrowableException(var3); } }

static

{

try

{ m1 = Class.forName(

"java.lang.Object"

).getMethod(

"equals"

, Class.forName(

"java.lang.Object"

)); m3 = Class.forName(

"com.workit.demo.proxy.VIPMovie"

).getMethod(

"vipPlay"

); m2 = Class.forName(

"java.lang.Object"

).getMethod(

"toString"

); m0 = Class.forName(

"java.lang.Object"

).getMethod(

"hashCode"

); }

catch

(NoSuchMethodException var2) {

throw

new

NoSuchMethodError(var2.getMessage()); }

catch

(ClassNotFoundException var3) {

throw

new

NoClassDefFoundError(var3.getMessage()); } } },/<code>

通過上述代碼我們可以看到 $Proxy0 extends Proxy implements VIPMovie 繼承了 Proxy 且實現了 VIPMovie 接口,這也就是為什麼jdk動態代理必須基於接口,java 是單繼承的。 然後再看下代理類實現的方法:

<code> 

public

final

void

vipPlay

()

throws

{

try

{

super

.h.invoke(

this

, m3, (Object[])

null

); }

catch

(RuntimeException | Error var2) {

throw

var2; }

catch

(Throwable var3) {

throw

new

UndeclaredThrowableException(var3); } }/<code>

這個 supper.h.invoke Proxy 中的h的invoke方法,即 InvocationHandler.invoke 也就是上面 MyInvocationHandler.invok e方法,至此整個流程就清晰了。這就是jdk的動態代理。

cglib動態代理

上面說jdk動態代理只能基於接口,那麼如果是類要動態代理怎麼辦呢?cglib動態代理就可解決關於類的動態代理。 下面我們來創建一個“《美國隊長2》”

<code>package com.workit.demo.proxy;

public

class

CaptainAmerica2MovieImpl

{

public

void

play

(

)

{ System.

out

.println(

"正在播放的電影是《美國隊長2》"

); } } /<code>

引入cglib pom依賴

<code> 

<

dependency

>

<

groupId

>

cglib

groupId

>

<

artifactId

>

cglib

artifactId

>

<

version

>

3.3.0

version

>

dependency

>

/<code>

創建一個自定義MethodInterceptor。

<code>

package

com.workit.demo.proxy;

import

net.sf.cglib.proxy.MethodInterceptor;

import

net.sf.cglib.proxy.MethodProxy;

import

java.lang.reflect.Method;

public

class

CglibProxyInterceptor

implements

MethodInterceptor

{

public

Object

intercept

(Object o, Method method, Object[] objects, MethodProxy methodProxy)

throws

Throwable

{ playStart(); Object object = methodProxy.invokeSuper(o, objects); playEnd();

return

object; }

public

void

playStart

()

{ System.out.println(

"電影開始前正在播放廣告"

); }

public

void

playEnd

()

{ System.out.println(

"電影結束了,接續播放廣告"

); } } /<code>

測試類

<code>

package

com.workit.demo.proxy;

import

net.sf.cglib.core.DebuggingClassWriter;

import

net.sf.cglib.proxy.Enhancer;

public

class

CglibProxyTest

{

public

static

void

main

(String[] args)

{ System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,

"C:\\class"

); Enhancer enhancer =

new

Enhancer(); enhancer.setSuperclass(CaptainAmerica2MovieImpl

.

class

)

; enhancer.setCallback(

new

CglibProxyInterceptor()); CaptainAmerica2MovieImpl captainAmerica2Movie = (CaptainAmerica2MovieImpl)enhancer.create(); captainAmerica2Movie.play(); System.out.println(

"cglib動態代理《美國隊長2》:"

+captainAmerica2Movie.getClass()); } }/<code>

輸出結果:

<code>電影開始前正在播放廣告
正在播放的電影是《美國隊長2》
電影結束了,接續播放廣告
cglib動態代理《美國隊長2》:class com.workit.demo.proxy.CaptainAmerica2MovieImpl$

$EnhancerByCGLIB

$

$5c3ddcfe

/<code>

我們看下最終創建的代理類生成的 play 方法

<code>

public

class

CaptainAmerica2MovieImpl

$$

EnhancerByCGLIB

$$5

c3ddcfe

extends

CaptainAmerica2MovieImpl

implements

Factory

{

public

final

void

play

()

{ MethodInterceptor var10000 =

this

.CGLIB$CALLBACK_0;

if

(var10000 ==

null

) { CGLIB$BIND_CALLBACKS(

this

); var10000 =

this

.CGLIB$CALLBACK_0; }

if

(var10000 !=

null

) { var10000.intercept(

this

, CGLIB$play$

0

$Method, CGLIB$emptyArgs, CGLIB$play$

0

$Proxy); }

else

{

super

.play(); } }/<code>

從代理對象反編譯源碼可以知道,代理對象繼承於 CaptainAmerica2MovieImpl ,攔截器調用intercept ()方法, intercept ()方法由自定義 CglibProxyInterceptor 實現,所以,最後調用 CglibProxyInterceptor 中的 intercept ()方法,從而完成了由代理對象訪問到目標對象的動態代理實現。

  • CGlib是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口。
  • 用CGlib生成代理類是目標類的子類。
  • 用CGlib生成 代理類不需要接口。
  • 用CGLib生成的代理類重寫了父類的各個方法。
  • 攔截器中的intercept方法內容正好就是代理類中的方法體。

總結

  • 代理分為靜態代理和動態代理兩種。
  • 靜態代理,代理類需要自己編寫代碼寫成。
  • 動態代理有jdk和cglib,代理類通過 Proxy.newInstance() 或者 ASM 生成。
  • Proxy proxy class InvocationHandler MethodInterceptor
  • 代理模式本質上的目的是為了增強現有代碼的功能。


分享到:


相關文章: