java动态代理原理详解
什么是代理?
简单的来说就是厂家不直接卖商品而是通过销售来卖,这里的销售商就是代理,厂家就是委托。也就是在厂家和商品之间增加了一定的间接性!那么这样的好处是什么?
- 隐藏委托类的实现。
- 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。(当其中一位客户还需要更多的要求,在不违背开闭原则的情况下,代理类可以增加一些额外的处理。)
静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
简单实现:
老板让秘书通知各个部门的部长来开会, 定义一个接口Move(开会)是代理类(秘书)和被代理类(部门)的公共接口。
public interface Move{
void play();
}
/*被代理类实现Move接口*/
class Department implements Move{
private String name;
public Department(String name){
this.name=name;
}
public void play(){
System.out.println(name+"开会");
}
}
/*代理类也要实现Move接口*/
class Secretary implements Move{
private Departmnt de;
public Secretary(Department de){
this.de=de;
}
public void play(){
de.play();
}
}
/*测试一下*/
public class Demo{
public static void main(String args[]){
Move de=new Department("人事");
/*通过持有被代理对象,来执行开会的行为*/
Move se=new Secretary(de);
se.play();
}
}
从上面的例子使用代理似乎没有什么好处。反而增加了代码长度。但当开会这一行为无法满足的时候,该如何设计被代理类?
- 直接在被代理类的方法中加上额外的功能,弊端很明显随着部门的增多,代码冗余越来越大。
- 直接在代理类中的方法找中增加额外的方法,避免了冗余。
public void play(){
System.out.println("部门情况");
de.play();
}
这样当我们通过代理类调用方法的时候,会在调用方法之前加入一些额外的操作。
但同时我们又会想到这样一种情况:当我们的被代理类有很多,而且方法是不同的这是我们又该如何解决?有下面两种情况:
-
我们为每一个被代理对象创建一个代理类,来持有这个对象。
se1.play1(); se2.play2(); se3.play3(); se4.play4();
-
我们创建一个代理类来实现这些被代理类的接口Move1,Move2,Move3…但是这样就会使得这一个代理类显得臃肿。
动态代理
通过上一个情况,引出了动态代理设计模式。就是解决静态代理上面这一缺点的。他和第一种解决方法类似,只不过这些se是在运行期间自动生成的代理类。
创建一个接口:
//接口
interface Subject{
public void rent();
public void hello(String str);
}
被代理对象:
//被代理对象
class RealSubject implements Subject{
public void rent()
{
System.out.println("I want to rent my house");
}
public void hello(String str)
{
System.out.println("hello: " + str);
}
}
创建一个类实现InvocationHandler接口,所有的被代理执行的方法都是通过执行该类中的invoke方法。
class DynamicProxy implements InvocationHandler{
// 这个就是我们要代理的真实对象
private Object proxy;//这是动态代理的好处,被封装的对象是Object类型,接受任意类型的对象
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object proxy)
{
this.proxy = proxy;
}
/*Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 在其上调用方法的代理实例.
method: 对应于在代理实例上调用的接口方法的 Method
实例: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 包含:传入代理实例上/方法调用/的参数值/的对象数组,如果接口方法不使用参数,则为 null。*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用,
// 而handler对象又传入了realSubject对象为参数,所以实际调用的是RealSubject中的方法。
method.invoke(proxy,args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");
return null;
}
}
在测试类中动态创建代理
public class DynamicProxyDemo
{
public static void main(String[] args)
{
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
// DynamicProxy实现InvocationHandler接口,通过有参构造方法将被代理对象传入,表明invoke调用的是realSubject对象的方法
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler的handler对象。
*/
Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
//这里会有一个疑问我们是如何调用rent方法的?
subject.rent();
subject.hello("world");
}
}
我只将realSubject关联到了DynamicProxy类中的subject对象上,知道了如果我调用了DynamicProxy的invoke方法其实就是调用realsubject中的方法,但是测试类中似乎我没有明显指出我哪里调用了DynamicProxy中的invok方法。
其实这里的newProxyInstance方法隐式地创建了一个代理类proxy0,通过反编译我们可以查看其中的源码。
public final class $Proxy0 extends Proxy implements Subject
{
private static Method m3;
/**
*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
*被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
*
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
static
{
//看看这儿静态块儿里面有什么,是不是找到了rent方法。请记住rent通过反射得到的名字m3,其他的先不管
m3 = Class.forName("proxy.Subject").getMethod("rent", new Class[0]);
}
/**
*
*这里调用代理对象的rent方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*this.h.invoke(this, m3, null);
这里的this.h代表的是父类proxy中的 protected InvocationHandler h。 this代表的是 当前的代理对象。
*/
public final void rent()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
}
分析subject.rent():
首先这里的
Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
//其实就等同于
Subject subject =new proxy0(handler);
那么:subject.rent()其实就是调用Proxy0中的rent()
public final void rent(){
this.h.invoke(this, m3, null);
return;
}
这里的this.h是什么?
public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler);
}
Proxy构造方法:
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
Proxy0继承Proxy,所以this.h代表的是Proxy中的h,而Proxy0中的构造方法super(paramInvocationHandler);但是我们又前面说过了new Proxy0(handler);
总:this.h=h=paramInvocationHandler=handler;
handler分析:
handler = new DynamicProxy(realSubject);
Subject realSubject = new RealSubject();
handler是DynamicProxy类的一个实例,我们初始化这个实例的时候我们将realSubject对象传递进去了。
private Object proxy;
public DynamicProxy(Object proxy)
{
this.proxy = proxy;
}
//所以这里的proxy就是realSubject
所以:this.h.invoke()就是调用的DynamicProxy中的invoke()方法
我们先看invoke(this,m3,null) 中的三个参数
//this不用说代表的是当前的代理对象
m3 = Class.forName("Subject").getMethod("rent", new Class[0]);
//class.forname是加载初始化Subject接口 getMethod是返回一个method对象 new Class[0]表示rent方法没有参数 一句话m3表示Subject中的rent()
我们再来看看DynamicProxy中的invoke()
public Object invoke(Object proxy, Method method, Object[] args){
method.invoke(proxy,args);
}
proxy=this; method=m3; args=null
分析method.invoke(proxy,null):上面说到初始化DynamicProxy类时传递了一个realSubject对象,proxy=realSubject。realSubject是RealSubject类的实例对象。
总体:subject.rent()结果就是: System.out.println(“I want to rent my house”);
最后:https://www.cnblogs.com/gonjan-blog/p/6685611.html 部分内容借鉴这篇文章,博主写的很详细!可以去学习学习!