都2021年了,动态代理你学会了吗

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

代理模式简介

生活中的实例

什么是代理?代理相当于一个中介,假如当前有一所大学,对全世界招生。但是生源太广泛,只能委托给招生中介,来帮助这所大学招生。中介就是学校的代理,中介就代替大学完成招生的功能。

这个代理有如下的特点:

  • 代理和学校他们做的事情是一样的,都是招生
  • 中介是学校的代理,学校是学生最终的目标
  • 中介代替学校为学生进行学校的介绍以及入学手续的办理
  • 中介不能白干活,需要收取一定的费用(功能增强)
  • 中介将学校和学生进行隔离,不让学生直接访问学校(控制访问)。

为什么要找中介呢?

  • 中介是专业的,可以减少很多不必要的麻烦
  • 学生没有能力直接访问学校,或者学校不接受学生的直接访问

开发中的实例

以上是一个实际生活中的例子,在我们的开发中,也会有类似的情况。

有一个A类,需要调用C类的方法,但是C不让A类调用,但是可以让B类进行访问。所以B类就成为了A和C之间的代理,A访问B,通过B访问C类的方法。

再比如发送短信验证码,中国移动、联通、电信具备发送短信的能力,但是我们在开发中不能直接调用它们的接口,需要通过其关联的子公司来调用,这些子公司专门负责向社会提供发送短信的功能接口。这个调用链如下:项目A调用—>子公司或者关联公司的公共接口—>调用移动/联通/电信的真实接口。
这些子公司就相当于上学实例中的中介,最终调用的真实接口就是我们的目标,也就是上学例子中的大学。

有什么好处

  • 功能增强:在原有的基础上,增加额外的功能,比如代理是要收费的,收完费才能帮你办理手续(这也不是啥好事儿啊 QAQ),收费就是新增的功能。
  • 控制访问:代理类不让你直接访问目标类,例如商家不让用户直接访问厂商,这样也会更加安全。

静态代理:

image.png

代理类是自己手动实现创建java类,表示代理类,同时你所代理的目标类是确定的。

实现流程

静态代理的实现比较简单,简单说一下大体的步骤吧。

  1. 创建一个接口,定义目标相关方法,表示目标类要实现的功能
public interface StudyInterface {
	void study();
}
复制代码
  1. 创建目标类,实现1中的接口
public class RealSchool implements StudyInterface {
	@Override
	public void study() {
		System.out.println("real study");
	}
}
复制代码
  1. 创建代理类,也实现1中的接口
public class ProxySchool implements StudyInterface{
	//持有目标类的引用
	RealSchool realSchool;
	
	public ProxySchool(RealSchool realSchool) {
		this.realSchool = realSchool;
	}

	@Override
	public void study() {
                //功能增强
		doSomethingBefore();
                //目标类调用
		realSchool.study();
                //功能增强
		doSomethingAfter();
	}
	
	public void doSomethingBefore() {
		System.out.println("before");
	}

	public void doSomethingAfter() {
		System.out.println("before");
	}

}
复制代码
  1. 创建客户端类,通过代理来调用
public class Test {
	public static void main(String[] args) {
                //创建代理类
		ProxySchool school = new ProxySchool(new RealSchool());
                //通过代理类来调用
		school.study();
	}
}
复制代码

缺陷

静态代理短短几个步骤就可以设计好了,由于其类型是定义好的,也就是说在程序运行之前,.class文件就已经存在了,其使用时运行效率还是很高的,同时我们在使用代理时,可以添加我们需要的功能进行扩展。

但是其也有很明显的缺陷,代理类和目标类需要实现同样的接口,这样就出现了大量的重复代码,如果接口增加一个方法或者修改一个方法,除了所有的目标类需要修改之外,所有的代理类也需要修改,这就直观的增加了维护的难度,其次,代理对象只能服务于一种类型的目标类,如果要服务多类型的目标类,就需要为每一种类型的目标创建响应的代理对象,如果是大型的项目,则使用静态代理所带来的麻烦则会成倍增加。

动态代理:

说完静态代理,也知道了静态代理的缺点,现在就来看看动态代理,以及动态代理是如何规避这些缺点的。

什么是动态代理?在程序的执行过程中,使用JDK的反射机制,创建代理类对象,并动态的指定要代理的目标类。也就是说,动态代理是一种创建java对象的能力。

image.png

动态代理的实现:使用java反射包中的类和接口实现动态代理的功能。有三个比较重要的类:

InvocationHandler

是一个接口,只有一个invoke()方法,这个方法表示代理对象要执行的功能代码,也就是代理类要完成的功能就写在这个方法中。代理类要完成哪些功能呢?

  1. 调用目标方法
  2. 功能增强,在调用目标方法的同时,增加功能
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
复制代码

这个方法中需要三个参数:

  1. Object proxy:jdk创建的代理对象,不需要赋值
  2. Method method:目标类中的方法,jkd提供method对象
  3. Object[] args:目标类中方法的参数,也是jdk提供

