分析代理类的作用与原理

本文主要探讨了代理类在Java中的作用,特别是在增加辅助功能但无法修改源代码时的解决方案。通过创建代理类,可以在不改变原有代码的基础上,实现如日志、安全等交叉业务的添加。介绍了静态代理和动态代理的概念,强调了动态代理在AOP(面向切面编程)中的重要性。讲解了JVM动态生成代理类的机制,以及在多线程环境下StringBuilder和StringBuffer的使用区别。最后,通过代码示例展示了如何使用InvocationHandler实现动态代理,并解释了代理对象调用方法时的内部工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析代理类的作用与原理


已经写好了一个类,里面有一个方法
class X{
  void sayHello(){
  System.out.println();
 }
}


要在这个类上面增加一些辅助功能,但是没有类的源代码,怎么办?

现在做一个X的代理
XProxy
{
 void sayHello()
 {
  startTime;
  X.sayHello();
  send time;
 }
}
代理类的方法必须和目标方法一样。

原来客户端需要调用Target,现在不调用Target,调用Proxy, 代理调用Target,Proxy和目标实现相同的接口,在客户端编程时,不调用目标,,也不调用代理,而是直接调用接口。
在代理里面一定要调用目标的对应方法,但是在调用这个方法的时候在前面或者后面需要增加一下动能。

采用工程模式和配置文件进行管理。
交叉业务,不同的模块有相同的事物,这样的业务叫做交叉业务。例如,安全,日志等功能贯穿到很多的模块中。

methodA(){ methodA(){ methodA(){

-------------------------------------------------------切面
    ...              ...          .....
-------------------------------------------------------切面

 }  }   }

像上面这样的,插入到了方法中。
像上面的这样的程序就叫做面向方面的编程。

面向方面的编程,AOP,AOP的目标就是要是交叉业务模块化,可以采用讲切面代码移动到原始方法的周围,这与直接在方法中编写切面代码与性的效果是一样的。

-------------------------------------------------------切面
methodA(){ methodA(){ methodA(){

    ...              ...          .....


 }  }   }
-------------------------------------------------------切面

使用代理技术正好可以解决这种问题,代理是实现AOP功能模块的核心和关键技术。

面向方面的编程,就要使用代理。

 

一个系统中的各种接口很多,如果需要给一个系统所有的类增加代理功能,那将会需要很多的代理类,全部采用静态的方式,会是一种很麻烦的事情。

但是JVM提供了一种方便的方式。JVM可以在运行期间动态的生出类的字节码,这种动态生成的类往往被用做代理类,即动态代理类,但是这个类不是真正的代理类。

JVM生成的动态类必须要实现一个或多个接口,为什么呢,因为如果你没有方法,那么这个这个代理类什么都不做就没有什么用了,告诉很多的方法的方法可以直接告诉一个代理类一个借口,因为这样,代理类就必须要实现接口的所有方法,所以,JVM生成的动态类智能用作具有相同接口的目标类的代理。

有的类本身没有实现某个接口,现在通过什么样的方式来告诉JVM生成的代理类和目标类有相同的方法,有一个第三方库,CGLIB,这个CGLIB可以动态的生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。


代理类的各个方法中通常要调用目标的相应的方法,但是光调用目标方法是没有任何作用的,是需要干一些额外的事情,那么这些目标代码放在哪里呢,这里有以下四个地方。
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法之前后
4.在处理目标方法异常的catch块中。

StringBuilder 和StringBuffer的区别
都是动态的往字符串里面添加内容。在单线程的情况下StringBuilder效率高一点,所以在单线程的情况下使用StringBuiilder,现在就算有五个线程,但是也会有五个StringBuilder实例,不用考虑安全的问题,所以开小会小一点,所以效率高,但是StringBuffer任何时候都需要考虑安全问题,所以单线程的情况下效率低。

Collection proxy3 = (Collection)Proxy.newProxyInstance(
    Collection.class.getClassLoader(),
    new Class[]{Collection.class},//在这里不能使用可变参数,因为可变参数的最有一个位置
    new InvocationHandler(){
     
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // TODO Auto-generated method stub
      ArrayList target = new ArrayList() ;
      long startTime = System.currentTimeMillis();
      Object retVal = method.invoke(target, args);
      long endTime = System.currentTimeMillis() ;
      System.out.println(method.getName()+" running time of "+ (endTime - startTime));
      //System.out.println(method.getClass());
      return retVal ;
      //return null;
     }
    }
    );
  
  Object obj = proxy3.add("zbb") ;
  
  //System.out.println(obj.toString());
  proxy3.add("gxd") ;
  proxy3.add("zxn") ;
  System.out.println(proxy3.size());


这里已经创建了一个InvocationHandler的子类的对象。在这里通过构造方法接受过一个handler,那么这个代理对象就会记住这个handler对象,因为构造方法就是用来给一个类里面的某个成员变量传值的作用,这里通过构造方法传递了一个InovacationHandler类型的对象,所以这里的代理对象就记住了你传递过来的InvocationHandler类型的对象。
当你在调用代理对象的方法是,如下:
proxy3.add("gxd") ;在调用这个add()这个方法时,他会去调用handler对象的invoke方法,只要代理调用一下方法,那么他就会相应的调用这个handler里面的invoke方法。

这里可以简单的写一下这个handler里面的方式是什么样子的。
int size(){
  return Handler.invoke(this.this.getClass().getMethod("size"),null);
}
这个就是一个基本的代理对象调用size方法对的流程。add方法的调用也是一样。
刚开始我们第一次传入proxy1代理对象时,里面的invoke方法返回的是null,所以我们在调用有返回值的函数时,就会报错,因为我们这个size()函数要接受一个返回值。
我们看到上面的InvocationHandler接口中定义的invoke方法接受的三个参数,但是不明白里面里面的参数是什么意思,现在来看一下。
Client程序调用objProxy(代理对象).add("abc")方法时,设计三个要素:objProxy对象、add方法、      "abc"参数
ClassProxy${                                                                                                                              |             |                         |
 add(Object obj){                                                                                                                        |             |                         |
                                                                                               return handler.invoke(Object proxy,Method method,Object[] args);
     //这里的参数的意思是,调用了那个代理对象,调用了代理对象的哪个方法,传进来的参数是什么。
 }
}

现在来看最上面的代码,当proxy3在调用add("abc")是,就调用了invoke(Object proxy, Method method, Object[] args),在这里就传入proxy3代理对象,add方法,和参数“abc”。
现在你需要的参数已经传入进来,就需要在目标的身上执行你需要的操作,例如
method.invoke(target,args);
这里调用代理调用的那个方法,但是不是在代理对象身上执行了,而是在目标对象身上执行。而且,刚才给代理传递什么方法,现在给目标就传递什么值。
在这过程每个方法返回结果的流程是首先目标对象的invoke方法返回结果给代理的invoke,外部代理invoke方法返回的结果返回给add方法,然后add方法再接着返回。

调用动态代理类的getClass方法,返回结果是正确的,什么意思?
System.out.println(proxy3.getClass().getName());//返回结果是$Proxy0
在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。


 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值