代理模式
代理 (Proxy) 是一种设计模式, 提供了对目标对象另外的访问方式; 即通过代理对象访问目标对象. 这样做的好处是: 可以在目标对象实现的基础上, 增强额外的功能操作, 即扩展目标对象的功能.
这里使用到编程中的一个思想: 不要随意去修改别人已经写好的代码或者方法, 如果需改修改, 可以通过代理的方式来扩展该方法.
举个例子来说明代理的作用: 假设我们想邀请一位明星, 那么并不是直接连接明星, 而是联系明星的经纪人, 来达到同样的目的. 明星就是一个目标对象, 他只要负责活动中的节目, 而其他琐碎的事情就交给他的代理人 (经纪人) 来解决. 这就是代理思想在现实中的一个例子.
用图表示如下:
代理模式的关键点是: 代理对象与目标对象. 代理对象是对目标对象的扩展, 并会调用目标对象.
1.1. 静态代理
静态代理在使用时, 需要定义接口或者父类, 被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
下面举个案例来解释:
模拟保存动作, 定义一个保存动作的接口: IUserDao.java, 然后目标对象实现这个接口的方法 UserDao.java, 此时如果使用静态代理方式, 就需要在代理对象 (UserDaoProxy.java) 中也实现 IUserDao 接口. 调用的时候通过调用代理对象的方法来调用目标对象.
需要注意的是, 代理对象与目标对象要实现相同的接口, 然后通过调用相同的方法来调用目标对象的方法.
代码示例:
接口: IUserDao.java
- /**
- * 接口
- */
- publicinterfaceIUserDao{
- voidsave();
- }
目标对象: UserDao.java
- /**
- * 接口实现
- * 目标对象
- */
- publicclassUserDaoimplementsIUserDao{
- publicvoidsave(){
- System.out.println("---- 已经保存数据!----");
- }
- }
代理对象: UserDaoProxy.java
- /**
- * 代理对象, 静态代理
- */
- publicclassUserDaoProxyimplementsIUserDao{
- // 接收保存目标对象
- privateIUserDao target;
- publicUserDaoProxy(IUserDao target){
- this.target=target;
- }
- publicvoidsave(){
- System.out.println("开始事务...");
- target.save();// 执行目标对象的方法
- System.out.println("提交事务...");
- }
- }
测试类: App.java
- /**
- * 测试类
- */
- publicclassApp{
- publicstaticvoidmain(String[] args){
- // 目标对象
- UserDao target =newUserDao();
- // 代理对象, 把目标对象传给代理对象, 建立代理关系
- UserDaoProxy proxy =newUserDaoProxy(target);
- proxy.save();// 执行的是代理的方法
- }
- }
静态代理总结:
1. 可以做到在不修改目标对象的功能前提下, 对目标功能扩展.
2. 缺点:
因为代理对象需要与目标对象实现一样的接口, 所以会有很多代理类, 类太多. 同时, 一旦接口增加方法, 目标对象与代理对象都要维护.
如何解决静态代理中的缺点呢? 答案是可以使用动态代理方式
1.2. 动态代理
动态代理有以下特点:
1. 代理对象, 不需要实现接口
2. 代理对象的生成, 是利用 JDK 的 API, 动态的在内存中构建代理对象(需要我们指定创建代理对象 / 目标对象实现的接口的类型)
3. 动态代理也叫做: JDK 代理, 接口代理
JDK 中生成代理对象的 API
代理类所在包: java.lang.reflect.Proxy
JDK 实现代理只需要使用 newProxyInstance 方法, 但是该方法需要接收三个参数, 完整的写法是:
- staticObjectnewProxyInstance(ClassLoader loader,
- Class[]interfaces,InvocationHandler h )
注意该方法是在 Proxy 类中是静态方法, 且接收的三个参数依次为:
ClassLoader loader,: 指定当前目标对象使用类加载器, 获取加载器的方法是固定的
Class[] interfaces,: 目标对象实现的接口的类型, 使用泛型方式确认类型
InvocationHandler h: 事件处理, 执行目标对象的方法时, 会触发事件处理器的方法, 会把当前执行目标对象的方法作为参数传入
代码示例:
接口类 IUserDao.java 以及接口实现类, 目标对象 UserDao 是一样的, 没有做修改. 在这个基础上, 增加一个代理工厂类 (ProxyFactory.java), 将代理类写在这个地方, 然后在测试类(需要使用到代理的代码) 中先建立目标对象和代理对象的联系, 然后代用代理对象的中同名方法. 点击这里 http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247483791&idx=1&sn=548b40dd2ff72f5b553fa6d5f63fda7f&chksm=eb5384b9dc240daf63e6cc3a29827d7de6629cd26902c92143ac0cd88d6fe878d7bc7dc1abfa&scene=21#wechat_redirect 有一份动态代理模式代码实战.
代理工厂类: ProxyFactory.java
- /**
- * 创建动态代理对象
- * 动态代理不需要实现接口, 但是需要指定接口类型
- */
- publicclassProxyFactory{
- // 维护一个目标对象
- privateObjecttarget;
- publicProxyFactory(Objecttarget){
- this.target=target;
- }
- // 给目标对象生成代理对象
- publicObjectgetProxyInstance(){
- returnProxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- newInvocationHandler() {
- @Override
- publicObjectinvoke(Objectproxy, Method method,Object[] args) throws Throwable {
- System.out.println("开始事务 2");
- // 执行目标对象方法
- ObjectreturnValue = method.invoke(target, args);
- System.out.println("提交事务 2");
- returnreturnValue;
- }
- }
- );
- }
- }
测试类: App.java
- /**
- * 测试类
- */
- publicclassApp{
- publicstaticvoidmain(String[] args){
- // 目标对象
- IUserDao target =newUserDao();
- // [原始的类型 class cn.itcast.b_dynamic.UserDao]
- System.out.println(target.getClass());
- // 给目标对象, 创建代理对象
- IUserDao proxy = (IUserDao)newProxyFactory(target).getProxyInstance();
- // class $Proxy0 内存中动态生成的代理对象
- System.out.println(proxy.getClass());
- // 执行方法 [代理对象]
- proxy.save();
- }
- }
总结:
代理对象不需要实现接口, 但是目标对象一定要实现接口, 否则不能用动态代理
1.3.Cglib 代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象, 但是有时候目标对象只是一个单独的对象, 并没有实现任何的接口, 这个时候就可以使用以目标对象子类的方式类实现代理, 这种方法就叫做: Cglib 代理. 点击这里 http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247483791&idx=1&sn=548b40dd2ff72f5b553fa6d5f63fda7f&chksm=eb5384b9dc240daf63e6cc3a29827d7de6629cd26902c92143ac0cd88d6fe878d7bc7dc1abfa&scene=21#wechat_redirect 有一份动态代理模式代码实战.
Cglib 代理, 也叫作子类代理, 它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
JDK 的动态代理有一个限制, 就是使用动态代理的对象必须实现一个或多个接口, 如果想代理没有实现接口的类, 就可以使用 Cglib 实现.
Cglib 是一个强大的高性能的代码生成包, 它可以在运行期扩展 java 类与实现 java 接口. 它广泛的被许多 AOP 的框架使用, 例如 Spring AOP 和 synaop, 为他们提供方法的 interception(拦截)
Cglib 包的底层是通过使用一个小而块的字节码处理框架 ASM 来转换字节码并生成新的类. 不鼓励直接使用 ASM, 因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉.
Cglib 子类代理实现方法:
1. 需要引入 cglib 的 jar 文件, 但是 Spring 的核心包中已经包括了 Cglib 功能, 所以直接引入 pring-core-3.2.5.jar 即可.
2. 引入功能包后, 就可以在内存中动态构建子类
3. 代理的类不能为 final, 否则报错
4. 目标对象的方法如果为 final/static, 那么就不会被拦截, 即不会执行目标对象额外的业务方法.
代码示例:
目标对象类: UserDao.java
- /**
- * 目标对象, 没有实现任何接口
- */
- publicclassUserDao{
- publicvoidsave(){
- System.out.println("---- 已经保存数据!----");
- }
- }
Cglib 代理工厂: ProxyFactory.java
- /**
- * Cglib 子类代理工厂
- * 对 UserDao 在内存中动态构建一个子类对象
- */
- publicclassProxyFactoryimplementsMethodInterceptor{
- // 维护目标对象
- privateObjecttarget;
- publicProxyFactory(Objecttarget) {
- this.target = target;
- }
- // 给目标对象创建一个代理对象
- publicObjectgetProxyInstance(){
- //1. 工具类
- Enhancer en =newEnhancer();
- //2. 设置父类
- en.setSuperclass(target.getClass());
- //3. 设置回调函数
- en.setCallback(this);
- //4. 创建子类(代理对象)
- returnen.create();
- }
- @Override
- publicObjectintercept(Objectobj, Method method,Object[] args, MethodProxy proxy) throws Throwable {
- System.out.println("开始事务...");
- // 执行目标对象的方法
- ObjectreturnValue = method.invoke(target, args);
- System.out.println("提交事务...");
- returnreturnValue;
- }
- }
测试类:
- /**
- * 测试类
- */
- publicclassApp{
- @Test
- publicvoidtest()
- {
- // 目标对象
- UserDao target =newUserDao();
- // 代理对象
- UserDao proxy = (UserDao)newProxyFactory(target).getProxyInstance();
- // 执行代理对象的方法
- proxy.save();}}
来源: http://www.jianshu.com/p/bc52a86ac144