Java代理分靜態代理和動態代理。
一、靜態代理
1、什麼是靜態代理
編譯時就已經將接口,被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。
2、靜態代理的簡單實現
需求:
假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這裏,班長就是代理學生上交班費。代理模式在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。這裏的間接性就是指不直接調用實際對象的方法。就這個例子來說,我們需要班長在幫張三上交班費之前想要先反映一下張三是否上交作業及時,我們通過代理來實現。
具體實現:
首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有上交班費的行爲。這樣,學生上交班費就可以讓班長來代理執行。
public interface Person {
//上交班費
void giveMoney();
}
Student類實現Person接口。Student可以具體實施上交班費的動作。
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班費50元");
}
}
StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,由於實現了Peson接口,同時持有一個學生對象,那麼他可以代理學生類對象執行上交班費(執行giveMoney()方法)行爲。
public class StudentsProxy implements Person{
//被代理的學生
Student stu;
public StudentsProxy(Person stu) {
// 只代理學生對象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班費,調用被代理學生的上交班費行爲
public void giveMoney() {
System.out.println("交作業及時");
stu.giveMoney();
}
}
爲什麼代理類也同樣需要繼承被代理類實現的接口?在點我們在後面會詳細概述
下面測試一下,看如何使用代理模式:
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的學生張三,他的班費上交有代理對象monitor(班長)完成
Person zhangsan = new Student("張三");
//生成代理對象,並將張三傳給代理對象
Person monitor = new StudentsProxy(zhangsan);
//班長代理上交班費
monitor.giveMoney();
}
}
運行結果:
交作業及時
張三上交班費50元
可以看到,只需要在代理類中幫張三上交班費之前,執行其他操作就可以了。這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切面編程(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
問題:
想要在每個代理的方法前都加上一個處理方法,這裏只有一個giveMoney方法,就寫一次beforeMethod方法,但是如果出了giveMonney還有很多其他的方法,那就需要寫很多次beforeMethod方法,麻煩。那看看下面動態代理如何實現。
二、動態代理
1、什麼是動態代理
代理類是在程序運行時被創建。該方式被稱爲動態代理。代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的,它實現了一組給定接口。
2、爲什麼要使用代理
代理類持有具體類的實例,代爲執行具體類實例方法。代理模式就是在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。這裏的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。
基於這個原理,代理可能出於很多原因,我列舉常見的幾種:
1.補充功能
2.簡化處理
3.路由對遠程服務器的調用。我並不需要直接對遠程服務器進行調用,因爲調用過程複雜,所以我們將這些方法全部交由代理處理,我只需要直接調用本地代理就可以了。
4.爲調試,跟蹤方法調用。
3、關鍵方法介紹
在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象。
1.創建代理方法
使用Proxy類的newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)方法。這個方法有三個參數:
參數一:一個類加載器。用null表示使用默認的類加載器。
參數二:一個class對象數組,每個元素都是需要實現的接口。
參數三:一個調用處理器。
2.調用處理器的invoke方法
調用處理器是實現了InvocationHandler接口的類對象,在這個接口中只有一個方法:
Object invoke(Object proxy,Method method,Object args[])
參數一:當前調用該方法的代理對象
參數二:代理對象調用的被代理對象的方法
參數三:調用被代理對象的方法所需要傳遞的參數
無論何時調用代理對象的方法,調用處理器的invoke方法都會被調用,並向其傳遞Method對象和原始的調用參數。調用處理器必須給出調用處理的方式。
3.如何使用代理
我們先概述一下流程,後面會有具體實例。
1.先了解下基礎知識
代理類可以在運行時創建全新的類,這樣的代理類能夠實現指定的接口。尤其是,它具有下列方法:
- 指定接口所需要的全部方法。
- Object類中的全部方法。
2.基本流程
1.創建被代理對象
2.創建與被代理對象相關聯的調用處理器
3.創建代理對象
4.代理對象調用被代理對象的方法
4、動態代理簡單實現
還是上面的例子,班長需要幫學生代交班費。
首先是定義一個Person接口:
public interface Person {
//上交班費
void giveMoney();
}
創建需要被代理的實際類:
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班費50元");
}
}
創建StuInvocationHandler類,實現InvocationHandler接口。這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行invoke方法。
在invoke方法中執行被代理對象target的相應方法。當然,在代理過程中,我們在真正執行被代理對象的方法前加入自己其他處理。這也是Spring中的AOP實現的主要原理,這裏還涉及到一個很重要的關於java反射方面的基礎知識。
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理對象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表動態代理對象
* method:代表正在執行的方法
* args:代表調用目標方法時傳入的實參
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行" +method.getName() + "方法");
return method.invoke(target,args);
}
}
做完上面的工作後,我們就可以具體來創建動態代理對象了,上面簡單介紹瞭如何創建動態代理對象,我們使用簡化的方式創建動態代理對象:
public class ProxyTest {
public static void main(String[] args) {
//創建一個實例對象,這個對象是被代理的對象
Person zhangsan = new Student("張三");
//創建一個與代理對象相關聯的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//創建一個代理對象stuProxy來代理zhangsan,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理執行上交班費的方法
stuProxy.giveMoney();
}
}
我們執行這個ProxtyTest類,先想一下,我們創建了一個需要被代理的學生張三,將zhangsan對象傳給了stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作爲參數了的,上面也有說到所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最後執行的是SuInvocationHandler中的invoke方法。所以在看到下面的運行結果也就理所當然了。
運行結果:
代理執行giveMoney()方法
張三上交班費50元
上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因爲所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。
動態代理的過程,代理對象和被代理對象的關係不像靜態代理那樣一目瞭然,清晰明瞭。因爲動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地的看到代理類的具體樣子,而且動態代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的,爲什麼代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行。帶着這些問題,我們就需要對java動態代理的源碼進行簡要的分析,弄清楚其中緣由。
5、動態代理原理分析
我們將代理類類文件反編譯,來看下源碼
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*
*super(paramInvocationHandler),是調用父類Proxy的構造方法。
*父類持有:protected InvocationHandler h;
*Proxy構造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//這個靜態塊本來是在最後的,我把它拿到前面來,方便描述
static
{
try
{
//看看這兒靜態塊兒裏面有什麼,是不是找到了giveMoney方法。請記住giveMoney通過反射得到的名字m3,其他的先不管
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
*
*這裏調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。
*this.h.invoke(this, m3, null);這裏簡單,明瞭。
*來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象,
*再聯繫到InvacationHandler中的invoke方法。嗯,就是這樣。
*/
public final void giveMoney()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意,這裏爲了節省篇幅,省去了toString,hashCode、equals方法的內容。原理和giveMoney方法一毛一樣。
}
爲什麼無論何時調用代理對象的方法,調用處理器的invoke方法都會被調用?
我們可以對InvocationHandler看做一箇中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉爲對被代理對象的調用。
代理類調用自己方法時,通過自身持有的中介類對象來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理通過中介類實現了具體的代理功能。
爲什麼創建代理類要傳接口?
1.代理類繼承了Proxy類,所以也就決定了java動態代理只能對接口進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理。
2.從代理類的類文件中我們可以看到,正是實現了接口,所以在最下方纔可以重寫被代理類的接口中所擁有的方法。而又是正因爲重寫了,所以纔可以直接用代理類調用被代理對象的方法。(也正是靜態代理中代理類需要繼承和被代理類相同接口的原因)
注:代理類只可以代理調用被代理類的接口所擁有的方法
5、代理類的特性
1.代理類是在程序運行過程中創建的,然而,一旦被創建,就變成了常規類,與虛擬機中的任何其他類沒有什麼區別。
2.一個代理類只有一個實例域——調用處理器,它定義在roxy的超類中。一個調用處理器只持有一個實例域,即被代理對象。
3.所有的代理類都擴展於Proxy類。爲了履行代理對象的職責,所需要的任何附加數據都必須存儲在調用處理器中。
4.所有的代理來都覆蓋了Object類中的方法toString、equals和hashCode。如同所有的代理方法一樣,這些方法僅僅調用了調用處理器的invoke。Object類中的其他方法(如clone和getClass)沒有被重新定義。
5.沒有定義代理類的名字,Sun虛擬機中的Proxy類將生成一個以字符串$Proxy開頭的類名。$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)
6.沒有特定的類加載器和預設的一組接口來說,只能有一個代理類。也就是說,如果使用同一個類加載器和接口數組調用兩次newProxyInstance方法的話,那麼只能夠得到同一個類的兩個對象。也可以利用getProxyClass方法獲得這個類
7.代理類一定是public和final。如果代理類實現的所有接口都是public,代理類就不屬於某個特定的包;否則,所有非公有的接口都必須屬於同一個包,同時,代理類也屬於這個包。
8.可以通過調用Proxy類中的isProxyClass方法檢測一個特定的Class對象是否代表一個代理類。
參考博客: