代理模式(Proxy)

关于代理模式的介绍,以及动态代理(jdk、cglib)的介绍

代理模式

简要介绍

代理,就是代理人

当A想找B做一件事,但是可能B在十万八千里之外,联系不上,但是C又能联系上B,而且A也能找到C,所以A找C办这件事,但真正做事的人是B,C只是个代理人

一句话:代理人可以控制客户端对其他对象的访问

代理种类:

  • 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

哪里用到这个模式了?

  1. 使用过的一些中间件例如;RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。
  2. 另外像我们常用的MyBatis,基本是定义接口但是不需要写实现类,就可以对xml或者自定义注解里的sql语句进行

类图

在上图中,Client就是A,Proxy就是C,RealSubject就是B,也就是真正做事的人

例子

定义一个接口叫HelloService,然后定义一个sayHello方法,我们通过代理来调用这个方法,来实现在目标对象执行sayHello方法的前后记录日志

HelloService

1
2
3
4
5
6
7
public interface HelloService {

/**
* say hello
*/
void say();
}

实现类(具体做事的人):HelloServiceImpl

1
2
3
4
5
6
public class HelloServiceImpl implements HelloService{
@Override
public void say() {
System.out.println("hello!");
}
}

代理:HelloServiceProxy

可以看到我们就是在这里直接调用真正做事的人执行方法,并且前后还可以加额外的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloServiceProxy implements HelloService{

private final HelloService target;

public HelloServiceProxy(HelloService target) {
this.target = target;
}

@Override
public void say() {
System.out.println("记录日志");
target.say();
System.out.println("清理数据");
}
}

客户端:Main

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
// 代理对象
HelloServiceProxy helloServiceProxy = new HelloServiceProxy(target);
helloServiceProxy.say();
}
}

执行结果:

1
2
3
记录日志
hello!
清理数据

缺点

经过上面的介绍,我们可以发现,一个代理类只能为一个接口服务,平时开发是有N多个接口的,肯定会产生很多的代理类的,所以我们就会想,有没有可能一个代理类,可以完成所有代理的功能,所以就有了动态代理

动态代理

JDK

JDK为我们提供了动态代理的支持,我们的代理类需要实现java.lang.reflect.InvocationHandler接口并且调用java.lang.reflect.Proxy类的newProxyInstance方法创建一个代理实例,然后调用具体的方法,其实就是通过反射生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

在上述代码基础上加入MyInvocationHandler:

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
public class MyInvocationHandler implements InvocationHandler {

private final Object target;

public MyInvocationHandler(Object target) {
this.target = target;
}

/**
* 执行具体方法
* @param proxy 代理类
* @param method 目标具体的方法
* @param args 方法参数
* @return 方法返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印方法执行信息
System.out.println(target.getClass().getName() + "." + method.getName());
// 前置通知
System.out.println("记录日志");
Object result = method.invoke(target, args);
System.out.println("清理数据");
return result;
}


public Object getProxy() {
// target.getClass().getInterfaces() 这个是获取目标对象实现的接口,也就是jdk的动态代理的对象必须得实现至少一个接口才可以被代理
// 第三个参数是一个InvocationHandler,可以写到方法里,也可以以这种形式写下来,实现接口然后this
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
}

关于Proxy.newProxyInstance方法参数的说明:

  1. 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
  2. 第一个参数指定产生代理对象的类加载器(就是要为谁加代理,就传谁的class),需要将其指定为和目标对象同一个类加载器
  3. 第二个参数要实现和目标对象一样的接口(可以看出jdk的动态代理,其实就是把我们手动创建和代理对象相同接口的代理类自动化了),所以只需要拿到目标对象的实现接口
  4. 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法

下面使用一下这个动态代理,看看好使不:

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
MyInvocationHandler invocationHandler = new MyInvocationHandler(target);
HelloService proxy = (HelloService) invocationHandler.getProxy();
proxy.say();
}
}

结果:

1
2
3
4
com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据

cglib

jdk动态代理的局限性:只能对实现了接口的类进行代理,对于没有实现接口的类无法代理

cglib对于没有实现接口的类也可以进行代理

这个是咋动态生成代理的呢?他利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

cglib需要引入依赖:

1
2
3
4
5
6
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>

在上述基础,再加一个CglibProxyFactory类:

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
public class CglibProxyFactory implements MethodInterceptor {

/**
* 工具类
*/
private final Enhancer enhancer = new Enhancer();

private final Object target;

public CglibProxyFactory(Object target) {
this.target = target;
}

public Object getProxy() {
// 设置要创建子类的父类
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 打印方法执行信息
System.out.println(target.getClass().getName() + "." + method.getName());
// 前置通知
System.out.println("记录日志");
Object result = method.invoke(target, args);
System.out.println("清理数据");
return result;
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
CglibProxyFactory helloServiceProxyFactory = new CglibProxyFactory(helloService);
HelloService helloServiceProxy = (HelloService) helloServiceProxyFactory.getProxy();
helloServiceProxy.say();
// 找一个没有实现接口的类进行代理,上面的helloService实现了HelloService接口
Student student = new Student();
CglibProxyFactory studentProxyFactory = new CglibProxyFactory(student);
Student studentProxy = (Student) studentProxyFactory.getProxy();
studentProxy.study();
}
}
1
2
3
4
5
6
7
8
com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据
com.hc.basics.dynamicproxy.cglib.Student.study
记录日志
student study!
清理数据