InvocationHandler表示我们的代理要干什么,怎么用呢?

  1. 创建类实现InvocationHandler接口
  2. 重写invokeP()方法,把原来在静态代理中要实现的功能,放在这个方法中。

Method

表示方法,准确的说是目标类中的方法。通过Method可以执行某个目标类的方法,method.invoke(目标对象,方法参数)

Proxy

创建代理对象,通过Porxy的类方法newProxyInstance()来创建代理对象。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    ...
    }
复制代码

看一下其三个参数:

  1. ClassLoader loader:类加载器,负责向内存中加载对象,使用反射获取对象的classLoader。A.getClass().getClassLoader(),获取目标对象的类加载器
  2. Class<?>[] interfaces:接口,目标对象实现的接口,也是通过反射来获取
  3. InvocationHandler h:我们自己实现的InvocationHandler,表示代理类要完成的功能

实现一个动态代理

  1. 创建接口,定义目标类要完成的功能
public interface StudyInterface {
        //接口中需要执行的方法
	void study();
}
复制代码
  1. 创建目标类实现接口
public class RealSchool implements StudyInterface{

	@Override
	public void study() {
		//真实目标的实现
		System.out.println("real school");
	}

}
复制代码
  1. 创建InvocationHandler接口的实现类,在invoke方法中完成指定的功能
public class MyInvocationHandler implements InvocationHandler {
	
	private Object obj;
	//目标类是活的,动态传入目标类,传入什么就给什么创建代理对象
	public MyInvocationHandler(Object obj) {
		this.obj = obj;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//功能增强
		System.out.println("study before");
                //执行目标类方法
		Object invoke = method.invoke(obj, args);
                //功能增强
		System.out.println("study after");
                //返回结果
		return invoke;
	}

}

复制代码
  1. 使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型
	public static void main(String[] args) {
		//创建目标对象
		StudyInterface study = new RealSchool();
		//创建InvocationHandlerduixiang 
		MyInvocationHandler handler = new MyInvocationHandler(study);
		//创建代理对象
		StudyInterface newProxyInstance = (StudyInterface) Proxy.newProxyInstance(study.getClass().getClassLoader(), study.getClass().getInterfaces()
				, handler);
                //com.sun.proxy.$Proxy0
		System.out.println(newProxyInstance.getClass().getName());
		//通过代理对象调用方法
		newProxyInstance.study();
	}
复制代码

动态代理原理

System.out.println(newProxyInstance.getClass().getName());
复制代码

在上边的代码中我们打印一下newProxyInstance代理类的name:com.sun.proxy.$Proxy0
这个Proxy0是什么呢?

public final class $Proxy0 extends Proxy implements StudyInterface
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  * 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型
  *
  * super(paramInvocationHandler),是调用父类Proxy的构造方法。
  * 父类持有:protected InvocationHandler h;
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //这个静态块本来是在最后的,把它拿到前面来,方便描述
   static
  {
    ...
      //看看这儿静态块儿里面有什么,是不是找到了study方法。study通过反射得到的名字m3
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.StudyInterface").getMethod("study", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    ...
  }
 
  /**
  * 
  * 这里调用代理对象的study方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  * this.h.invoke(this, m3, null)
  */
  public final void study()
  {
     ...
      this.h.invoke(this, m3, null);
      return;
     ...
  }

}
复制代码

jdk为我们生成一个proxy0(0是编号,有多个代理类就依次递增),这个类文件在内存中,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。

我们可以将InvocationHandler看做一个中介类,中介类持有一个被代理对象(我们传入的目标对象),在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

调用流程

image.png

动态代理问答

  • 动态代理动态在哪里

动态代理之所以称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有Proxy类,它是根据需要,从你传入的接口集创建的。

  • 如何知道某个类是不是代理类?

通过静态方法isProxyClass,返回值为true,表示是一个动态代理类。

boolean proxyClass = Proxy.isProxyClass(handler.getClass());
复制代码
  • 对于我能传入newProxyInstance()的接口类型,有没有什么限制?

首先,我们需要传给newProxyInstance一个接口数组,此数组中只能有接口,不能有类。如果接口不是public,就必须属于同一个package,不同的接口内,不可以有名称和参数完全一样的方法。

  • 代理和装饰者模式有什么区别吗

简单来说装饰者模式会为对象增加行为,是对原始对象的包装,而代理除了会对原有功能进行额外的功能添加,还可以对保护进行目标,避免不必要的访问。

总结

代理模式在实际工作中经常会用到,是一个非常重要并且实用的设计模式。它帮助我们对目标类进行扩展同时又控制对目标类的访问。动态代理规避了静态代理的很多问题,更加的灵活的同时,不会因为业务逐渐庞大而变得难以管理。在Android开发中,retrofit中就使用了动态代理来进行网络请求的封装,这也是动态代理的一个很重要的体现。希望通过本文可以让你对代理模式有一个更清晰的认识。

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享