日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java提高班(六)反射和动态代理(JDK Proxy和Cglib)

發(fā)布時(shí)間:2025/3/15 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java提高班(六)反射和动态代理(JDK Proxy和Cglib) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

反射和動(dòng)態(tài)代理放有一定的相關(guān)性,但單純的說動(dòng)態(tài)代理是由反射機(jī)制實(shí)現(xiàn)的,其實(shí)是不夠全面不準(zhǔn)確的,動(dòng)態(tài)代理是一種功能行為,而它的實(shí)現(xiàn)方法有很多。要怎么理解以上這句話,請(qǐng)看下文。

一、反射

反射機(jī)制是 Java 語言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者對(duì)象,比如獲取某個(gè)對(duì)象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對(duì)象,甚至可以運(yùn)行時(shí)修改類定義。

1、獲取類(Class)對(duì)象

獲取類對(duì)象有三種方法:

  • 通過forName() -> 示例:Class.forName("PeopleImpl")
  • 通過getClass() -> 示例:new PeopleImpl().getClass()
  • 直接獲取.class -> 示例:PeopleImpl.class

2、類的常用方法

  • getName():獲取類完整方法;
  • getSuperclass():獲取類的父類;
  • newInstance():創(chuàng)建實(shí)例對(duì)象;
  • getFields():獲取當(dāng)前類和父類的public修飾的所有屬性;
  • getDeclaredFields():獲取當(dāng)前類(不包含父類)的聲明的所有屬性;
  • getMethod():獲取當(dāng)前類和父類的public修飾的所有方法;
  • getDeclaredMethods():獲取當(dāng)前類(不包含父類)的聲明的所有方法;

更多方法:http://icdn.apigo.cn/blog/class-all-method.png

3、類方法調(diào)用

反射要調(diào)用類中的方法,需要通過關(guān)鍵方法“invoke()”實(shí)現(xiàn)的,方法調(diào)用也分為三種:

  • 靜態(tài)(static)方法調(diào)用
  • 普通方法調(diào)用
  • 私有方法調(diào)用

以下會(huì)分別演示,各種調(diào)用的實(shí)現(xiàn)代碼,各種調(diào)用的公共代碼部分,如下:

// 此段代碼為公共代碼 interface People {int parentAge = 18;public void sayHi(String name); } class PeopleImpl implements People {private String privSex = "男";public String race = "漢族";@Overridepublic void sayHi(String name) {System.out.println("hello," + name);}private void prvSayHi() {System.out.println("prvSayHi~");}public static void getSex() {System.out.println("18歲");} }

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

// 核心代碼(省略了拋出異常的聲明) public static void main(String[] args) {Class myClass = Class.forName("example.PeopleImpl");// 調(diào)用靜態(tài)(static)方法Method getSex = myClass.getMethod("getSex");getSex.invoke(myClass); }

靜態(tài)方法的調(diào)用比較簡(jiǎn)單,使用 getMethod(xx) 獲取到對(duì)應(yīng)的方法,直接使用 invoke(xx)就可以了。

3.2 普通方法調(diào)用

普通非靜態(tài)方法調(diào)用,需要先獲取類示例,通過“newInstance()”方法獲取,核心代碼如下:

Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method method = myClass.getMethod("sayHi",String.class); method.invoke(object,"老王");

getMethod 獲取方法,可以聲明需要傳遞的參數(shù)的類型。

3.3 調(diào)用私有方法

調(diào)用私有方法,必須使用“getDeclaredMethod(xx)”獲取本類所有什么的方法,代碼如下:

Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method privSayHi = myClass.getDeclaredMethod("privSayHi"); privSayHi.setAccessible(true); // 修改訪問限制 privSayHi.invoke(object);

除了“getDeclaredMethod(xx)”可以看出,調(diào)用私有方法的關(guān)鍵是設(shè)置 setAccessible(true) 屬性,修改訪問限制,這樣設(shè)置之后就可以進(jìn)行調(diào)用了。

4、總結(jié)

1.在反射中核心的方法是 newInstance() 獲取類實(shí)例,getMethod(..) 獲取方法,使用 invoke(..) 進(jìn)行方法調(diào)用,通過 setAccessible 修改私有變量/方法的訪問限制。

2.獲取屬性/方法的時(shí)候有無“Declared”的區(qū)別是,帶有 Declared 修飾的方法或?qū)傩?#xff0c;可以獲取本類的所有方法或?qū)傩?#xff08;private 到 public),但不能獲取到父類的任何信息;非 Declared 修飾的方法或?qū)傩?#xff0c;只能獲取 public 修飾的方法或?qū)傩?#xff0c;并可以獲取到父類的信息,比如 getMethod(..)和getDeclaredMethod(..)。

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

動(dòng)態(tài)代理是一種方便運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建代理、動(dòng)態(tài)處理代理方法調(diào)用的機(jī)制,很多場(chǎng)景都是利用類似機(jī)制做到的,比如用來包裝 RPC 調(diào)用、面向切面的編程(AOP)。

