动态代理和静态代理的区别静态代理和动态代理的理解动态代理和静态代理简单理解




动态代理和静态代理的区别静态代理和动态代理的理解动态代理和静态代理简单理解

2022-07-21 2:25:40 网络知识 官方管理员

Java静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为**代理三要素**

共同接口


  1. publicinterfaceAction{
  2. publicvoiddoSomething();
  3. }

真实对象


  1. publicclassRealObjectimplementsAction{
  2. publicvoiddoSomething(){
  3. System.out.println("dosomething");
  4. }
  5. }

代理对象


  1. publicclassProxyimplementsAction{
  2. privateActionrealObject;
  3. publicProxy(ActionrealObject){
  4. this.realObject=realObject;
  5. }
  6. publicvoiddoSomething(){
  7. System.out.println("proxydo");
  8. realObject.doSomething();
  9. }
  10. }

运行代码


  1. Proxyproxy=newProxy(newRealObject());
  2. proxy.doSomething();

动态代理和静态代理的区别(静态代理和动态代理的理解)(1)

这种代理模式也最为简单,就是通过proxy持有realObject的引用,并进行一层封装。

静态代理的优点和缺点

先看看代理模式的优点:扩展原功能,不侵入原代码。

再看看这种代理模式的缺点:

假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代码可能是这样的:


  1. realObject.doSomething();
  2. realObject1.doAnotherThing();
  3. realObject2.doTwoAnother();

为了解决这个问题,我们有方案一:

为这些方法创建不同的代理类,代理后的代码是这样的:


  1. proxy.doSomething();
  2. proxy1.doAnotherThing();
  3. proxy2.doTwoAnother();

当然,也有方案二:

通过创建一个proxy,持有不同的realObject,实现Action1、Action2、Action3接口,来让代码变成这样:


  1. proxy.doSomething();
  2. proxy.doAnotherThing();
  3. proxy.doTwoAnother();

于是你的代理模型会变成这样:

动态代理和静态代理的区别(静态代理和动态代理的理解)(2)

毫无疑问,仅仅为了扩展同样的功能,在方案一种,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。

而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。

java动态代理

搞清楚静态代理的缺点十分重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。

简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理基本用法

使用动态代理,需要将要扩展的功能写在一个InvocationHandler实现类里:


  1. publicclassDynamicProxyHandlerimplementsInvocationHandler{
  2. privateObjectrealObject;
  3. publicDynamicProxyHandler(ObjectrealObject){
  4. this.realObject=realObject;
  5. }
  6. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
  7. //代理扩展逻辑
  8. System.out.println("proxydo");
  9. returnmethod.invoke(realObject,args);
  10. }
  11. }

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

到这里,需要先看一下这个handler的用法:


  1. publicstaticvoidmain(String[]args){
  2. RealObjectrealObject=newRealObject();
  3. Actionproxy=(Action)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),newClass[]{Action.class},newDynamicProxyHandler(realObject));
  4. proxy.doSomething();
  5. }

Proxy.newProxyInstance传入的是一个ClassLoader,一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。

仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的,有没有一点动态的味道。

让我们再回顾一下代理三要素:真实对象:RealObject,代理接口:Action,代理实例:Proxy

上面的代码实含义也就是,输入RealObject、Action,返回一个Proxy。妥妥的代理模式。

综上,动态生成+代理模式,也就是动态代理。

网上搜了不少文章,到了这里,接下来就是和cglib等动态代理实现方法做一下横向比较。本文不做横向比较,为了不偏离主题,接下来做纵向挖掘。

看一下源码

道理清楚了,但是这篇文章题目是搞懂,所以来看一下这个Proxy是如何自动被生成的。入口就在newProxyInstance方法,核心代码如下:


  1. privatestaticfinalClass<?>[]constructorParams=
  2. {InvocationHandler.class};
  3. publicstaticObjectnewProxyInstance(ClassLoaderloader,
  4. Class<?>[]interfaces,
  5. InvocationHandlerh)
  6. throwsIllegalArgumentException
  7. {
  8. Class<?>cl=getProxyClass0(loader,intfs);
  9. ...
  10. finalConstructor<?>cons=cl.getConstructor(constructorParams);
  11. if(!Modifier.isPublic(cl.getModifiers())){
  12. AccessController.doPrivileged(newPrivilegedAction<Void>(){
  13. publicVoidrun(){
  14. cons.setAccessible(true);
  15. returnnull;
  16. }
  17. });
  18. }
  19. returncons.newInstance(newObject[]{h});
  20. }

