代理模式
意图:为其他对象提供一种代理以控制对这个对象的访问。
说白话就是在不侵入原来功能的基础上,附加一些功能。例如在执行方法前后打印日志啊,统计一下执行方法次数啊,一些异常处理啊..
1.什么是代理模式
我看来整个代理模式大同小异就是这重要的三者 1. 被代理类 2.代理类 3.需要被增强的方法继承的接口
这里用一个大学时老师讲的例子举例,少说废话上代码
先简单定义一个接口Animal,代表所有动物,其中run()方法作为被代理方法
我们定义一个小鸟继承Animal和run()方法
如果这就是我们正常的业务需求,创建小鸟然后执行run()方法就可以了。
突然有天加需求,需要记录小鸟的run()方法每天会执行多少次,如果不侵入原来代码逻辑去处理呢? 如果以后会有更多动物都需要记录run()调用次数如何处理?
我们创建了一个Animal的代理类 AnimalProxy,
通过构造方法传入被代理对象animal,
重写run()方法,执行animal原本的run()逻辑,同时加上附加功能,
最后执行printCount() 打印调用次数
没有侵入原来代码,完成了调用计数,还易于扩展(无论是Dog Cat Elephent 啥都能代理)
这tnd的其实就是静态代理
大学课上还问过老师为啥代理类一定要继承Animal接口,不继承不是也完全可以实现的吗。 现在想想设计者大概是为了是统一管理,代理前代理后都是run()方法,这显得很专业!
2.静态代理与动态代理
上一节详细介绍了静态代理的实现,但是缺点也很明显,
1 根据这种形式,被代理的方法一定要继承于接口实现,并且代理类一定要继承该接口。 AnimalProxy如果不继承Animal接口那就没得玩了
2 这次需求我们做各种动物的方法计数,下次需求我们做飞机大炮的方法调用计数怎么办? 你说你改改代理类的构造入参,改改接口继承关系还能接着玩,那下下次呢? 下下下次? 一直改代码肯定自闭啊
3 第三点和上一点差不多,如果后续这个run()方法入参变化了,返回值变了等等,被代理类 代理类都需要反复修改,肯定难受
有没有方便灵活的办法解决这些问题? 有,动态代理
静态代理动态代理我认为最大的区别在于代理类的生成
静态代理属于编译之前,我手动把代理类写好,然后编译成.class文件执行
动态代理属于运行时,利用反射机制,动态的生成代理类
3.JDK动态代理
- 动态代理类:在程序运行时,通过反射机制动态生成。
- 动态代理类通常代理接口下的所有类。静态一般指定某个类代理。
- 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。静态是编译期确定的。
先上demo
在之前的例子上,Animal接口 与Bird类均不动,只在AnimalProxy做文章
AnimalProxy继承于InvocationHandler,
重写invoke()方法,用来做增强附加功能的具体实现,
定义了一个Object任意被代理target,
定义bind()方法,用来绑定被代理的对象。
结果和静态代理相同,只是这里的角色有些改变
被代理类和接口没变,多了一个工具人的身份,
工具人负责写好增强功能效果,绑定好被代理类,动态的创建出一个代理类来
问几个问题,bind()中传入的对象应该具有什么条件?
为什么传入了一个对象,就能直到为哪个方法实现了增强? 并且能动态的生成继承了同一个接口的代理对象?
源码之下无秘密,一切就藏在 Proxy.newProxyInstance()源码中(玩转个屁)
。。。未完待续
4.cglib动态代理
- JDK 动态代理只能只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
5.Spring AOP中动态代理
Spring代理其实是对JDK动态代理和CGLIB代理进行了封装,并且引入了AOP的概念,同时引入了AspectJ中的一些注解:@pointCut @After 等。