0%

java动态代理

卷首语

最遥不可及的不是十年后,而是今天之前。

今天要说的内容从前本人便已经坐过文章,而且,那时候动态代理还是本人第一次在网上发的博客。时隔三年,又来炒冷饭。成长了也没有成长。长大的过程就是这样曲折蜿蜒,总让人有种不真实的感觉,坐在同一个房间,写着同样的内容,浮现的回忆却不一样。时间真是不可思议的东西。

那么,今天就想从前那样,从头认识一下这个熟悉的陌生人——动态代理。不过要了解动态代理,最好还是把代理模式给弄清楚。

静态代理模式

一来就开始怼概念,那和别人的文章又有何别?我喜欢讲故事,所以今天先讲个故事。话说王二是个王老五,至于是不是钻石级别的,那咱就不得而知了。不过,他这人生来就有个毛病,总觉得这世上每个女人都喜欢他,也许就是因为这个毛病,没几个女人想和他说话。就好像走路上时遇到的飞虫,叨扰你却总觉得是因为你闯进了它的领地造成的。过了而立之年,王二终于发现没人陪是那么一点寂寞。但自己的名声在这个镇子上早臭了。可好巧不巧,他就在镇上遇到了心动的女子。碍于自己的臭名声,他不敢接近她。没法,他向了无大仙求助。大仙看在他平日里还是会偷偷给自己送几只烧鸡的份上,也就答应帮他一把,他让王二留下了一缕头发,叫王二三天后来取宝物。

三日过后,王二屁颠屁颠跑到大师那里,顺带又带了两只烧鸡。等大师风卷残云之后,心满意足的从口袋里拿出一个头套。说着,“拿去吧,在上街之前,对着头套把你想要他帮你改掉的毛病对他说了,再把它带到头上就可以了”。王二谢过大师之后,就戴着头套跑回了家。到家后,他感到装着的头套和自己的心跳保持着同样的频率。他小心翼翼取出头套。在心里默念:“帮我在那个女人面前表现得诚恳一些,不要说一些自恋的话。”之间头套轻轻动了下,就慢慢飘到他头上去了,就好像答应他似的。每次遇到那些女人,王二都会通过头套组织好语言,和她们寒暄几句。镇上的人也发现了王二的转变,王二心仪的女子也慢慢王二热络起来。一切都朝着美好的方向发展着。

故事说了一半,但这一部分已经足够去重新阐述和了解代理模式是什么?在理解代理模式之前,我们先确定几件事,委托者,代理者,需求接口。那么,故事里的这些意象又是指的什么呢?首先,是我们的主角王二,他充当的角色是委托者,他想要让头套帮助自己润色语言。其次,便是头套,这个意象其实就是所说的代理者,由它去代替王二润色语言,把润色后的语言告诉王二,让王二把话说出来就好了。最后,便是王二向头套说自己需求的过程,这个过程抽象后,其实就是需求接口。王二和头套的关联在于王二把需求告知了头套,而代理模式则是让委托者和代理者都实现了需求接口,从而达到了代理者能够在委托者执行响应操作前进行一些特定的业务逻辑,同时,这样的操作又不会对原有的委托者对象代码有任何的影响。

那么把这一段故事用代码表示就可以是这样的:

王二告知头套想要他帮忙做的事:

1
2
3
4
5
6
7
public interface IWhatWangWannaDo {
/**
* 说话
* @param word 说的内容
*/
void sayWord(String word);
}

王二这个人:

1
2
3
4
5
6
public class Wang implements IWhatWangWannaDo {
@Override
public void sayWord(String word) {
System.out.println(String.format("王二说:%s", word));
}
}

头套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HeadGearProxy implements IWhatWangWannaDo {
private Wang wang;

// 可以理解为戴在了王二的头上
// 以便于控制王二
public HeadGearProxy(Wang wang) {
this.wang = wang;
}

@Override
public void sayWord(String word) {
String hornedWord = hornWord(word);
wang.sayWord(hornedWord);
}

/**
* 用于对王二的话进行润色
* @param word 王二要说的话
* @return 润色后的话
*/
public String hornWord(String word){
if (!word.equals("你一定是喜欢我吧?"))
return "今晚月色真美";
else
return "明天见";
}
}

