概念

代理模式是Java中常用的设计模式。

其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法来提供简单的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

静态代理

指在编译时接口,代理类,被代理类等就已经确定下来了,在程序运行之前,代理类的.class文件就已经生成。

需要代理类与委托类有相同的接口

缺点:当需要代理的对象过多就需要实现大量的代理类,并且当接口增加方法,目标对象与代理对象都要进行修改

Demo:

拿学生交作业举例,一般都是学生交给课代表,课代表交给老师这种模式。此时学生就相当于委托类,课代表相当于一个学生代理类。

这里需要一个公共的接口,即学生(委托类)和课代表(代理类)的公共接口:

1
2
3
public interface Event{ 
void SubmitWork();
}

编写一个学生类(委托类),实现公共代理接口和重写接口中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class Student implements Event{
String name;

public Student(String m){
this.name=m;
}

@Override
public void SubmitWork(){
System.out.println(this.name+"提交作业");
}
}

可理解为这个类实现学生将作业交给课代表,即从委托类到代理类。

同时编写一个代理类,即课代表,实现从代理类到目标对象,同样需要公共代理接口和重写接口中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class StudentInnovation implements Event{
Student student;
int count=0; //收到的作业数量

public StudentInnovation(Student stu){
//只代理学生对象
if(stu.getClass()==Student.class){
this.student=stu;
}
}

//用于动态切换代理对象
public void setStudent(Student student){
this.student=student;
}
@Override
public void SubmitWork(){
this.student.SubmitWork();
count+=1;
System.out.println("已收作业数:"+count);
}
}

代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) throws Exception {
//被代理的学生张三,他的作业提交由代理对象monitor(课代表)完成
Student s1=new Student("张三");
Student s2=new Student("李四");
Student s3=new Student("王五");
//生成代理对象,并将张三传给代理对象
StudentInnovation monitor=new StudentInnovation(s1);
//向课代表交作业
monitor.SubmitWork();
monitor.setStudent(s2); //切换代理对象
monitor.SubmitWork();
monitor.setStudent(s3);
monitor.SubmitWork();
}
1
2
3
4
5
6
张三提交作业
已收作业数:1
李四提交作业
已收作业数:2
王五提交作业
已收作业数:3

动态代理

与静态代理原理相同,需要公共接口,委托类和代理类。区别就是动态代理是利用反射机制在运行时创建代理类

主要通过Java.lang.reflect.Proxy类与InvocationHandler接口

InvocationHandler接口:负责提供调用代理的操作

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

其中proxy为动态生成的代理对象(不是被代理的实际对象)

method表示调用的方法名(通过反射获取的Method对象)

args为调用方法的参数数组

Proxy类:负责动态构建代理类

该类提供了一个静态方法用于得到代理对象:

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

loader表示类加载器(通常使用目标接口的类加载器,用于加载动态生成的代理类)

第二个参数指代理类要实现的接口列表

第三个参数指方法调用的处理器

Demo:

委托类和公共接口不用修改:

1
2
3
public interface Event {
void SubmitWork();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student implements Event{

String name;

public Student(String n) {
this.name = n;
}

@Override
public void SubmitWork() {
System.out.println(this.name + "提交作业");
}

}

编写动态获取代理类的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxyHandler implements InvocationHandler {
private Object object;
int count=0; //收到的作业数量
//动态切换被代理对象
public void setStudent(Student student){
this.object=student;
}

//设置需要代理的对象
public ProxyHandler(Object object){
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(object,args);
this.count+=1;
System.out.println("已收作业数量:"+this.count);
return null;
}
}

其中:

1
2
3
4
5
6
7
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(object,args);
this.count+=1;
System.out.println("已收作业数量:"+this.count);
return null;
}

当调用代理对象的方法时就会触发invoke方法,触发该invoke方法的前提条件:

  • 代理对象需基于接口动态生成,并绑定 InvocationHandler

    1
    2
    3
    4
    5
    6
    InvocationHandler handler = new ProxyHandler(s1);
    Event proxy = (Event) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(), // 必须实现至少一个接口
    handler // InvocationHandler 实例
    );
  • 调用的方法必须属于代理接口

  • 方法调用必须通过代理对象发起

这里我们设置一个动态代理:

1
2
3
4
5
6
7
8
9
10
11
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
Student s1 = new Student("张三");
InvocationHandler handler = new ProxyHandler(s1); //由于是接口类型不能使用自定义方法setStudent()切换代理对象
Event proxyHello = (Event) Proxy.newProxyInstance(s1.getClass().getClassLoader(), s1.getClass().getInterfaces(), handler); //设置代理对象
proxyHello.SubmitWork();
}
}
1
2
张三提交作业
已收作业数量:1

其中调用Proxy.newProxyInstance()时,JVM首先会根据传入的interfaces接口列表,动态生成一个新的类(代理类),该类会实现所有指定的接口。然后使用传入ClassLoader类加载器加载生成的代理类字节码,将其定义为新的Java类,最后通过反射调用代理类的构造函数,传入调用处理器handler,创建代理对象。

当调用proxyHello.SubmitWork();触发invoke方法,object代理对象为proxyHello,args为null,即调用我们的proxyHello.Submitwork()

参考

https://www.cnblogs.com/gonjan-blog/p/6685611.html

https://nivi4.notion.site/Java-b97e232be04941a7860206a0e908f2cd

https://liaoxuefeng.com/books/java/reflection/proxy/index.html