整体流程就是:

1、生成代理类Proxy的Class对象。

2、如果Class作用域为私有,通过setAccessible支持访问

3、获取ProxyClass构造函数,创建Proxy代理实例。

生成Proxy的Class文件

生成Class对象的方法中,先是通过传进来的ClassLoader参数和Class[]数组作为组成键,维护了一个对于Proxy的Class对象的缓存。这样需要相同Proxy的Class对象时,只需要创建一次。

第一次创建该Class文件时,为了线程安全,方法进行了大量的处理,最后会来到ProxyClassFactory的apply方法中,经过以下流程:

1、校验传入的接口是否由传入的ClassLoader加载的。

2、校验传入是否是接口的Class对象。

3、校验是否传入重复的接口。

4、拼装代理类包名和类名,生成.class文件的字节码。

5、调用native方法,传入字节码,生成Class对象。


  1. proxyPkg=ReflectUtil.PROXY_PACKAGE+".";
  2. longnum=nextUniqueNumber.getAndIncrement();
  3. StringproxyName=proxyPkg+proxyClassNamePrefix+num;
  4. byte[]proxyClassFile=ProxyGenerator.generateProxyClass(
  5. proxyName,interfaces,accessFlags);
  6. returndefineClass0(loader,proxyName,
  7. proxyClassFile,0,proxyClassFile.length);

看一下第四步生成.class文件字节码的过程,主要分为两个阶段:


  1. addProxyMethod(hashCodeMethod,Object.class);
  2. addProxyMethod(equalsMethod,Object.class);
  3. addProxyMethod(toStringMethod,Object.class);
  4. for(inti=0;i<interfaces.length;i++){
  5. Method[]methods=interfaces[i].getMethods();
  6. for(intj=0;j<methods.length;j++){
  7. addProxyMethod(methods[j],interfaces[i]);
  8. }
  9. }
  10. methods.add(this.generateConstructor());
  11. for(List<ProxyMethod>sigmethods:proxyMethods.values()){
  12. for(ProxyMethodpm:sigmethods){
  13. fields.add(newFieldInfo(pm.methodFieldName,
  14. "Ljava/lang/reflect/Method;",ACC_PRIVATE|ACC_STATIC));
  15. methods.add(pm.generateMethod());
  16. }
  17. }
  18. methods.add(generateStaticInitializer());

第一个阶段的代码比较清晰,主要就是添加各种Method,比如toString()、equals,以及传入的代理接口中的方法。再添加一下构造方法以及静态初始化方法。这要构成了一个对象,存储生成Proxy的Class的一些信息。

到了这里,已经把要构造的Proxy的方法基本定义完成了,接下来就要生成这个.class文件了。


  1. ByteArrayOutputStreambout=newByteArrayOutputStream();
  2. DataOutputStreamdout=newDataOutputStream(bout);
  3. dout.writeInt(0xCAFEBABE);
  4. ...
  5. dout.writeShort(ACC_PUBLIC|ACC_FINAL|ACC_SUPER);
  6. ...
  7. returnbout.toByteArray();

看到这个CAFEBABE,就清楚第二阶段的内容了。CAFEBABE是Class文件的魔数,关于Class文件这个咖啡宝贝的魔数,相信做Java的人都知道。没错,第二阶段就是生成字节码。按JVM规范,写入Class文件中包括权限控制、方法表、字段表等内容,生成符合规范的Class文件。最后返回对应的字节码。

字节码生成以后,通过调用native方法defineClass解析字节码,就生成了Proxy的Class对象。

Proxy构造方法

