设计模式之动态代理(dynamic proxy)

1 动态代理与静态代理

我们从上一篇设计模式之代理模式一文中已经知道,在代理模式中代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。这就是传统的代理模式静态代理的特点。

那么传统的静态代理模式有什么问题呢?如果需要代理的类只有一个,那么静态代理没什么问题,如果有很多类需要代理呢,用静态代理的话就需要为每一个类创建一个代理类,显然这么做太过繁琐也容易出错。为此,JDK 5引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。也就是说,我们不用为每个类再单独创建一个代理对象了。

比如Spring的aop框架,它可以通过简单的配置,在不新增、修改任何业务逻辑代码情况下,动态的给我们的业务逻辑增加诸如日志打印、事务处理、异常处理等,这就是利用的动态代理机制。

2 动态代理的作用

  • 数据库连接以及事物管理
  • 单元测试中的动态 Mock 对象
  • 自定义工厂与依赖注入(DI)容器之间的适配器
  • 类似 AOP 的方法拦截器
  • 日志、缓存等业务增强
  • Java RMI远程通信
  • 各种访问控制器、验证器
  • … …

3 动态代理的原理

动态代理主要是利用了Java的反射机制。

4 动态代理类的创建

要创建一个动态代理,只需要利用Java API提供的两个类:

  • java.lang.reflect.InvocationHandler: 这是调用处理器接口,它自定义了一个 invoke() 方法,我们就在这个方法里触发代理对象自己的方法,你可以在它的前后增加我们自己的增强方法。
  • java.lang.reflect.Proxy: 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象,也就是动态生成代理对象的方法。

每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandlerinvoke()方法。在 invoke()方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke()方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。熟悉AOP的人对这种使用模式应该不陌生。但是这种方式不需要依赖AspectJ等AOP框架。

4.1 创建一个代理

我们可以通过Proxy.newProxyInstance()方法来动态的创建一个代理。这个方法有3个参数:

1. ClassLoader :负责加载动态代理类
2. 接口数组
3. InvocationHandler:把方法调用转到代理上

Proxy类动态创建代理类:

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                        MyInterface.class.getClassLoader(),
                        new Class[] { MyInterface.class },
                        handler);

在执行完这段代码之后,变量proxy 包含一个 MyInterface 接口的的动态实现。所有对 proxy 的调用都被转向到实现了 InvocationHandler 接口的 handler 上。有关 InvocationHandler 的内容会在下一段介绍。

4.3 关于InvocationHandler接口

在前面提到了当你调用Proxy.newProxyInstance()方法时,你必须要传入一个InvocationHandler接口的实现。所有对动态代理对象的方法调用都会被转向到InvocationHandler接口的实现上,下面是 InvocationHandler 接口的定义:

public interface InvocationHandler{
  Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable;
}

传入invoke()方法中的proxy参数是实现要代理接口的动态代理对象。通常你是不需要他的。invoke()方法中的Method对象参数代表了被动态代理的接口中要调用的方法,从这个method对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。Object数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。

5 说再多不如举个实例

比如我们有两个业务,要为这两个业务添加日志打印功能。如果是静态代理,那么就需要分别为每个业务类写一个代理类,而如果用动态代理,只需要实现一个日志打印功能的handler即可,完全不需要自己再单独写代理类,下面我们具体看一下这个例子。

5.1 准备两个业务接口及其实现

接口A和接口B:

public interface SubjectA {
   public void setUser(String name,String password);
}

public interface SubjectB {
   public void sayHello(String name);
}

接口A和接口B的实现:

public class RealSubjectA implements SubjectA {
   public void setUser(String name,String password){
      System.out.println("-------------set user,name:"+name+" password:"+password+"-------------");
   }
}

public class RealSubjectB implements SubjectB{
   public void sayHello(String name) {
      System.out.println("--------------say hello:"+name+"-------------");
   }
}

5. 2 写一个日志打印的handler

/**
 * 日志打印handler,打印调用代理对象的方法及其参数值
 * **/
public class LogHandler implements InvocationHandler{
   private Object proxied;
   LogHandler(Object proxied){
      this.proxied=proxied;
   }
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("begin to invoke method:"+method.getName()+" params:"+ Arrays.toString(args));
      Object result=method.invoke(proxied,args);
      System.out.println("invoke "+method.getName()+" end");
      return result;
   }
}

5.3 最后用Proxy类生产动态代理对象

public class TestDynamicProxy {
   public static void main(String[] args) {
  RealSubjectA realA = new RealSubjectA();
  SubjectA proxySubjectA = (SubjectA) Proxy.newProxyInstance(SubjectA.class.getClassLoader(),
        new Class[]{SubjectA.class},
        new LogHandler(realA));//生成一个业务A的动态代理对象
  RealSubjectB realB = new RealSubjectB();
  SubjectB proxySubjectB = (SubjectB) Proxy.newProxyInstance(SubjectB.class.getClassLoader(),
        new Class[]{SubjectB.class},
        new LogHandler(realB));//生成一个业务B的动态代理对象
  proxySubjectA.setUser("heaven","123456");
  proxySubjectB.sayHello("heaven");
   }
}