實(shí)現(xiàn)動(dòng)態(tài)代理的方式很多,比如 JDK 自身提供的動(dòng)態(tài)代理,就是主要利用了上面提到的反射機(jī)制。還有其他的實(shí)現(xiàn)方式,比如利用傳說中更高性能的字節(jié)碼操作機(jī)制,類似 ASM、cglib(基于 ASM)等。

動(dòng)態(tài)代理解決的問題?

首先,它是一個(gè)代理機(jī)制。如果熟悉設(shè)計(jì)模式中的代理模式,我們會(huì)知道,代理可以看作是對(duì)調(diào)用目標(biāo)的一個(gè)包裝,這樣我們對(duì)目標(biāo)代碼的調(diào)用不是直接發(fā)生的,而是通過代理完成。通過代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦。比如進(jìn)行 RPC 調(diào)用,通過代理,可以提供更加友善的界面。還可以通過代理,可以做一個(gè)全局的攔截器。

1、JDK Proxy 動(dòng)態(tài)代理

JDK Proxy 是通過實(shí)現(xiàn) InvocationHandler 接口來實(shí)現(xiàn)的,代碼如下:

interface Animal {void eat(); } class Dog implements Animal {@Overridepublic void eat() {System.out.println("The dog is eating");} } class Cat implements Animal {@Overridepublic void eat() {System.out.println("The cat is eating");} }// JDK 代理類 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);}@Overridepublic 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(); }

如上代碼,我們實(shí)現(xiàn)了通過動(dòng)態(tài)代理,在所有請(qǐng)求之前和之后打印了一個(gè)簡(jiǎn)單的信息。

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

JDK Proxy 為什么只能代理實(shí)現(xiàn)接口的類?

這個(gè)問題要從動(dòng)態(tài)代理的實(shí)現(xiàn)方法 newProxyInstance 源碼說起:

@CallerSensitive public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException { // 省略其他代碼

來看前兩個(gè)源碼參數(shù)說明:

* @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class to implement
  • loader:為類加載器,也就是 target.getClass().getClassLoader()
  • interfaces:接口代理類的接口實(shí)現(xiàn)列表

所以這個(gè)問題的源頭,在于 JDK Proxy 的源碼設(shè)計(jì)。如果要執(zhí)意動(dòng)態(tài)代理,非接口實(shí)現(xiàn)類就會(huì)報(bào)錯(cuò):

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx

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

JDK 動(dòng)態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,Cglib 是針對(duì)類來實(shí)現(xiàn)代理的,他的原理是對(duì)指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對(duì) final 修飾的類進(jìn)行代理。

Cglib 可以通過 Maven 直接進(jìn)行版本引用,Maven 版本地址:https://mvnrepository.com/artifact/cglib/cglib

本文使用的是最新版本 3.2.9 的 Cglib,在 pom.xml 添加如下引用:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.9</version> </dependency>

Cglib 代碼實(shí)現(xiàn),如下:

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è)置父類為實(shí)例類enhancer.setSuperclass(this.target.getClass());// 回調(diào)方法enhancer.setCallback(this);// 創(chuàng)建代理對(duì)象return enhancer.create();}@Overridepublic 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(); }

cglib 的調(diào)用通過實(shí)現(xiàn) MethodInterceptor 接口的 intercept 方法,調(diào)用 invokeSuper 進(jìn)行動(dòng)態(tài)代理的,可以直接對(duì)普通類進(jìn)行動(dòng)態(tài)代理。

三、JDK Proxy VS Cglib

JDK Proxy 的優(yōu)勢(shì):

  • 最小化依賴關(guān)系,減少依賴意味著簡(jiǎn)化開發(fā)和維護(hù),JDK 本身的支持,更加可靠;
  • 平滑進(jìn)行 JDK 版本升級(jí),而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版上能夠使用;

Cglib 框架的優(yōu)勢(shì):

  • 可調(diào)用普通類,不需要實(shí)現(xiàn)接口;
  • 高性能;

總結(jié): 需要注意的是,我們?cè)谶x型中,性能未必是唯一考量,可靠性、可維護(hù)性、編程工作量等往往是更主要的考慮因素,畢竟標(biāo)準(zhǔn)類庫和反射編程的門檻要低得多,代碼量也是更加可控的,如果我們比較下不同開源項(xiàng)目在動(dòng)態(tài)代理開發(fā)上的投入,也能看到這一點(diǎn)。

本文所有示例代碼:https://github.com/vipstone/java-core-example.git

四、參考文檔

Java核心技術(shù)36講:http://t.cn/EwUJvWA

Java反射與動(dòng)態(tài)代理:https://www.cnblogs.com/hanganglin/p/4485999.html

轉(zhuǎn)載于:https://www.cnblogs.com/vipstone/p/10104020.html

總結(jié)

以上是生活随笔為你收集整理的Java提高班(六)反射和动态代理(JDK Proxy和Cglib)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。