JAVA动态代理

代理

最近在看JAVA动态代理的东西,写了这篇文章记录一下自己的理解,不当之处,欢迎指正!

1 现实中的代理

代理,在我们平时的生活中就有很多的体现,在我们的平时生活中,经常会听到中介这个词,中介就是一种代理。

房屋中介代理房东的职责,负责向租户看房子,签合同,收房租等等,房东在有代理的情况下,所需要的任务就比较简单了:提供房子,收取利润。

再如留学中介也是一种代理,留学中介帮助国际学校,面向全世界招生,帮助学校招生,同时帮忙留学生了解学校。

我理解的代理特点是:

  1. 代理和代理目标要做的事情是一致的:如留学中介(代理)和学校(代理目标)要做的事情都是:招生
  2. 但代理和代理目标要做的事情又并非完全一样的,代理常常需要做一些额外的事情:譬如房屋中介和房东虽然要做的最主要的事情是出租房子,但是房屋中介还要做一些额外的事情:带租户看房子,聊价格,签合同,甚至有时候收房租也是由中介收取的。

代理模式:在开发中,在某种情况下,在一个对象(A)不适合或不能直接访问另一个对象©时,我们需要在这两个对象直接找到一个代理(B),这就是代理模式。

2 为什么需要代理?

从现实的角度看

  1. 代理中介是专业的,通过代理中介替我们做一些我们不熟悉的事情是方便的,可靠的。

  2. 还有一个重要的原因是:有时候客户和目标直接是无法直接取得联系的,只能通过代理来间接取得联系

还是举上面留学的例子:首先留学中介相对于学生和家长来说,对于国外的学校是更加了解的,是更“专业"的,其次,有些国外的学校根本就不允许个人的访问,只允许通过留学代理中介的方式申请留学。

而在我们的开发中,也会出现相似的情形,譬如有一个A类和C类,A类想访问C类,但C类不允许A类直接访问,而只允许B类直接访问。在这种情况下,A类只能通过B类访问到C类,B类成为了A类和C类之间的中介。

3 使用代理模式的作用?

  1. 功能增强:在原有的功能上,需要增加额外的功能。在代理模式中我们称为功能增强。如房屋中介代理代理房东出租房子,比需要比房东做更多的事情:如看房子,服务等等,这就是功能增强。
  2. 控制访问:通过代理类可以直接访问到代理目标。

4 实现代理的方式

1 静态代理

静态代理中,代理类是需要自己手工实现的,我们自己创建一个JAVA类,表示代理类,同时代理目标也是确定的

如一个顾客想要买一双鞋,最直接的方式当然就是直接找产商买咯,直接到李宁,安踏,361说我要跟你厂买一双鞋,然而这种情况产商通常是不会理你的。。。。。 所以中间就需要有代理,比如时顾客可以去淘宝上买,可以向微商买,淘宝微商在这里就相当于中间代理。

我们用代码模拟买鞋这样一个过程:

  1. 首先创建一个产商接口,产商接口之有一个方法,就是sell,这是产商(代理目标)和代理如下都要做的事情卖鞋
1
2
3
public interface Sellshoe {
public void sell();
}
  1. 然后我们创建实际的产商,实际的产商需要实现产商接口,我们实现一个Anta产商,如下:
1
2
3
4
5
6
public class Anta implements Sellshoe {
@Override
public void sell() {
System.out.println("Anta 卖出一双鞋");
}
}
  1. 产商创建完毕后,我们还需要一个代理类,因为顾客是无法直接向产商买鞋的,我们这里假设代理类为淘宝,淘宝同样要实现Sellshoe接口,因为淘宝要做的事情就是卖鞋。

    同时淘宝要有一个代理目标,这里为Anta,表明淘宝有卖Anta鞋(也就是淘宝代替产商,将鞋卖给顾客),同时淘宝不是单词地说从产商拿鞋后就直接卖给顾客,淘宝这里我们假设只多做了一件事情检查鞋的质量(功能增强),实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
