`
HelloSure
  • 浏览: 308348 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

聊一下Java代理那点事

阅读更多
代理模式

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:
  • 抽象角色:声明真实对象和代理对象的共同接口;
  • 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  • 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

以下举个简单的例子:
抽象角色:
abstract class Subject{ 
       public void request();
}  

真实角色:实现了Subject的request()方法。
public class RealSubject extends Subject {
    public RealSubject() { }

    public void request() {
        System.out.println("From real subject.");
    }
}

代理角色:
public class ProxySubject extends Subject{
  private RealSubject realSubject; //以真实角色作为代理角色的属性

  public ProxySubject() { }

  //该方法封装了真实对象的request方法
  public void request() {
      preRequest();
      if( realSubject == null ) {
           realSubject = new RealSubject();
      }
      realSubject.request(); //此处执行真实对象的request方法
      postRequest();
}

  private void preRequest() {
    //something you want to do before requesting
  }

  private void postRequest() {
    //something you want to do after requesting
  }
}

客户端调用:
Subject sub=new ProxySubject();
Sub.request(); 

由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理 RealSubject类,同样达到目的,同时还封装了其他方法(preRequest(),postRequest()),可以处理一些其他问题。

代理模式可以用下面这个UML图表示:

为了保持行为的一致性,代理类(ProxySubject)和委托类(RealSubject)通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个 代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。 这就引出了动态代理类:

JDK动态代理
Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
(1) Interface InvocationHandler:该接口中仅定义了一个方法:
invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。

(2)Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
  • Protected Proxy(InvocationHandler h):构造函数,给内部的h赋值。
  • Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
  • Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。

所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
在使用动态代理类时,我们必须实现InvocationHandler接口,以第一节中的示例为例:
抽象角色(之前是抽象类,此处应改为接口):
public interface Subject {
   abstract public void request();
}

具体角色RealSubject:
public class RealSubject implements Subject{
  public RealSubject(){}

  public void request(){
    System.out.println("From real subject.");
  }
} 

代理处理器:
public class DynamicSubject implements InvocationHandler {
  private Object sub;
  public DynamicSubject() {}

  public DynamicSubject(Object obj) {
    sub = obj;
  }

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   System.out.println("before calling " + method);
   method.invoke(sub,args);//其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数。

   System.out.println("after calling " + method);
   return null;
 }
}

客户端:
public class Client {
 static public void main(String[] args) throws Throwable {
   RealSubject rs = new RealSubject(); //在这里指定被代理类
   InvocationHandler ds = new DynamicSubject(rs);
   Class cls = rs.getClass();

   //生成代理
   Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),ds );
   subject.request();//运行目标方法
 } 
}

程序运行结果:
before calling public abstract void Subject.request()
From real subject.
after calling public abstract void Subject.request()

通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。

下面我们来看看在 Proxy类的newProxyInstance方法中发生了什么,来看源码:
public static Object newProxyInstance(ClassLoader loader,
					  Class<?>[] interfaces,
					  InvocationHandler h)
	throws IllegalArgumentException
 {
	if (h == null) {
	    throw new NullPointerException();
	}
        //【1】创建代理类
	Class cl = getProxyClass(loader, interfaces);

	try {
            //【2】创建代理类对象
	    Constructor cons = cl.getConstructor(constructorParams);
	    return (Object) cons.newInstance(new Object[] { h });
	} catch (NoSuchMethodException e) {
	    throw new InternalError(e.toString());
	} catch (IllegalAccessException e) {
	    throw new InternalError(e.toString());
	} catch (InstantiationException e) {
	    throw new InternalError(e.toString());
	} catch (InvocationTargetException e) {
	    throw new InternalError(e.toString());
	}
    }

先来看看【1】代码处,调用了getProxyClass方法创建了代理类,Proxy的public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)方法比较长(我数了一下算上注释有200+行),这个方法的具体分析可以参考http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
其实这个方法做的事情就是创建代理类,这个代理类实现了interfaces接口,使用的是loader类装载器。
在这个例子中,创建的代理类实现了Subject接口。

再来看看【2】代码处,创建了一个代理类的对象,并将一个InvocationHandler的对象传入。这实际调用的是Proxy的构造方法:
protected Proxy(InvocationHandler h) {
	this.h = h;
 }

这也说明了另一个问题,这个创建的代理类是Proxy的子类。
代理类的继承结构如下:


说到这插播一段java.lang.ClassLoader的介绍:它是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象。

下面再来看看InvocationHandler接口的源码:
public interface InvocationHandler {
  public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable;
}

invoke方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象,第三个是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行

那么,Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),ds )所创建的这个代理类到底是什么样子的呢?下面就来揭开它神秘的面纱(此处参考了http://hi.baidu.com/malecu/blog/item/45d4952b31bc0e27d52af17a.html):
//实现了Subject接口,并继承了Proxy 
public final class $Proxy0 extends Proxy implements Subject{

private static Method m0;
private static Method m1;
private static Method m2;
private static Method m3;

static {
   try {
    m1 = Class.forName("java.lang.Object").getMethod("equals",
      new Class[] { Class.forName("java.lang.Object") });
    m0 = Class.forName("java.lang.Object").getMethod("hashCode",
      new Class[0]);
    m3 = Class.forName("com.itec.test.Subject").getMethod("request",
      new Class[0]);
    m2 = Class.forName("java.lang.Object").getMethod("toString",
      new Class[0]);
   } catch (NoSuchMethodException nosuchmethodexception) {
    throw new NoSuchMethodError(nosuchmethodexception.getMessage());
   } catch (ClassNotFoundException classnotfoundexception) {
    throw new NoClassDefFoundError(classnotfoundexception.getMessage());
   }
}

//和Proxy的构造方法相对应的
public $Proxy0(InvocationHandler invocationhandler) {
   super(invocationhandler);
}

@Override
public final boolean equals(Object obj) {
   try {
    return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))
      .booleanValue();
   } catch (Throwable throwable) {
    throw new UndeclaredThrowableException(throwable);
   }
}

@Override
public final int hashCode() {
   try {
    return ((Integer) super.h.invoke(this, m0, null)).intValue();
   } catch (Throwable throwable) {
    throw new UndeclaredThrowableException(throwable);
   }
}

public final void request() {
   try {
    super.h.invoke(this, m3, null);//调用的是InvocationHandler对象的invoke方法
    return;
   } catch (Error e) {
   } catch (Throwable throwable) {
    throw new UndeclaredThrowableException(throwable);
   }
}

@Override
public final String toString() {
   try {
    return (String) super.h.invoke(this, m2, null);
   } catch (Throwable throwable) {
    throw new UndeclaredThrowableException(throwable);
   }
}
}

在得到这个代理类之后,接着把得到的$Proxy0实例强制转换成Subject。
当执行subject.request();方法时,就调用了$Proxy0类中的request()方法。
在request方法中,调用父类Proxy中的h的invoke()方法,即InvocationHandler.invoke();
可以明显看出,这个InvocationHandler.invoke()方法使用的就是回调的机制。(如果对回调的内容感兴趣,请参考这篇帖子http://hellosure.iteye.com/blog/1130176

好了,现在来对比一下前面提到的这两个例子,对于后一个例子来说:代理类$Proxy0就相当于是前一个例子中的ProxySubject类,只不过这个代理类的创建需要用到Java.lang.reflect.Proxy,并且实际的代理方法需要借助到InvocationHandler来完成回调。
那么,使用动态代理的好处显而易见:
Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。

CJLIB动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
抽象角色:
public interface Subject {
   abstract public void request();
}

具体角色RealSubject:
public class RealSubject {//注意没有实现Subject 接口
  public RealSubject(){}

  public void request(){
    System.out.println("From real subject.");
  }
} 

代理处理器:
public class SubjectCglib implements MethodInterceptor {  
    private Object target;  
  
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  
        // 回调方法  
        enhancer.setCallback(this);  
        // 创建代理对象  
        return enhancer.create();  
    }  
  
    @Override  
    // 回调方法  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        System.out.println("before calling " + method);  
        proxy.invokeSuper(obj, args);  
        System.out.println("after calling " + method);  
        return null; 
    }  
}  

客户端:
public class TestCglib {  
    public static void main(String[] args) {  
        SubjectCglib cglib=new SubjectCglib ();  
        RealSubject subjectCglib=(RealSubject)cglib.getInstance(new RealSubject());  
        subjectCglib.request();  
    }  
}  


反射机制简介
动态代理的实现大量使用了Java反射机制,这个部分和本文主题关系不大,算是一个小小的补充,老鸟请略过,菜鸟瞄一下就行了,可能会帮助你理解。
JAVA语言中的反射机制:
    在Java 运行时 环境中,对于任意一个类,能否知道这个类有哪些属性和方法?
    对于任意一个对象,能否调用他的方法?这些答案是肯定的,这种动态获取类的信息,以及动态调用类的方法的功能来源于JAVA的反射。从而使java具有动态语言的特性。

JAVA反射机制主要提供了以下功能:
  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法)
  • 在运行时调用任意一个对象的方法(前提都是在运行时,而不是在编译时)

Java 反射相关的API简介:位于java。lang。reflect包中
  • Class类:代表一个类
  • Filed类:代表类的成员变量
  • Method类:代表类的方法
  • Constructor类:代表类的构造方法
  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法。该类中的所有方法都是静态方法

Class类
     在 java 的Object类中的申明了数个应该在所有的java类中被改写的methods:
hashCode(), equals(),clone(),toString(),getClass()等,其中的getClass()返回一个Class 类型的对象。
     Class类十分的特殊,它和一般的类一样继承自Object,其实体用以表达java程序运行
时的 class和 interface,也用来表达 enum,array,primitive,Java Types 以及关键字void,当加载一个类,或者当加载器(class loader)的defineClass()被JVM调用,便产生一个Class对象,
     Class是Reflection起源,针对任何你想探勘的class(类),唯有现为他产生一个Class
的对象,接下来才能经由后者唤起为数十多个的反射API。

Java允许我们从多种途径为一个类class生成对应的Class对象。
  • 运用 getClass():Object类中的方法,每个类都拥有此方法
  • 运用 Class.getSuperclass():Class类中的方法,返回该Class的父类的Class
  • 运用 Class.forName()静态方法:
  • 运用 Class:类名.class
  • 运用primitive wrapper classes的TYPE语法: 基本类型包装类的TYPE,如:Integer.TYPE注意:TYPE的使用,只适合原生(基本)数据类型

运行时生成instance
     想生成对象的实体,在反射动态机制中有两种方法,一个针对无变量的构造方法,一个针对带参数的构造方法,如果想调用带参数的构造方法,就比较的麻烦,不能直接调用Class类中的newInstance(),而是调用Constructor类中newInstance()方法,首先准备一个Class[]作为Constructor的参数类型。
然后调用该Class对象的getConstructor()方法获得一个专属的Constructor的对象,最后再准备一个Object[]作为Constructor对象昂的newInstance()方法的实参。在这里需要说明的是 只有两个类拥有newInstance()方法,分别是Class类和Constructor类.
Class类中的newInstance()方法是不带参数的,而Constructro类中的newInstance()方法是带参数的
需要提供必要的参数。
    例:
Class c=Class.forName("DynTest");
Class[] ptype=new Class[]{double.class,int.class};
Constructor ctor=c.getConstructor(ptypr);
Object[] obj=new Object[]{new Double(3.1415),new Integer(123)};
Object object=ctor.newInstance(obj);
System.out.println(object);

运行时调用Method
    这个动作首先准备一个Class[]{}作为getMethod(String name,Class[])方法的参数类型,接下来准备一个Obeject[]放置自变量,然后调用Method对象的invoke(Object obj,Object[])方法。

运行时调用Field内容
    变更Field不需要参数和自变量,首先调用Class的getField()并指定field名称,获得特定的Field对象后便可以直接调用Field的 get(Object obj)和set(Object obj,Object value)方法
  • 大小: 3.8 KB
  • 大小: 2 KB
7
4
分享到:
评论
3 楼 treemap 2011-09-10  
楼主写的不错,看这 非常用心啊
2 楼 HelloSure 2011-08-09  
s929498110 写道
支持一下、 

谢谢
1 楼 s929498110 2011-08-06  
支持一下、 

相关推荐

    Java 代理 代理模式 静态代理与动态代理 常见的动态代理实现 .md

    说在前面:今天我们来聊一聊 Java 中的代理,先来聊聊故事背景: 小明想购买法国某个牌子的香水送给女朋友,但是在国内没有货源售卖,亲自去法国又大费周章了,而小红现在正在法国玩耍,她和小明是好朋友,可以帮...

    Java 动态代理.md

    这篇文章我们来聊一下 Java 中的动态代理。 动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。 在了解动态代理前,我们需要先...

    Java企业微信群机器人发送消息

    3.有含代理的构造方法和不含代理的构造方法,可根据需要选择调用; 4.参数使用的是JSONObject,防止在用字符串拼接参数时出现各种特殊字符转义问题; 5.发送图片大小不超过2M(企业微信的规定)。 具体使用步骤见...

    2021互联网大厂Java架构师面试题突击视频教程

    03_关于互联网Java工程师面试突击训练课程的几点说明 04_体验一下面试官对于消息队列的7个连环炮 05_知其然而知其所以然:如何进行消息队列的技术选型? 06_引入消息队列之后该如何保证其高可用性? 07_我的天!我为...

    Java物联网开发“尚方宝剑”之EMQ视频教程

    1.有一定JAVA编程基础,希望快速提升技术水平。 2.有一定项目开发经验,希望从事物联网行业应用开发的程序员。 课程内容: 1.MQTT协议 2.EMQ Dashboard 3.EMQ认证 4.日志与追踪 5.发布订阅ACL 6.WebHook 7.管理监控...

    java8集合源码分析-javaInterview:java面试

    hr简单聊了一下,让我回去了,没有技术面试 0525 牧原数字技术 char数字类型可以存数字么,为什么? java集合 juc包 下常用的类? 线程池下executor 的futre方法? (听不太清 executorService 用过么? 线程池具体 怎么使用...

    2020年春招最新阿里Java面试题集锦

    jdk和cglib实现的AOP实际上会在内存生成动态代理对象,还有什么其他办法实现AOP?经提示答出AspectJ以及实现原理 Spring中的对象的作用域 Singleton对象引用Prototype会发生什么 项目中怎样使用微服务? 两个服务...

    Java 实时社区论坛.zip

    初衷Sym 的诞生是有如下几点原因:大多数论坛用户体验不够现代化,想做一个和聊 QQ 一样体验的论坛已有的用 Java 写的论坛真的很少也很丑,并且大多已经不再维护我们想实现一种新的网络社区体验,独立博客 社区互动...

    java高级软件工程师教程快速入门Zookeeper+dubbo视频教程

    Dubbo是一款高性能、轻量级的开源Java RPC框架,提供面向接口代理的高性能RPC调用、智能负载均衡、服务自动注册和发现、运行期流量调度、可视化服务治理和运维等功能。 本套课程中,第一阶段深入Zookeeper原理和源码...

    javaOA办公系统模块设计方案.pdf

    javaOA办公系统模块设计⽅案 1.模型管理 :web在线流程设计器、预览流程xml、导出xml、部署流程 2.流程管理 :导⼊导出流程资源⽂件、查看流程图、根据流程实例反射出流程模型、激活挂起 、⾃由跳转 3.运⾏中流程:...

    基于Python3.10的OpenAI聊天机器人ChatGPT模型接入钉钉Dingding机器人单聊-群聊功能.zip

    基于Python3.10的OpenAI聊天机器人ChatGPT模型接入钉钉Dingding机器人单聊-群聊功能.zip 启动server端接受C端的钉钉机器人返回的信息 python3 server.py 使用Dingding实例向C端发送信息 dingding = DingDing("appkey...

    基于JAVA SMART系统-系统框架设计与开发(毕业设计+lw)

    在业务层则是采用单例模式设计与Spring的IoC模式相结合,实现了公共代理类的编写,各业务逻辑接口的封装。而在持久层的设计中则是采用基于现有持久层框架的实现模式,实现了对产生Session实例的封装,对常用数据库...

    基于Springboot的一个后端服务,用于实时接收chatGPT的消息,并通过websocket的方式实时反馈给前端

    1、本项目还可以助你将GPT机器人集成到钉钉群聊中,通过@机器人进行聊天交互。 2、支持现有的 GPT 3.5 支持 GPT 4.0 支持 GPT 4.0-32k 支持 流式对话 支持 阻塞式对话 支持 上下文 支持 多KEY轮询 支持 代理 支持 ...

    是一个基于Springboot的一个后端服务,用于实时接收chatGPT的消息,并通过websocket的方式实时反馈前端+源代

    - 本项目还可以助你将GPT机器人集成到钉钉群聊中,通过@机器人进行聊天交互。 ### 前端页面截图: - ![pc端](img/1.jpg) - ![手机端](img/2.jpg) ### 钉钉使用截图: - ![写代码](img/5.png) - ![入职介绍](img/6....

    iguanodon

    otel代理开销的测量。 描述 我们在有和没有代理的情况下运行一个,并执行一些测量。 目标最终包括希望比较以下方面的开销: 交易吞吐量(TPM)-平均值和P95 GC暂停(数量和总持续时间) 内存分配率 设置 $ git ...

    Spring高级之注解驱动开发视频教程

    n 技术详解-切入点表达式详解 l Spring JDBC n 基础应用-JdbcTemplate的使用 n 源码分析-自定义JdbcTemplate n 设计模式-RowMapper的策略模式 n 高级应用-NamedParameterJdbcTemplate的使用 n 源码分析-...

    flume-zipkin-collector-sink:支持将 Zipkin 跨度发送到 Zipkin 收集器的水槽接收器

    代理应该这样配置: ScribeSource -&gt; Channel of your choice -&gt; ZipkinSpanCollectorSink您还可以通过链接多个代理来使用多跳流。 这可以通过将 ZipkinSpanCollectorSink 发送到另一个代理的 ScribeSource 来完成...

    Apache Hue 2小时轻松搞定大数据可视化终端视频教程

    4,每一块知识点, 都有配套案例, 学习不再迷茫。 适用人群 1、对大数据感兴趣的在校生及应届毕业生。 2、对目前职业有进一步提升要求,希望从事大数据行业高薪工作的在职人员。 3、对大数据行业感兴趣的相关人员。 ...

    亿级流量电商详情页系统实战-缓存架构+高可用服务架构+微服务架构

    讲解一个真实的、复杂的大型企业级亿级高并发项目,是java架构实战课程。 通过本套课程的学习,可以积累大量架构设计经验,迈入架构师行列。 课程特色: 1、完整的大型电商详情页系统架构:不再只是关注电商详情页...

Global site tag (gtag.js) - Google Analytics