代理在我們?nèi)粘i_發(fā)中是一個(gè)很常見的知識(shí)點(diǎn)败许,也是我們面試中經(jīng)常被問到的內(nèi)容脐区,本本博文帶大家來學(xué)習(xí)和分析下代理的相關(guān)內(nèi)容。
1. 概念
代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式;即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.
這里使用到編程中的一個(gè)思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴(kuò)展該方法。
舉個(gè)例子來說明代理的作用:假設(shè)我們想邀請(qǐng)一位明星,那么并不是直接連接明星,而是聯(lián)系明星的經(jīng)紀(jì)人,來達(dá)到同樣的目的.明星就是一個(gè)目標(biāo)對(duì)象,他只要負(fù)責(zé)活動(dòng)中的節(jié)目,而其他瑣碎的事情就交給他的代理人(經(jīng)紀(jì)人)來解決.這就是代理思想在現(xiàn)實(shí)中的一個(gè)例子.
其中的明星我們一般稱為委托類葬燎,或者稱之為被代理類眯分;而明星的經(jīng)紀(jì)人我們稱之為代理類拌汇。
代理的優(yōu)點(diǎn):
- 隱藏委托類的實(shí)現(xiàn)。
- 解耦弊决,在不改委托類的實(shí)現(xiàn)下添加一些額外操作噪舀。
2. 分類
根據(jù)運(yùn)行前委托類是否存在魁淳,我們將代理分為兩類:
- 靜態(tài)代理
- 動(dòng)態(tài)代理
2.1 靜態(tài)代理
代理類在程序運(yùn)行前已經(jīng)存在的代理方式稱之為靜態(tài)代理。
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類.
2.1.1 實(shí)例
/**
* 接口
*/
public interface IUserDao {
void save();
}
/**
* 接口實(shí)現(xiàn)
* 目標(biāo)對(duì)象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
}
}
/**
* 代理對(duì)象,靜態(tài)代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目標(biāo)對(duì)象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("開始事務(wù)...");
target.save();//執(zhí)行目標(biāo)對(duì)象的方法
System.out.println("提交事務(wù)...");
}
}
優(yōu)缺點(diǎn):
1.可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展.
2.缺點(diǎn):
- 因?yàn)榇韺?duì)象需要與被代理對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多.同時(shí),一旦接口增加方法,被代理對(duì)象與代理對(duì)象都要維護(hù)与倡。
那么有沒有什么方法界逛,可以解決靜態(tài)代理的缺點(diǎn)呢?有纺座,動(dòng)態(tài)代理息拜。
2.2 動(dòng)態(tài)代理
代理類在程序運(yùn)行前不存在、運(yùn)行時(shí)由程序動(dòng)態(tài)生成的代理方式稱為動(dòng)態(tài)代理净响。
Java 提供了動(dòng)態(tài)代理的實(shí)現(xiàn)方式少欺,可以在運(yùn)行時(shí)刻動(dòng)態(tài)生成代理類。
這種代理方式的一大好處是可以方便對(duì)代理類的函數(shù)做統(tǒng)一或特殊處理馋贤,如記錄所有函數(shù)執(zhí)行時(shí)間赞别、所有函數(shù)執(zhí)行前添加驗(yàn)證判斷、對(duì)某個(gè)特殊函數(shù)進(jìn)行特殊操作掸掸,而不用像靜態(tài)代理方式那樣需要修改每個(gè)函數(shù)氯庆。
2.2.1 實(shí)現(xiàn)
- 新建委托類
- 實(shí)現(xiàn)InvocationHandler,這是負(fù)責(zé)代理類和委托類的中間類必須實(shí)現(xiàn)的接口扰付。
- 通過Proxy類實(shí)現(xiàn)新建代理類對(duì)象
- 新建委托類
public interface Operate {
public void operateMethod1();
public void operateMethod2();
public void operateMethod3();
}
public class OperateImpl implements Operate {
@Override
public void operateMethod1() {
System.out.println("Invoke operateMethod1");
sleep(110);
}
@Override
public void operateMethod2() {
System.out.println("Invoke operateMethod2");
sleep(120);
}
@Override
public void operateMethod3() {
System.out.println("Invoke operateMethod3");
sleep(130);
}
private static void sleep(long millSeconds) {
try {
Thread.sleep(millSeconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 實(shí)現(xiàn)InvocationHandler
public class TimingInvocationHandler implements InvocationHandler {
private Object target;
public TimingInvocationHandler() {}
public TimingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object obj = method.invoke(target, args);
System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
return obj;
}
}
- target屬性表示委托類對(duì)象堤撵。
- InvocationHandler是負(fù)責(zé)連接代理類和委托類的中間類必須實(shí)現(xiàn)的接口。其中只有一個(gè)
public Object invoke(Object proxy, Method method, Object[] args)
函數(shù)需要去實(shí)現(xiàn)羽莺,參數(shù):
- proxy表示下面通過 Proxy.newProxyInstance() 生成的代理類對(duì)象实昨。
- method表示代理對(duì)象被調(diào)用的函數(shù)。
- args表示代理對(duì)象被調(diào)用的函數(shù)的參數(shù)盐固。
調(diào)用代理對(duì)象的每個(gè)函數(shù)實(shí)際最終都是調(diào)用了InvocationHandler的invoke函數(shù)荒给。這里我們?cè)趇nvoke實(shí)現(xiàn)中添加了開始結(jié)束計(jì)時(shí),其中還調(diào)用了委托類對(duì)象target的相應(yīng)函數(shù)刁卜,這樣便完成了統(tǒng)計(jì)執(zhí)行時(shí)間的需求志电。
invoke函數(shù)中我們也可以通過對(duì)method做一些判斷,從而對(duì)某些函數(shù)特殊處理蛔趴。
- 生成代理對(duì)象
public class Main {
public static void main(String[] args) {
// create proxy instance
TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
timingInvocationHandler));
// call method of proxy instance
operate.operateMethod1();
System.out.println();
operate.operateMethod2();
System.out.println();
operate.operateMethod3();
}
}
- 這里我們先將委托類對(duì)象new OperateImpl()作為TimingInvocationHandler構(gòu)造函數(shù)入?yún)?chuàng)建timingInvocationHandler對(duì)象挑辆;
- 然后通過Proxy.newProxyInstance(…)函數(shù)新建了一個(gè)代理對(duì)象,實(shí)際代理類就是在這時(shí)候動(dòng)態(tài)生成的孝情。我們調(diào)用該代理對(duì)象的函數(shù)就會(huì)調(diào)用到timingInvocationHandler的invoke函數(shù)(是不是有點(diǎn)類似靜態(tài)代理)鱼蝉,而invoke函數(shù)實(shí)現(xiàn)中調(diào)用委托類對(duì)象new OperateImpl()相應(yīng)的 method(是不是有點(diǎn)類似靜態(tài)代理)。
2.2.2 newProxyInstance
下面我們來分下Proxy.newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- loader表示類加載器
- interfaces表示委托類的接口箫荡,生成代理類時(shí)需要實(shí)現(xiàn)這些接口
- h是InvocationHandler實(shí)現(xiàn)類對(duì)象魁亦,負(fù)責(zé)連接代理類和委托類的中間類
我們可以這樣理解,如上的動(dòng)態(tài)代理實(shí)現(xiàn)實(shí)際是雙層的靜態(tài)代理羔挡,開發(fā)者提供了委托類 B洁奈,程序動(dòng)態(tài)生成了代理類 A间唉。開發(fā)者還需要提供一個(gè)實(shí)現(xiàn)了InvocationHandler的子類 C,子類 C 連接代理類 A 和委托類 B睬魂,它是代理類 A 的委托類终吼,委托類 B 的代理類。用戶直接調(diào)用代理類 A 的對(duì)象氯哮,A 將調(diào)用轉(zhuǎn)發(fā)給委托類 C,委托類 C 再將調(diào)用轉(zhuǎn)發(fā)給它的委托類 B商佛。
3. 動(dòng)態(tài)代理原理
3.1 生成的動(dòng)態(tài)代理類代碼
下面是上面示例程序運(yùn)行時(shí)自動(dòng)生成的動(dòng)態(tài)代理類代碼喉钢。
public final class $Proxy0 extends Proxy
implements Operate
{
private static Method m4;
private static Method m1;
private static Method m5;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final void operateMethod1()
throws
{
try
{
h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod2()
throws
{
try
{
h.invoke(this, m5, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod3()
throws
{
try
{
h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
從中我們可以看出動(dòng)態(tài)生成的代理類是以$Proxy為類名前綴,繼承自Proxy良姆,并且實(shí)現(xiàn)了Proxy.newProxyInstance(…)第二個(gè)參數(shù)傳入的所有接口的類肠虽。
如果代理類實(shí)現(xiàn)的接口中存在非 public 接口,則其包名為該接口的包名玛追,否則為com.sun.proxy税课。
其中的operateMethod1()、operateMethod2()痊剖、operateMethod3()函數(shù)都是直接交給h去處理韩玩,h在父類Proxy中定義為
protected InvocationHandler h;
即為Proxy.newProxyInstance(…)第三個(gè)參數(shù)。
所以InvocationHandler的子類 C 連接代理類 A 和委托類 B陆馁,它是代理類 A 的委托類找颓,委托類 B 的代理類。
3.2 生成動(dòng)態(tài)代理類原理
- newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
return newInstance(cons, h);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}
從上面的代碼我們可以看到叮贩,調(diào)用了getProxyClass0得到動(dòng)態(tài)代理類击狮,然后將h傳入了動(dòng)態(tài)代理類。
- getProxyClass0
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 判斷委托類的接口數(shù)量益老,超載直接拋出異常
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 聲明代理類對(duì)象
Class<?> proxyClass = null;
/* collect interface names to use as key for proxy class cache */
String[] interfaceNames = new String[interfaces.length];
// for detecting duplicates
Set<Class<?>> interfaceSet = new HashSet<>();
/**
* 入?yún)?interfaces 檢驗(yàn)彪蓬,包含三部分
* (1)是否在入?yún)⒅付ǖ?ClassLoader 內(nèi)
* (2)是否是 Interface
* (3)interfaces 中是否有重復(fù)
*/
for (int i = 0; i < interfaces.length; i++) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
/*
* Using string representations of the proxy interfaces as
* keys in the proxy class cache (instead of their Class
* objects) is sufficient because we require the proxy
* interfaces to be resolvable by name through the supplied
* class loader, and it has the advantage that using a string
* representation of a class makes for an implicit weak
* reference to the class.
*/
// 以接口名對(duì)應(yīng)的 List 作為緩存的 key
List<String> key = Arrays.asList(interfaceNames);
/*
* Find or create the proxy class cache for the class loader.
*/
/*
* loaderToCache 是個(gè)雙層的 Map
* 第一層 key 為 ClassLoader,第二層 key 為 上面的 List捺萌,value 為代理類的弱引用
*/
Map<List<String>, Object> cache;
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
/*
* This mapping will remain valid for the duration of this
* method, without further synchronization, because the mapping
* will only be removed if the class loader becomes unreachable.
*/
}
/*
* Look up the list of interfaces in the proxy class cache using
* the key. This lookup will result in one of three possible
* kinds of values:
* null, if there is currently no proxy class for the list of
* interfaces in the class loader,
* the pendingGenerationMarker object, if a proxy class for the
* list of interfaces is currently being generated,
* or a weak reference to a Class object, if a proxy class for
* the list of interfaces has already been generated.
*/
/*
* 以上面的接口名對(duì)應(yīng)的 List 為 key 查找代理類档冬,如果結(jié)果為:
* (1) 弱引用,表示代理類已經(jīng)在緩存中
* (2) pendingGenerationMarker 對(duì)象互婿,表示代理類正在生成中捣郊,等待生成完成通知。
* (3) null 表示不在緩存中且沒有開始生成慈参,添加標(biāo)記到緩存中呛牲,繼續(xù)生成代理類
*/
synchronized (cache) {
/*
* Note that we need not worry about reaping the cache for
* entries with cleared weak references because if a proxy class
* has been garbage collected, its class loader will have been
* garbage collected as well, so the entire cache will be reaped
* from the loaderToCache map.
*/
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
/*
* The class generation that we are waiting for should
* take a small, bounded time, so we can safely ignore
* thread interrupts here.
*/
}
continue;
} else {
/*
* No proxy class for this list of interfaces has been
* generated or is being generated, so we will go and
* generate it now. Mark it as pending generation.
*/
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
String proxyPkg = null; // package to define proxy class in
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
/*
* 如果 interfaces 中存在非 public 的接口,則所有非 public 接口必須在同一包下面驮配,后續(xù)生成的代理類也會(huì)在該包下面
*/
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use the default package.
proxyPkg = "";
}
{
// Android-changed: Generate the proxy directly instead of calling
// through to ProxyGenerator.
List<Method> methods = getMethods(interfaces);
Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
validateReturnTypes(methods);
List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
Method[] methodsArray = methods.toArray(new Method[methods.size()]);
Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
/*
* Choose a name for the proxy class to generate.
*/
final long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 動(dòng)態(tài)生成代理類的字節(jié)碼
// 最終調(diào)用 sun.misc.ProxyGenerator.generateClassFile() 得到代理類相關(guān)信息寫入 DataOutputStream 實(shí)現(xiàn)
proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
exceptionsArray);
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
} finally {
/*
* We must clean up the "pending generation" state of the proxy
* class cache entry somehow. If a proxy class was successfully
* generated, store it in the cache (with a weak reference);
* otherwise, remove the reserved entry. In all cases, notify
* all waiters on reserved entries in this cache.
*/
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
4. CGLib實(shí)現(xiàn)代理
4.1 與JDK代理區(qū)別
JDK動(dòng)態(tài)代理和CGLIB字節(jié)碼生成的區(qū)別娘扩?
(1)JDK動(dòng)態(tài)代理只能對(duì)實(shí)現(xiàn)了接口的類生成代理着茸,而不能針對(duì)類
(2)CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類琐旁,覆蓋其中的方法
因?yàn)槭抢^承涮阔,所以該類或方法最好不要聲明成final
4.2 對(duì)比
JDK代理是不需要依賴第三方的庫,只要JDK環(huán)境就可以進(jìn)行代理灰殴,它有幾個(gè)要求
- 實(shí)現(xiàn)InvocationHandler
- 使用Proxy.newProxyInstance產(chǎn)生代理對(duì)象
- 被代理的對(duì)象必須要實(shí)現(xiàn)接口
使用JDK動(dòng)態(tài)代理敬特,目標(biāo)類必須實(shí)現(xiàn)的某個(gè)接口,如果某個(gè)類沒有實(shí)現(xiàn)接口則不能生成代理對(duì)象牺陶。
CGLib 必須依賴于CGLib的類庫伟阔,Cglib原理是針對(duì)目標(biāo)類生成一個(gè)子類,覆蓋其中的所有方法掰伸,所以目標(biāo)類和方法不能聲明為final類型皱炉。針對(duì)接口編程的環(huán)境下推薦使用JDK的代理。從執(zhí)行效率上看狮鸭,Cglib動(dòng)態(tài)代理效率較高合搅。在Hibernate中的攔截器其實(shí)現(xiàn)考慮到不需要其他接口的條件Hibernate中的相關(guān)代理采用的是CGLib來執(zhí)行。
4.3 CGLib實(shí)例
實(shí)例:
/**
* CGLibProxy動(dòng)態(tài)代理類的實(shí)例
*
*
*/
public class CGLibProxy implements MethodInterceptor {
private Object targetObject;// CGLib需要代理的目標(biāo)對(duì)象
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;// 返回代理對(duì)象
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object obj = null;
if ("addUser".equals(method.getName())) {// 過濾方法
checkPopedom();// 檢查權(quán)限
}
obj = method.invoke(targetObject, args);
return obj;
}
private void checkPopedom() {
System.out.println(".:檢查權(quán)限 checkPopedom()!");
}
}
// 調(diào)用
UserManager userManager = (UserManager) new CGLibProxy()
.createProxyObject(new UserManagerImpl());
5. 結(jié)束語
詳細(xì)大家通過上面的學(xué)習(xí)歧蕉,已經(jīng)對(duì)代理有了一個(gè)更深層次的認(rèn)識(shí)灾部,代理在AOP開發(fā)中特別有用,讓我們?cè)谠O(shè)計(jì)和開發(fā)中把代理用起來吧廊谓。