简单谈谈动态代理

A blog for the Dynamic-Proxy

Posted by if on 2022-01-17
Estimated Reading Time 10 Minutes
Words 2.4k In Total

简单谈谈动态代理

什么是代理模式?

为其他对象提供一个代理以控制对某个对象的访问。

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

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

其实就是代理类被代理类进行预处理消息过滤消息并在此之后将消息转发被代理类,之后还能进行消息的后置处理

代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途,例如记录日志等

有哪些代理模式

关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式——代理模式

而对于代理,根据创建代理类的时间点,又可以分为静态代理动态代理

  • 静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口被代理类代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
  • 动态代理:代理类在程序运行时才被创建。代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的

各代理模式的实现

前置场景说明

我们以生活中的场景为例子

假设现在有一个Teacher教师接口,张老师类与王老师类

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
public interface Teacher {
void speak();
void rest();
void standup(String studentName);
}
public class Wang implements Teacher {
@Override
public void speak() {System.out.println("王老师讲话。。。");}

@Override
public void rest() {System.out.println("王老师休息。。。");}

@Override
public void standup(String studentName) {
System.out.println("王老师让"+studentName+"同学站起来");
}
}
public class Zhang implements Teacher {
@Override
public void speak() {System.out.println("张老师讲话。。。");}

@Override
public void rest() {System.out.println("张老师休息。。。");}
@Override
public void standup(String studentName) {
System.out.println("张老师让"+studentName+"同学站起来");
}
}

我们平时让其执行,都是使用接口的实现类,然后实例化执行

1
2
3
4
5
6
7
8
9
System.out.println("----------接口实现----------");
Teacher zhang = new Zhang();
zhang.speak();
zhang.rest();
zhang.standup("小张");
Wang wang = new Wang();
wang.speak();
wang.rest();
wang.standup("小王");

这样非常方便就能执行方法

可是这也会产生一些问题

问题提出

如果我想让方法执行前后都记录成日志,应该怎么做?

俗话说得好,加一层能解决90%的问题,如果还是无法解决,那么就再加一层!

在每个方法具体代码前后都执行一次记录方法是不可取的

因为2个实现类共6条方法就需要写下12条重复代码

如果有10个类共有100个方法,那岂不是需要写下100条重复代码,这样明显是不易于维护的

静态代理

我们为zhangwang都添加一个代理类

ZhangProxy为例

这样就可以解决掉前后记录日志的问题了,但是重复代码的问题依然存在:用泛型解决!

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
public class ZhangProxy implements Teacher {
private Zhang zhang;

public ZhangProxy(Zhang zhang) {
this.zhang = zhang;
}

@Override
public void speak() {
System.out.println("before speak");
zhang.speak();
System.out.println("after speak");
}

@Override
public void rest() {
System.out.println("before rest");
zhang.rest();
System.out.println("after rest");
}

@Override
public void standup(String studentName) {
System.out.println("before standup");
zhang.standup(studentName);
System.out.println("after standup");
}
}

System.out.println("----------静态代理----------");
ZhangProxy zhangProxy = new ZhangProxy(new Zhang());
zhangProxy.speak();
zhangProxy.rest();
zhangProxy.standup("小静");
WangProxy wangProxy = new WangProxy(new Wang());
wangProxy.speak();
wangProxy.rest();
wangProxy.standup("小王");

泛型静态代理

这样就不需要重复编写多个代理类与前后记录日志的方法了