当我们开始故事之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Story {
public static void main(String[] args) {
// 从前有个人叫王二
Wang wang = new Wang();

// 王二带上了头套
HeadGearProxy headGearProxy = new HeadGearProxy(wang);

// 旁白
System.out.println("王二想说:你怕是喜欢我吧?");

// 头套帮王二对话语进行了润色
// 但真正说话的人还是王二
headGearProxy.sayWord("你怕是喜欢我吧?");
}
}

通过代码运行,我们可以看到:

1
2
王二想说:你怕是喜欢我吧?
王二说:今晚月色真美

从代码里,我们也在此看到了头套只是一个处理语言的代理角色,真正说话的人仍然是王二。这也就是代理模式的本质。代理者在委托人执行真实操作之前货或之后进行其他与委托人代码逻辑不相关的一些操作,同时,执行这些操作时,并不影响委托人原本的代码情况

动态代理模式

现在我们了解了静态代理模式,那么进一步了解动态代理模式也就水到渠成。不过,现在还不能直接说动态代理,因为王二的故事还要继续。

话说,在王二追求到心仪女子之后,他的胃口也越来越到,并不仅限于搭讪,坑蒙拐骗的计俩,他都于要求头套帮他完成。而头套似乎不堪重负,再也没有听完他的需求之后,飘到他的脑袋上了。这可急坏了王二,他赶紧跑到了无大师那里,问他怎么回事。大师看了看头套,笑着对他说,“心仪之人已入怀中,又何必再求其施舍?”。王二急忙奉上烤鸡,外加五十两的银票。大师不紧不慢的把烤鸡吃了,却把银票还给了王二。大师从兜里掏出了一张人皮,对王二说道,“他可以满足你的一切需要,你穿上他,他会知道你的想法,直接替你完成,不用你再和他交代什么了,只是不要被他引往歧途。”王二谢过到大师,直接把人皮套在了自己身上,从那之后,王二通过坑蒙拐骗,夺得腰缠万贯。

故事有挤牙膏一样挤出来一小段,但这一小段就足以把动态代理理解了。动态代理相比于静态代理,其实只是实现代理的方式不同,静态代理之所以静态,就是代理者本身需要对需求接口进行实现,而动态代理,则不然,他将整个需求接口反射为类,并将委托人对象传入代理类,通过反射的原理获取到真实执行委托人方法的对象。动态代理主要的几个关键点是:委托者、代理者、需求接口、反射调用方法的接口。首先,王二仍然是委托者,其次,人皮则为代理者,其三,王二的所想要做的事则仍然是需求接口,最后,人皮通过读取王二想法的过程可以理解为动态代理接口InvocationHandler通过反射调用需求方法的过程。

好了,同样的,我们还是用代码再来演绎一遍这个故事吧。

王二心中想做的事:

1
2
3
4
5
6
7
public interface IWhatWangWannaDo {
/**
* 说话
* @param word 说的内容
*/
void sayWord(String word);
}

王二这个人:

1
2
3
4
5
6
public class Wang implements IWhatWangWannaDo {
@Override
public void sayWord(String word) {
System.out.println(String.format("王二说:%s", word));
}
}

人皮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SkinProxy implements InvocationHandler {
private Object object;

// 可以理解为把人皮穿在了王二身上
public SkinProxy(Object object) {
this.object = object;
}

/**
* 用于对王二的话进行润色
* @param word 王二要说的话
* @return 润色后的话
*/
public String hornWord(String word){
if (word.equals("其实这个一文不值"))
return "童叟无欺,价值千金百两";
else
return "最低五十两";
}

@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
objects[0] = hornWord((String) objects[0]);
return method.invoke(object, objects);
}
}

当我们开始故事之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Story {
public static void main(String[] args) {
// 穿上人皮
SkinProxy proxyIpProcessor = new SkinProxy(new Wang());

// 人皮读取了王二的思想
IWhatWangWannaDo whatWangWannaDo = (IWhatWangWannaDo) Proxy.newProxyInstance(IWhatWangWannaDo.class.getClassLoader(), new Class[] {IWhatWangWannaDo.class}, proxyIpProcessor);

// 执行王二的需求最终说话的人仍然是王二
System.out.println("王二想说:其实这个一文不值");
whatWangWannaDo.sayWord("其实这个一文不值");
}
}

