Java中的代理模式

什么是代理

  假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子

代理模式的分类

  • 远程代理模式:为不同地理的对象提供局域网代表对象(例子:通过远程代理可以监控各个店铺,使之可以直观的了解店里的情况)

  • 虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建 (类似于新闻网站加载时,图片加载不出来先用一张空的图片代替)

  • 保护代理:控制用户的访问权限

  • 智能引用代理:提供对目标对象提供额外的服务或者减少特定的服务(火车票代售处)

两种实现方式

静态代理

  代理和被代理对象在代理之前是确定的。他们都是实现相同的接口或者继承相同的抽象类。就比如已知一个汽车类并且也只到要有一个汽车的代理类,且它们实现相同接口或者继承相同抽象类。

继承的方式实现静态代理

  代理者继承被代理者,对所代理的方法进行改造

  需求:一个汽车有一个move方法,方法中打印了移动中三个字并有一个行驶时间(通过Thread.sleep方法),现在我们需要使用代理类算出移动时间并打印消息。

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
//提供一个代理类和被代理类需要实现的接口
public interface Movable {
void move();
}

//创建一个Car并实现Movable接口
public class Car implements Movable{

public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("移动中。。。");
}

}

//使用继承方式
//代理类继承被代理类,并重写其方法
public class CarTimeProxy extends Car{

@Override
public void move() {
long startTime=System.currentTimeMillis();
System.out.println("开始移动。。。");
super.move();
long endTime=System.currentTimeMillis();
System.out.println("结束移动。。。,一共移动"+(endTime-startTime)+"毫秒!");
}
}

//客户端只需要创建代理类并实行代理类重写被代理类的方法
public class Client {

public static void main(String[] args) {
CarTimeProxy carTimeProxy=new CarTimeProxy();
carTimeProxy.move();
}

}

聚合的方式实现静态代理

  继承同一个接口,且代理对象持有被代理的对象

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
//使用聚合方式构造代理对象
public class CarTimeProxy implements Movable{

private Movable movable;

public CarTimeProxy(Movable movable) {
this.movable = movable;
}

public void move() {
long startTime=System.currentTimeMillis();
System.out.println("开始移动。。。");
movable.move();
long endTime=System.currentTimeMillis();
System.out.println("结束移动。。。,一共移动"+(endTime-startTime)+"毫秒!");
}
}

//客户端先要构造car,再将car传入代理对象
public class Client {
public static void main(String[] args) {
Car car=new Car();
CarTimeProxy carTimeProxy=new CarTimeProxy(car);
carTimeProxy.move();
}
}

聚合方式和继承方式哪个好呢?

  这时我们又新增了一个需求,我们需要给汽车再添加一个日志代理类,并在移动前后打印日志信息。

  首先我们使用继承方式,那么我们只需要再创建一个日志代理类并继承于时间代理类就好了,代码如下:

1
2
3
4
5
6
7
8
9
10
//日志代理类
public class CarTimeAndLogProxy extends CarTimeProxy {

@Override
public void move() {
System.out.println("开始打印日志");
super.move();
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
//创建日志代理类并实现Movable接口
public class CarLogProxy implements Movable {

private Movable movable;

public CarLogProxy(Movable movable) {
this.movable = movable;
}

public void move() {
System.out.println("开始打印日志");
movable.move();
System.out.println("结束打印日志");
}
}

//这时候我们只需要调换一下代理类的顺序就好
public static void main(String[] args) {
Car car=new Car();
CarLogProxy carLogProxy=new CarLogProxy(car);
CarTimeProxy carTimeProxy=new CarTimeProxy(carLogProxy);
carTimeProxy.move();
}

动态代理

  来自静态代理的思考:静态代理是对特定类产生代理对象,但是就日志打印这个代理功能而言,成千上百的类都会用到日志打印,那么这时候如果我们使用静态代理的话,我们就要创建成千上百的代理类,这样会非常麻烦。

  所以出现了动态代理:动态产生代理,实现对不同类和不同方法的代理。

JDK动态代理

  1. 创建事务处理器,实现InvocationHandler接口,覆写invoke方法

    参数说明:

    proxy 代理对象

    Method 被代理对象的方法

    args 方法的参数

    1
    invoke(Object proxy,Method method,Object[] args)
  2. 创建被代理的类以及接口

  3. 调用Proxy的静态方法,创建代理类(这个类是实现了被代理类的接口的)

    参数说明:

    loader:被代理类的类加载器

    interfaces:被代理类实现的接口

1
newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
  1. 事务处理器

  2. 通过代理调用被代理的方法

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建一个Handler实现InvocationHandler
//并重写invoke方法和创建Object对象
存放被代理的对象(用来)
public class TimeHandler implements InvocationHandler {
private Object target;

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

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime=System.currentTimeMillis();
System.out.println("开始移动。。。");;
method.invoke(target);
long endTime=System.currentTimeMillis();
System.out.println("结束移动。。。,一共移动"+(endTime-startTime)+"毫秒!");
return null;
}
}

cglib动态代理

  JDK只能代理实现了接口的类,cglib针对类来实现代理,对指定目标类产生一个子类,通过方法拦截技术拦截所有调用父类方法地调用。也因为cglib使用了继承的方式,所以它不能对final修饰的类进行代理。

  需求:实现对火车类的日志代理

  代码如下:

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

private Enhancer enhancer = new Enhancer();

public Object getProxy(Class clazz){
//设置创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);

return enhancer.create();
}

/**
* 拦截所有目标类方法的调用
* obj 目标类的实例
* m 目标方法的反射对象
* args 方法的参数
* proxy代理类的实例
*/
@Override
public Object intercept(Object obj, Method m, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("日志开始...");
//代理类调用父类的方法
proxy.invokeSuper(obj, args);
System.out.println("日志结束...");
return null;
}

}


public class Train {

public void move(){
System.out.println("火车行驶中...");
}
}


public class Client {

/**
* @param args
*/
public static void main(String[] args) {

CglibProxy proxy = new CglibProxy();
Train t = (Train)proxy.getProxy(Train.class);
t.move();
}

}

参考

  本博文参考于Java的三种代理模式和慕课网的模式的秘密——代理模式课程

-------------本文结束感谢阅读-------------