减少了大量的重复代码

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class AllProxy<T> implements Teacher {
private T t;
private Method[] methods;
private Map<String,Method> temp;

public AllProxy(T t) {
this.t = t;
this.methods = t.getClass().getMethods();
temp=new HashMap<>();
for (Method method : methods) {
temp.put(method.getName(),method);
}
}

@Override
public void speak() {
System.out.println("before speak");
try{
temp.get("speak").invoke(t);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("after speak");
}

@Override
public void rest() {
System.out.println("before rest");
try{
temp.get("rest").invoke(t);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("after rest");
}

@Override
public void standup(String studentName) {
System.out.println("before standup");
try{
temp.get("standup").invoke(t,studentName);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("after standup");
}
}

System.out.println("----------泛型静态代理----------");
AllProxy<Wang> wangAllProxy = new AllProxy<>(new Wang());
wangAllProxy.speak();
wangAllProxy.rest();
wangAllProxy.standup("小王");
AllProxy<Zhang> zhangAllProxy = new AllProxy<>(new Zhang());
zhangAllProxy.speak();
zhangAllProxy.rest();
zhangAllProxy.standup("小张");

新的问题

这样虽然将重复编写代理类的问题解决了

可是实现了多个接口后,重写的方法会越来越多,不满足开闭原则:程序对修改关闭,对扩展开放

那么就应该使用动态代理来解决这个问题

动态代理

具体步骤

  1. 通过实现 InvocationHandler 接口并重写invoke方法创建自己的调用处理器
  2. 通过为 Proxy 类的newProxyInstance方法,指定类加载器 ClassLoader 对象、一组被代理接口的 Class 数组与调用处理器的实例化对象来创建动态代理类实例
  3. 通过反射机制获得动态代理类的构造函数var5.getConstructor(constructorParams),其唯一参数类型是调用处理器接口类型constructorParams = new Class[]{InvocationHandler.class}
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入var6.newInstance(var2)

创建代理类

创建一个动态代理类DynamicProxy,实现InvocationHandler接口,重写invoke方法

Object res = method.invoke(obj, objects);通过反射执行method,其中obj被代理类的实例化对象,也就是上文中的Zhang或者Wang的实例化对象

被代理类执行的所有方法都被替换成执行了invoke方法,减少了99%的重复代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DynamicProxy<T> implements InvocationHandler {

private T obj;

public DynamicProxy(T obj) {
this.obj = obj;
}

@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("before "+method.getName());
Object res = method.invoke(obj, objects);
System.out.println("after "+method.getName());
return res;
}
}

Proxy.newProxyInstance

Proxy.newProxyInstance方法有三个参数:

  1. ClassLoader loader : 用哪个类加载器去加载代理对象(等下用Factory工厂类去实例化代理,即工厂的class当做加载器)
  2. Class<?>[] interfaces: 动态代理类需要实现的接口(就是被代理的接口,Teacher这种)
  3. InvocationHandler proxy:InvocationHandler的实现类,也就是代理类
1
2
3
4
5
6
7
8
9
10
11
12
//实例化被代理类的对象
Zhang zhang = new Zhang();
//将被代理类的对象传给代理类的对象
DynamicProxy<Zhang> zhangDynamicProxy = new DynamicProxy<>(zhang);
//实例化被代理的接口类(必须以多态形式,声明为父类接口类型)
Teacher teacher =
(Teacher)Proxy.newProxyInstance(
Main.class.getClassLoader(),
test.getClass().getInterfaces(),
zhangDynamicProxy);
//执行方法
teacher.standup("小代");

before standup
张老师让小代同学站起来
after standup

动态代理工厂

上述执行实例化代理类有点麻烦了,如果我们用个代理工厂,以泛型的形式去实例化代理呢?

classLoader用factory本身的:this.getClass().getClassLoader()

Class<?>[] interfaces用泛型T的:t.getClass().getInterfaces()

InvocationHandler还是用我们自定义的代理类模板:new DynamicProxy(t),别忘了将泛型对象传入

1
2
3
4
5
6
7
8
9
10
11
12
public class ProxyFactory {
public <T> T getInstance(T t) {
//类加载器,用工厂本身的就行
ClassLoader classLoader = this.getClass().getClassLoader();
//被代理的接口数组
Class<?>[] interfaces = t.getClass().getInterfaces();
//动态代理类模板
DynamicProxy proxy = new DynamicProxy(t);
//实例化并返回代理对象
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}

那么我们实例化代理时,只需要一句话

1
Teacher mrZhang = new ProxyFactory().getInstance(new Zhang());

其实这个代理工厂类不仅仅可以实例化Teacher的代理,其他的类其实也可以代理

因为用了泛型,而无需指定具体类型

所以如果我们新建了一个Student接口与接口的实现类Chen

一样可以代理执行,满足开闭原则无需修改任何代码,只需在调用时传入被代理的对象即可

1
2
3
4
5
6
7
8
9
10
11
12
//工厂方式
Teacher mrZhang = new ProxyFactory().getInstance(new Zhang());
mrZhang.speak();
mrZhang.rest();
mrZhang.standup("小张");
Teacher mrWang = new ProxyFactory().getInstance(new Wang());
mrWang.speak();
mrWang.rest();
mrWang.standup("小王");
//不仅可以代理Teacher接口及其实现类,还可以代理其他的接口
Student chen = new ProxyFactory().getInstance(new Chen());
chen.discuss();

before speak
张老师讲话。。。
after speak
————————
before discuss
小陈同学与同桌讨论题目
after discuss


本个人博客提供的内容仅用于个人学习,不保证内容的正确性。通过使用本站内容随之而来的风险与本站无关!