代理模式

学习Spring框架时,遇到动态代理,就将代理模式整理一下

1. 什么是设计模式?

  • 设计模式:一些通用的解决固定问题的方式

2. 为什么需要代理模式?

可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。

3. 什么是代理模式

代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。java中有静态代理、JDK动态代理、CGLib动态代理的方式。静态代理指的是代理类是在编译期就存在的,相反动态代理则是在程序运行期动态生成的。

概念:

  1. 真实对象:被代理的对象
  2. 代理对象:增强真实对象
  3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的

4. 实现方式

1. 静态代理

有一个类文件描述代理模式

1)实现步骤:

  1. 代理对象与真实对象实现相同的接口
  2. 代理对象中创建真实对象
  3. 使用真实对象调用方法
  4. 增强方法

2)具体实例

接口:

1
2
3
4
5
6
7
8
9
10
package site.qujq.staticProxy;

/**
* 代理类和被代理的接口
* Created by qjq on 2020/2/23 10:51
*/
public interface SaleComputer {
public String sale(double money);
public void show();
}

被代理类(联想电脑厂商):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package site.qujq.staticProxy;

import site.qujq.proxy.SaleComputer;

/**
* 联想电脑厂商
* Created by qjq on 2020/2/23 10:53
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}

@Override
public void show() {
System.out.println("展示电脑");
}
}

代理类(经销商):

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
package site.qujq.staticProxy;


/**
* 代理 / 经销商
* Created by qjq on 2020/2/23 10:55
*/
public class StaticProxy implements SaleComputer {
// 真实对象 / 被代理对象
private Lenovo factory = new Lenovo();
@Override
public String sale(double money) {
money = money*0.8;//经销商从联想买电脑的价格
String product = factory.sale(8000);//得到的产品
return product;
}

@Override
public void show() {
factory.show();//展示的产品
System.out.println("小新pro13");
System.out.println("ThinkPad");
System.out.println("....");
}
}

在不使用代理的情况下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
package site.qujq.staticProxy;

/**
* Created by qjq on 2020/2/23 13:33
*/
public class Test {
public static void main(String[] args) {
Lenovo factory = new Lenovo();
factory.sale(8000);//相当于工厂直销
System.out.println("---------------");
factory.show();
}
}

结果是:

1
2
3
卖出一台电脑,价值:8000.0
---------------
展示电脑

使用代理的情况下进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package site.qujq.staticProxy;

/**
* 使用静态代理测试
* Created by qjq on 2020/2/23 13:17
*/
public class StaticProxyTest {
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy();
String product = staticProxy.sale(10000);
System.out.println("得到的产品:"+product);
System.out.println("-------------");
staticProxy.show();

}
}

结果是:

1
2
3
4
5
6
7
卖出一台电脑,价值:8000.0
得到的产品:联想电脑
-------------
展示电脑
小新pro13
ThinkPad
....

对比,使用静态代理和不使用静态代理,可以发现使用了代理之后,可以在被代理方法的执行前或后加入别的代码,实现诸如权限及日志的操作。

但,静态代理也存在一定的问题,如果被代理方法很多,就要为每个方法进行代理,增加了代码维护的成本。有没有其他的方式可以减少代码的维护,那就是动态代理。

2. JDK动态代理

在内存中形成代理类

  • 特点:字节码随用随创建,随用随加载

  • 作用:不修改源码的基础上对方法增强

  • 分类:

    • 基于接口的动态代理(本节)
    • 基于子类的动态代理(下一小节)

1) 实现步骤:

  1. 代理对象和真实对象实现相同的接口
  2. 代理对象 = Proxy.newProxyInstance();
  3. 使用代理对象调用方法。
  4. 增强方法

2)增强方式:

  1. 增强参数列表
  2. 增强返回值类型
  3. 增强方法体执行逻辑

3)具体实现

接口:(与静态代理一样)

1
2
3
4
5
6
7
8
9
10
package site.qujq.staticProxy;

/**
* 代理类和被代理的接口
* Created by qjq on 2020/2/23 10:51
*/
public interface SaleComputer {
public String sale(double money);
public void show();
}

被代理对象(联想电脑厂商):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package site.qujq.proxy;