通过运行代码,我们可以看到:

1
2
王二想说:其实这个一文不值
王二说:童叟无欺,价值千金百两

经过润色后的话语,总是令人信服却又多了许多的虚伪。通过代码,我们可以从实现的接口来区分动态代理和静态代理,动态代理代理类实现的是Java反射包下的InvocationHandler接口,而静态代理则是普通的需求接口。正是因为这两个接口的思想不同,所以,使得动态代理能够在运行时再去通过反射获取真实执行委托类的方法,并在invoke()方法里进行调用。从故事里我们也可以得知,因为人皮这个代理者并不需要去主动听王二讲需求(不用实现需求接口),而是通过读取王二的思想了解需求,所以人皮其实也是可以被其他人所使用的,其方法和过程同王二相同。动态代理的作用也是如此,当有多个委托者类,同时都有相同的需求接口时,可以通过同一个代理类对其进行动态调用,而不需要重复实现多个代理类区分别代理他们。这样大大简化了开发流程,同样,在Spring框架中也能够发光发热。如果真的理解了动态代理,那么对于理解依赖注入(IoC)/控制反转(DI)这两个概念也会更容易。

动态代理的作用

动态代理在我们真实开发环境中其实应用不会太多,而对于框架的设计与开发则十分重要,在这里由于本人技术的限制就简单把网上冲浪来的几种用途进行总结吧。

  • 添加日志
  • 权限控制
  • 加验证
  • Spring AOP核心思想

对于开发和实际应用中是如何进行动态代理使用的,主要分为两种途径。

  • Java反射包中的InvocationHandler接口。这种使用方式主要针对委托对象拥有需求接口,这样反射才能够正常的被使用。
  • CGLib库。这种方式应用于委托类并没有实现需求接口,而代理类是通过CGLib在底层实现了类的继承从而进行动态代理,这个在Spring中应用广泛。

这两者的效率在高版本中已经相差甚小,低版本中CGLib占有绝对的优势,不过这两者应用的场景不同,所以,不能单从效率这个角度出发去分析。

尾声

关于动态代理的总结到这里也就结束了,通篇来看,对于动态代理的认识相比三年前有所提升,但仍有许多不足,经验和学习都还有所欠缺。希望三年之后,又会有全新的收获。

且慢,对于虽然技术是讲完了,但是我们的故事还没有结尾。王二究竟会有何种结局?结局的内容和我们今天所讨论的技术并无关联,现在就让我们来看看……王二自从穿上了人皮之后,从一个普通百姓变成了富甲一方的商人。王二本以为抱得美人归加之腰缠万贯就是他后半生的宿命。可谁知,他醉生梦死的后一天造成,人皮竟然自己有了血肉之躯,可以直接拿王二声音说话,由王二的所有特点。等王二发现时,他自己已经不能说话,而且脸部被人皮腐蚀得面目全非。他很惊恐,四处寻找着人皮,终于,在自己的安乐窝看到了人皮抱着风尘女子快活的模样,他想要进去把人皮撕碎,可被自己的家丁当做是乞丐赶到了大街上。他失魂落魄的走着,无意识间竟然走到了大师的府上,大师似乎未卜先知,早已在门口等候。他说不出话,只能不停朝大师比划。大师只是对他轻蔑的笑了笑,手一挥,人皮就回到了大师的袋中。王二以为事情就此结束了,但从镜子中看到,自己的脸仍然没有任何的变化,同样也不能说话。但他仍然忘不了自己当初的爱人,以杂役的身份重新回到了自己的府邸,每天都可以在清晨瞥到爱人打开窗户,那样的芳容仍然令他留恋。府上的人都以为这个府邸真正的主人另有其人,所以对于王二的消失只口不提。王二的爱人也就像王二从未出现过一般,从未谈及过王二的消失。每天结束工作的王二躺在床上,在昏昏欲睡时,他仿佛从镜中看到了另一个人,他在想自己究竟是不是王二,又或许自己其实才是人皮,而真正的自己早已落入了大师的口袋?

-------- 本文结束 感谢阅读 --------