运行结果

begin to invoke method:setUser params:[heaven, 123456]
-------------set user,name:heaven password:123456-------------
invoke setUser end
begin to invoke method:sayHello params:[heaven]
--------------say hello:heaven-------------
invoke sayHello end

结果说明

1. 通过动态代理,我们的业务逻辑没有做任何修改便实现了日志打印功能,实现了解耦(这其实就是一个aop编程的例子)
2. 我们没有为每个业务单独去写代理类,代理的代码量不会因为业务增加而庞大

5.4 数据库连接以及事物管理

Spring 框架中有一个事物代理可以让你提交/回滚一个事物,如果用动态代理的话,其方法调用序列如下:

web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();
时间: 2024-08-23 05:29:45

设计模式之动态代理(dynamic proxy)的相关文章

【设计模式】动态代理Proxy_01

大家都知道设计模式中一个比较难理解的模式--动态代理模式,下面我们来通过一步一步完善一个工程来学习动态代理. 首先我们创建一个JavaProject,名字为"ProxyTest". 创建一个类Tank.java,是一个坦克类,然后我们创建一个接口Moveable.java Moveable.java: package cn.edu.hpu.proxy; public interface Moveable { void move(); } Tank类实现Moveable接口 Tank.j

【设计模式】动态代理Proxy_02

我们继续上一次的动态代理探讨. 上一篇我们说道,所以我们要实现一种"通用"代理,可以对任意对象代理. 那么怎么实现呢? 我们规定产生代理的时候,被代理的类一定要实现一个接口.这样我们可以根据接口来生成代理对象,而不是根据具体的类. 我们明确一下我们的目标:我们可以对任何的对象,任何的类(前提是这个类实现了某个接口),我们就可以给它生成一个代理. 我们怎么样给它生成这个代理呢?为了模拟JDK的实现,我们添加一个新的类叫Proxy.java: package cn.edu.hpu.prox

java动态代理实现Proxy和InvocationHandler cglib

概念: 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行时,运用反射机制动态创建而成. JDK的动态代理用起来非常简单,当它有一个限制,就是使用动态代理的对象必须实现一个或多个接口.如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包. JDK动态代理实现 import java.lang.reflect.Constructor; import java.lang.reflect.Invoc

【设计模式】动态代理Proxy_03

我们继续上一篇总结. 我们把TankTimeProxy的类Load进内存之后我们要生成它的一个对象.我们先来回顾一下我们之前写好的Proxy类: package cn.edu.hpu.ProxyTest; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import j

Java 动态代理(Proxy)

动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实,代理对象对客户隐藏了实际对象.动态代理可以对请求进行其他的一些处理,在不允许直接访问某些类,或需要对访问做一些特殊处理等,这时候可以考虑使用代理.目前 Java 开发包中提供了对动态代理的支持,但现在只支持对接口的实现. 主要是通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口. Proxy 类主要用来获取动态代理对象,InvocationHand

设计模式之代理模式(Proxy Pattern)

1 代理模式定义 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 本篇文章主要介绍的是静态代理,关于动态代理请参考:设计模式之动态代理(dynamic proxy) 2 代理模式的作用 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 通过代理类对原有类进行功能增强,如增加缓存.异常处理.日志处理等,Spring AOP用的就是(动态)代理模式 3 代理模式一

【设计模式】【动态代理,在方法前和方法后加事务,AOP】

/** * Description: * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a> * <br/>Copyright (C), 2001-2010, Yeeku.H.Lee * <br/>This program is protected by copyright laws. * <br/>Program Name: * <br/>Da

人人都会设计模式:05-代理模式--Proxy

版权声明:本文为博主原创文章,未经博主允许不得转载公众号:TigerChain 更多文章等着你 作者: TigerChain 教程简介 1.阅读对象 本篇教程适合新手阅读,老手直接略过 2.教程难度 初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢 正文 一.什么是代理模式 1.生活中的代理 1.微商代理 代理在生活中就太多了,比如微商,在朋友圈中很多时候都可以看到微商说城招全国代理「不需要货源,不需要启动资金,只需要一个电话就能做生意,好吧我口才不好,没有人家吹的好」,这类

求救,java动态代理问题!

问题描述 各位同学:如何得到Java动态代理方法Proxy.newProxyInstance产生的临时代理类的内部结构是什么? 解决方案 解决方案二:你看下这个类的原码吧.解决方案三:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html?ca=drs-cn-0121我当时看动态代理的时候看过这篇文章,写的很好,推荐一下