看一下Proxy的构造方法字节码生成部分:


  1. MethodInfominfo=newMethodInfo("<init>","(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC);
  2. DataOutputStreamout=newDataOutputStream(minfo.code);
  3. code_aload(0,out);
  4. code_aload(1,out);
  5. out.writeByte(opc_invokespecial);
  6. out.writeShort(cp.getMethodRef(superclassName,"<init>","(Ljava/lang/reflect/InvocationHandler;)V"));
  7. ...

关键在于,生成了一个参数为InvocationHandler的构造方法,code加载的是jvm方法区中的代码,然后通过invokespecial指令调用了父类构造方法。

查看生成的Class文件

上面利用字节码生成技术产生Class文件的过程,看起来可能比较晦涩,其实我们可以查看这个产生的Proxy到底是个什么样子。

注意ProxyGenerator中有这样一个逻辑:


  1. if(saveGeneratedFiles){
  2. ...
  3. FileOutputStreamfile=newFileOutputStream(dotToSlash(name)+".class");
  4. file.write(classFile);
  5. ...
  6. }

再看一下saveGeneratedFiles这个变量:


  1. privatefinalstaticbooleansaveGeneratedFiles=
  2. java.security.AccessController.doPrivileged(
  3. newGetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))
  4. .booleanValue();

这是一个final类型的变量,通过GetBooleanAction方法读取系统变量,获取系统设置。默认这个值是false,稍微看一下System这个类的源码,发现有可以设置系统变量的Api,然后在程序的main函数设置一下这个变量:

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

这个时候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接双击利用ide反编译。


  1. packagecom.sun.proxy;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Method;
  4. importjava.lang.reflect.Proxy;
  5. importjava.lang.reflect.UndeclaredThrowableException;
  6. publicfinalclass$Proxy0extendsProxyimplementsAction{
  7. privatestaticMethodm1;
  8. privatestaticMethodm3;
  9. privatestaticMethodm2;
  10. privatestaticMethodm0;
  11. public$Proxy0(InvocationHandlervar1)throws{
  12. super(var1);
  13. }
  14. publicfinalvoiddoSomething()throws{
  15. try{
  16. super.h.invoke(this,m3,(Object[])null);
  17. }catch(RuntimeException|Errorvar2){
  18. throwvar2;
  19. }catch(Throwablevar3){
  20. thrownewUndeclaredThrowableException(var3);
  21. }
  22. }
  23. ...
  24. static{
  25. try{
  26. ...
  27. m3=Class.forName("Action").getMethod("doSomething",newClass[0]);
  28. }catch(NoSuchMethodExceptionvar2){
  29. thrownewNoSuchMethodError(var2.getMessage());
  30. }catch(ClassNotFoundExceptionvar3){
  31. thrownewNoClassDefFoundError(var3.getMessage());
  32. }
  33. }
  34. }

省略一些无关代码,可以看到两个重要的方法。

一个就是我们的代理方法doSomething、另一个就是构造方法。

这个$Proxy0继承Proxy并调用了父类的构造方法,回忆一下上文提到的invokeSpecial,怎么样,对上了吧。

看一下Proxy中这个构造方法:


  1. protectedProxy(InvocationHandlerh){
  2. Objects.requireNonNull(h);
  3. this.h=h;
  4. }

在看一下$Proxy0的代理方法:

super.h.invoke(this,m3,(Object[])null);

再来回顾一下生成Proxy实例的过程:


  1. privatestaticfinalClass<?>[]constructorParams=
  2. {InvocationHandler.class};
  3. ...
  4. finalConstructor<?>cons=cl.getConstructor(constructorParams);
  5. ...
  6. returncons.newInstance(newObject[]{h});

其实newInstance生成Proxy实例时,通过$Proxy0的Class对象,选择了这个InvocationHandler为参数的构造方法,传入我们定义的InvocationHandler并生成了一个Proxy0的实例!InvocationHandler里有realObject的逻辑以及我们的扩展逻辑,当我们调用Proxy0的doSomething方法时,就会调用到我们InvocationHandler里实现的invoke方法。

对上面这个过程,做一张图总结一下:

动态代理和静态代理的区别(静态代理和动态代理的理解)(3)

发表评论:

最近发表
网站分类
标签列表