Java靜態代理和動態代理
一、概述
代理是一種模式,提供了對目標對象的間接訪問方式,即通過代理訪問目標對象。如此便於在目標實現的基礎上增加額外的功能操作,前攔截,後攔截等,以滿足自身的業務需求,同時代理模式便於擴展目標對象功能的特點也爲多人所用。
二、圖形描述
三、靜態代理
靜態代理的實現比較簡單,代理類通過實現與目標對象相同的接口,並在類中維護一個代理對象。通過構造器塞入目標對象,賦值給代理對象,進而執行代理對象實現的接口方法,並實現前攔截,後攔截等所需的業務功能。
/**
* 目標對象實現的接口
* @author jiyukai
*/
public interface BussinessInterface {
void execute();
}
/**
* 目標對象實現類
* @author jiyukai
*/
public class Bussiness implements BussinessInterface{
@Override
public void execute() {
System.out.println("執行業務邏輯...");
}
}
/**
* 代理類,通過實現與目標對象相同的接口
* 並維護一個代理對象,通過構造器傳入實際目標對象並賦值
* 執行代理對象實現的接口方法,實現對目標對象實現的干預
* @author jiyukai
*/
public class BussinessProxy implements BussinessInterface{
private BussinessInterface bussinessImpl;
public BussinessProxy(BussinessInterface bussinessImpl) {
this.bussinessImpl = bussinessImpl;
}
@Override
public void execute() {
System.out.println("前攔截...");
bussinessImpl.execute();
System.out.println("後攔截...");
}
}
靜態代理的總結
優點:可以做到不對目標對象進行修改的前提下,對目標對象進行功能的擴展和攔截。
缺點:因爲代理對象,需要實現與目標對象一樣的接口,會導致代理類十分繁多,不易維護,同時一旦接口增加方法,則目標對象和代理類都需要維護。
四、動態代理
動態代理是指動態的在內存中構建代理對象(需要我們制定要代理的目標對象實現的接口類型)。
- 動態代理的分類:基於接口的動態代理(JDK官方)和基於子類的動態代理(cglib)
- 具體演示
(1)基於接口的動態代理
package com.renjing.proxy;
/**
* 此爲被代理類的接口
* 一個經紀公司的要求:
* 能做基本的表演和危險的表演
*/
public interface IActor {
/**
* 基本的演出
*
*/
public void basicAct(float money);
/**
* 危險演出
*/
public void dangerAct(float money);
}
package com.renjing.proxy;
/**
* 此爲實際的被代理類
*/
public class Actor implements IActor {
/**
* 基本演出
* @param money
*/
@Override
public void basicAct(float money) {
System.out.println("拿到錢,開始基本的表演:"+money);
}
/**
* 危險演出
* @param money
*/
@Override
public void dangerAct(float money) {
System.out.println("拿到錢,開始危險的表演:"+money);
}
}
package com.renjing.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//一個劇組找演員
Actor actor = new Actor();//直接
/**
* 代理:間接。
* 獲取代理對象:
* 要求:被代理類最少實現一個接口。但是代理類無需實現任何接口。
* 創建的方式:Proxy.newProxyInstance(三個參數)
* 參數含義:
* ClassLoader:和被代理對象使用相同的類加載器。
* Interfaces:和被代理對象具有相同的行爲。實現相同的接口。
* InvocationHandler:如何代理。
* 策略模式:使用場景是:
* 數據有了,目的明確。
* 達成目標的過程,就是策略。
*/
IActor proxyActor = (IActor) Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 執行被代理對象的任何方法,都會經過該方法。
* 此方法有攔截的功能。
* 參數:
* proxy:代理對象的引用。不一定每次都用得到。
* method:當前執行的方法對象。
* args:執行方法所需的參數。
* 返回值:
* 當前執行方法的返回值。
*/
String name = method.getName();
Float money = (Float)args[0];
Object rtValue = null;
//每個經紀公司對不同演出收費不一樣,此處開始判斷
if ("basicAct".equals(name)) {
//基本演出
if (money > 2000) {
//看上去劇組是給了 8000,實際到演員手裏只有 4000
//這就是我們沒有修改原來 basicAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money/2);
}
}
if ("dangerAct".equals(name)) {
//基本演出
if (money > 5000) {
//看上去898劇組是給了 8000,實際到演員手裏只有 4000
//這就是我們沒有修改原來 basicAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
}
});
//沒有經濟公司的時候,直接找演員。
actor.basicAct(100f);
actor.dangerAct(500f);
//劇組無法直接聯繫演員,而是由經紀公司找的演員
proxyActor.basicAct(8000f);
proxyActor.dangerAct(50000f);
}
}
(2)基於子類的動態代理
package com.renjing.cglib;
/**
* 此爲被代理類:不能爲最終類
*/
public class Actor {
/**
* 基本演出
* @param money
*/
public void basicAct(float money) {
System.out.println("CGLIB拿到錢,開始基本的表演:"+money);
}
/**
* 危險演出
* @param money
*/
public void dangerAct(float money) {
System.out.println("CGLIB拿到錢,開始危險的表演:"+money);
}
}
package com.renjing.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
//一個劇組找演員
final Actor actor = new Actor();//直接(注意在匿名內部類中只能引用final類型的外部變量)
/**
* 基於子類的動態代理
* 要求:被代理的對象不能是最終類。
* 用到的類:Enhancer
* 用到的方法:create(Class,Callback)
* 方法的參數:
* Class:被代理對象的字節碼。
* Callback:如何代理。
*/
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() {
/**
* 執行被代理對象的任何方法,都會經過該方法。在此方法內部就可以對被代理對象的任何方法進行增強。
*
* 參數:
* 前三個和基於接口的動態代理是一樣的。
* MethodProxy:當前執行方法的代理對象。
* 返回值:當前執行方法的返回值。
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
//每個經紀公司對不同演出收費不一樣,此處開始判斷
if ("basicAct".equals(name)) {
//基本演出
if (money > 2000) {
//看上去劇組是給了 8000,實際到演員手裏只有 4000
//這就是我們沒有修改原來 basicAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money / 2);
}
}
if ("dangerAct".equals(name)) {
//基本演出
if (money > 5000) {
//看上去898劇組是給了 8000,實際到演員手裏只有 4000
//這就是我們沒有修改原來 basicAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money / 2);
}
}
return rtValue;
}
});
cglibActor.basicAct(10000);
cglibActor.dangerAct(100000);
}
}
動態代理的總結
優點:代理對象無需實現接口,免去了編寫很多代理類的煩惱,同時接口增加方法也無需再維護目標對象和代理對象,只需在事件處理器中添加對方法的判斷即可。
缺點:代理對象不需要實現接口,但是目標對象一定要實現接口,否則無法使用JDK動態代理。