public class Taobao implements Sellshoe{

private Anta producer=new Anta();

@Override
public void sell() {
producer.sell();
System.out.println("淘宝检查鞋的质量");
System.out.println("淘宝将鞋卖给顾客");
}
}
  1. 这里我们就可以通过淘宝买到安踏鞋了,淘宝代理实现了控制访问功能增强,顾客买鞋变成了如下:

    1
    2
    3
    4
    5
    6
    public class Test {
    public static void main(String[] args) {
    Taobao taobao=new Taobao(); //找到淘宝店
    taobao.sell(); //淘宝店卖鞋
    }
    }

    结果为:

    Anta 卖出一双鞋
    淘宝检查鞋的质量
    淘宝将鞋卖给顾客

    顾客成功地买到了鞋。

这就是静态代理的实现,静态代理的优点是:实现比较简单,也容易理解,但它的缺点在于:1. 一个代理目标就会产生一个代理类,当目标类增加了,代理类也需要成倍的增加,代码量翻倍。 2. 当我们的功能接口如Sellshoe中功能增加了,或许修改了,那么所有的实现类(比如Anta)和所有的代理类都需要修改,影响较多,耦合度高。

2 动态代理

相较于静态代理一个代理目标就会产生一个代理类,没当增加一个代理目标,代码量就会翻倍的缺点,动态代理通过JAVA的反射机制,可以不用创建类,就可以创建代理类对象,不需要写JAVA文件。

动态代理的实现基于JAVA反射包的类和接口

分别是InvocationHandler,Method,Proxy

首先需要强调的是基于jdk的动态代理是基于接口的,也就是目标类必须有一个接口表示

首先同静态代理一样,我们使用Sellshoe作为接口,代码如下:

1
2
3
public interface Sellshoe {
public void sell();
}

然后实现Sellshoe的三个目标类,实现目标接口,也就是三家产商Anta,Lining,Nike,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Anta implements Sellshoe {
@Override
public void sell() {
System.out.println("Anta 卖出一双鞋");
}
}

public class LiNing implements Sellshoe {
@Override
public void sell() {
System.out.println("LiNing 卖出一双鞋");
}
}

public class Nike implements Sellshoe {
@Override
public void sell() {
System.out.println("Nike 卖出一双鞋");
}
}

首先说明一个Method反射类的作用,在JAVA反射中,我们可以通过

1
Method method = Sellshoe.class.getMethod("sell",null)

获取到对应的Method对象,在这里我们传输的方法名为“sell",没有形参,所以第二个参数为null。

我们获取到了Method对象,如何去调用该方法呢,答案是使用Method对象的invoke函数去调用该方法

我们通过调用

1
method.invoke(Object object,Object... args)

即可以调用object对象的sell方法,args为sell方法的参数,我们依次传入一个Anta,Nike,LiNing对象,则method.invoke会调用各自对象的sell函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = Sellshoe.class.getMethod("sell",null);
Anta anta=new Anta();
Nike nike=new Nike();
LiNing liNing=new LiNing();
method.invoke(anta);
method.invoke(nike);
method.invoke(liNing);
}
}
//结果为
/*
Anta 卖出一双鞋
Nike 卖出一双鞋
LiNing 卖出一双鞋
*/

总结:也就是说,我们通过了接口Sellshoe获取到了sell方法对应的Method对象,之后我们传入Sellshoe接口的不同实现类给Method对象的invoke方法,invoke方法会根据实现类的不同找到对应实现类的那个sell方法

然后我们需要自定义一个调用处理程序实现InvocationHandler接口,然后重写InvocationHandler中的invoke方法,在invoke方法实现我们代理类的目的 1. 目标方法引用 2. 功能增强,

关于代理实例调用处理程序的关系:

每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并发配到其处理程序的invoke方法。

先贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler {
private Object target; //target表示代理的真实对象,这里用Object类型,可以接纳所有的真实对象
public MyHandler(Object object){
this.target=object; //设置代理的真实对象
}

/*
invoke方法表示 代理对象要执行的功能代码

首先看一下参数
Object provy:jdk创建的代理对象,无需赋值
Method method: jdk提供的method对象,通过反射获取得到
args:method方法的参数 也是由jdk提供的

因为invoke方法是代理对象要完成的功能代码,代理对象要完成的还是那两件事
1. 目标方法引用:代理对象始终只是目标对象的代理,最终还是要调用目标方法引用,目标方法引用为
method.invoke();
前面起到,method.invoke()会根据传入的具体目标类,在这里是target,调用具体的方法。这种方法在我看来就是实现了代码的复用,再也不需要写一个实现类就写一个代理类了,大大减少了代码量。代理类现在实际上代理的是Sellshoe接口,而不是类,所有实现了Sellshoe的目标类都可以通过代理类访问到。

2. 功能增强,功能增强可以在执行目标方法引用之前 增加一些业务before(),也可以在执行目标方法引用之后增加一些业务after

*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(target,null);
after();
return null;
}

private void after() {
System.out.println("do somethings after");
}

private void before() {
System.out.println("do somethings before");
}
}

前面提到

每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并发配到其处理程序的invoke方法。

调用处理程序有了,接下来就是如何去获取一个代理实例

代理实例我们通过JAVA的Proxy类获取,同样是通过反射机制

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

这是newProxyInstance的原型,我们首先需要指定该代理类代理的是哪个具体的实现类,我们这里假定代理类代理的是Anta类,我们需要按照newProxyInstance一次传入参数,loader为代理的那个具体的实现类的类加载器,interfaces为代理的那个具体的实现类的接口,而InvocationHandler为与该代理实例关联对应的调用处理程序

实现代码如下:

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Anta anta=new Anta(); //具体的代理目标,也就是要被代理目标类
MyHandler myHandle=new MyHandler(anta); //获得一个与代理实例对应的 **调用处理程序**
Sellshoe proxy= (Sellshoe) /* 产生一个代理类 */ Proxy.newProxyInstance(anta.getClass().getClassLoader(), anta.getClass().getInterfaces(),myHandle);
proxy.sell(); //然后我们就可以使用我们的代理类去完成对应的业务了
}
}

我的理解是:通过动态代理,只要我们确定了代理目标类,就可以获得一个与代理目标类对应的调用处理程序,然后用代理目标类调用处理程序又可以得到一个与代理目标类对应的代理实例,之后我们就通过代理实例就可以实现我们对代理目标类的业务需求。

动态代理展现出来的优势是:

  1. 不需要自己手动写代理类,只需要一个调用处理程序,该调用处理程序接收一个具体目标实现类为,设置为自己的target
  2. 可以给不同的目标实现类随时设置代理,只需要给出目标实现类,生成调用处理程序,获得实例对象即可,很灵活方便。

动态代理执行过程如下:

首先动态创建代理对象,通过代理对象调用方法时实际上会跳转到调用处理程序的invoke方法中,invoke按顺序先执行在我们调用具体对象的方法前需要增加的业务,然后通过method.invoke调用具体对象的方法,之后是调用方法后需要增加的业务,然后返回结果,重新回到proxy.sell()的下一语句。

实现动态代理的步骤:

  1. 创建目标类接口,说明目标类需要完成的功能,注意目标类接口的必须的。
  2. 目标类接口的实现类,可以多个
  3. 调用处理程序InvocationHandler 重写Invoke方法,before()业务,after()业务
  4. 根据给定要代理的具体目标实现类,生成一个调用处理程序,并通过Proxy.newInstance()创建代理实例,然后代理实例就可以代替目标类执行业务了。

至于动态代理更加细节的如方法调用的时机还没有深入探究,还有待于后续的学习~

参考资料:

Java-JDK动态代理(AOP)使用及实现原理分析

文章作者: luo
文章链接: https://luo41.top/2021/12/10/JAVA动态代理/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 luo's Blog