/**
* Created by qjq on 2020/2/23 10:53
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}

@Override
public void show() {
System.out.println("展示电脑");
}
}

代理对象(经销商):

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
package site.qujq.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* Created by qjq on 2020/2/23 10:55
*/
public class ProxyTest {
public static void main(String[] args) {
//1. 创建真实对象
Lenovo company = new Lenovo();
//2. 创建动态代理
/*
三个参数:
1. 类加载器:真实对象.getClass().getClassLoader()
2. 接口数组:真实对象.getClass().getInterfaces()
3. 处理器:new InvocationHandler(),处理和被代理对象的方法,即方法增强的地方
*/
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(company.getClass().getClassLoader(), company.getClass().getInterfaces(),
new InvocationHandler() {
/*代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法的执行
参数:
1. proxy:代理对象(一般不用)
2. method:代理对象调用的方法,被封装为的对象
3. args:代理对象调用的方法时,传入的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj =null;
System.out.println("这里是代理。。。。");
if ("sale".equals(method.getName())){
//增强参数
Double money = (Double) args[0];//用户买电脑付的钱
money = money*0.8;//给电脑厂商的钱
System.out.println("你被增强啦");
//使用compay对象执行此方法,返回值就是真实对象方法的返回值
obj = method.invoke(company, money);
//增强返回值
obj = obj+"_鼠标垫";
}else {
obj = method.invoke(company, args);
}
return obj;
}
});

String computer = proxy_lenovo.sale(8000);//用户付钱
System.out.println(computer);//用户得到的产品
System.out.println("---------------------");
proxy_lenovo.show();//展示商品
}
}

不使用代理的测试和上面一样,不再叙述。

测试结果:

1
2
3
4
5
6
7
8
9
10
这里是代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是代理。。。。
展示电脑
小新pro13
ThinkPad
....

从上面可以看出代理类是由Proxy这个类通过newProxyInstance方法动态生成的,生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。

JDK的动态代理也存在不足,即被代理类必须要有实现的接口,如没有接口则无法使用动态代理(从newProxyInstance方法的第二个参数可得知,必须传入被代理类的实现接口),那么需要使用CGLib动态代理。

3. CGLib动态代理

CGLib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了JDK代理需要被代理对象实现某个接口的问题。对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调用时直接调用子类对象的实例,也可以达到代理的效果。CGLib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是CGLib代理为什么不需要为每个被代理类编写代理逻辑的原因。这里需要注意的是,根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。

1)细节

  • 基于子类的动态代理:

    • 涉及的类:Enhancer
    • 提供者:第三方cglib库
  • 如何创建代理对象:

    • 使用Enhancer类中的create方法
  • 创建代理对象的要求:

  • 被代理类不能是最终类

  • create方法的参数:

    • Class:字节码

      它是用于指定被代理对象的字节码。

    • Callback:用于提供增强的代码

      它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor

2)具体实现

被代理对象(电脑厂商):此时不需要实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package site.qujq.cglibProxy;


/**
* 电脑生产商
* Created by qjq on 2020/2/23 12:53
*/
public class Lenovo {
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}

public void show() {
System.out.println("展示电脑");
}
}

代理对象:

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
package site.qujq.cglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* CGLib动态代理
* Created by qjq on 2020/2/24 9:10
*/
public class CglibProxy {
public static void main(String[] args) {
final Lenovo factory = new Lenovo();
Lenovo proxy_CGLib = (Lenovo) Enhancer.create(factory.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
*
* @param proxy
* @param method
* @param args 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return 调用方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = null;
System.out.println("这里是CGLib动态代理。。。。");
if ("sale".equals(method.getName())) {//当是sale方法时
//增强参数
Double money = (Double) args[0];//用户买电脑付的钱
money = money * 0.8;//给电脑厂商的钱
System.out.println("你被增强啦");
//使用factory对象执行此方法,返回值就是真实对象方法的返回值
obj = method.invoke(factory, money);
//增强返回值
obj = obj + "_鼠标垫";
} else {//因为这里只有两个方法,所以使用else,也就是指show方法
obj = method.invoke(factory, args);//这里执行打印,有无返回值一样
System.out.println("小新pro13");
System.out.println("ThinkPad");
System.out.println("....");
}
return obj;
}
});
//测试
String computer = proxy_CGLib.sale(8000);//用户付钱
System.out.println(computer);//用户得到的产品
System.out.println("---------------------");
proxy_CGLib.show();//展示商品
}
}

结果是:

1
2
3
4
5
6
7
8
9
10
这里是CGLib动态代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是CGLib动态代理。。。。
展示电脑
小新pro13
ThinkPad
....

5.总结

对静态代理、JDK动态代理、CGLib动态代理做一个总结,静态代理的维护成本比较高,有一个被代理类就需要创建一个代理类,而且需要实现相同的接口。动态代理模式和CGLib动态代理的区别是JDK动态代理需要被代理类实现接口,而CGLib则是生成被代理类的子类,要求被代理类不能是final的,因为final类无法被继承。