静态代理

静态代理的UML图

被追求者:

1
2
3
4
5
6
7
8
9
10
11
public class SchoolGirl {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
public interface GiveGift {
void GiveDolls();
void GiveFlowers();
void GiveChocolates();
}

真正的追求者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Pursuit implements GiveGift{
SchoolGirl mm;

public Pursuit(SchoolGirl mm) {
this.mm = mm;
}

@Override
public void GiveDolls() {
System.out.println(mm.getName() + "送你dolls");
}

@Override
public void GiveFlowers() {
System.out.println(mm.getName() + "送你flower");
}

@Override
public void GiveChocolates() {
System.out.println(mm.getName() + "送你chocolate");
}
}

代理者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Proxy implements GiveGift{
Pursuit gg;
public Proxy(SchoolGirl mm) {
gg = new Pursuit(mm);
}

@Override
public void GiveDolls() {
gg.GiveDolls();
}

@Override
public void GiveFlowers() {
gg.GiveFlowers();
}

@Override
public void GiveChocolates() {
gg.GiveChocolates();
}
}

使用代理方法调用:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
SchoolGirl jiaojiao = new SchoolGirl();
jiaojiao.setName("娇娇");

//这样的话,根本不知道是谁送的礼物
Proxy daili = new Proxy(jiaojiao);
daili.GiveChocolates();
daili.GiveDolls();
daili.GiveFlowers();
}

动态代理

动态代理的UML图

​ Java在java.lang.reflect中有自己的代理支持,利用这个包可以在运行时动态地创建一个代理类,实现一个或者多个接口,并将方法的调用转发到指定的类。因为实际的代理类是在运行时创建的,我们称这个Java技术为:动态代理。

​ 因为Java已经为我们创建了Proxy类,所以需要有办法来告诉proxy类我们需要做什么。我们不能像之前一样将代码放在Proxy类中,因为Proxy不是我们直接实现的了。既然代码不能放在Proxy中,那么放在哪里呢?放在InvocationHandle中。InvocationHandle的工作是响应代理的任何调用。可以把InvocationHandle看成是代理收到方法调用后,请求实际工作的对象。

对象村的配对例子

  每个城镇都需要配对服务,不是吗?你负责帮对象村实现约会服务系统。你又一个好点子,就是在服务中加入“Hot”和“Not”的评鉴,“Hot”就表示喜欢对象,“Not”表示不喜欢。你希望这套系统能鼓励你的顾客找到可能的配对对象,这也会让事情更有趣。

  你的服务系统涉及到一个Person bean,允许设置或取得一个人的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface PersonBean {
/**
* 这里我们可以取得人的名字,性别、兴趣和HotOrNot评分(1到10)
* @return
*/
String getName();
String getGender();
String getInterests();
int getHotOrNotRating();

/**
* 通过调用各自的方法,我们也可以设置这些信息。
* @param name
*/
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);

}

  现在,让我们看看实现……

PersonBean的实现

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
58
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;


@Override
public String getName() {
return name;
}

@Override
public String getGender() {
return gender;
}

@Override
public String getInterests() {
return interests;
}

@Override
public int getHotOrNotRating() {
if(ratingCount == 0){
return 0;
}
//计算rating的平均值
return rating / ratingCount;
}

@Override
public void setName(String name) {
this.name = name;
}

@Override
public void setGender(String gender) {
this.gender = gender;
}

@Override
public void setInterests(String interests) {
this.interests = interests;
}

/**
* setHotOrNotRating()增加计数值
* 并将参数加到rating实例变量中
* @param rating
*/
@Override
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}

​ Elory:我以前不太容易找到约会对象,后来我发现原来有人篡改过我的兴趣。我还发现有人居然给自己平高分,以拉高自己的HotOrNotRating值。这真的是太卑鄙了!我认为系统不应该允许用户篡改别人的兴趣,也不应该允许让用户给自己打分数。

​ 虽然外面怀疑Elory找不到约会对象可能是因为其他的因素,但是他说的没错,系统不应该允许用户篡改别人的数据。而根据我们定义PersonBean的方式,任何客户都可以调用任何方法。

​ 所以我们有一些问题需要修正:顾客不可以修改自己的HotOrNot评分,也不可以修改其他人的个人信息。要修正这些问题,必须创建两个代理,一个访问自己的PersonBean对象,另一个访问其他人的PersonBean对象。这样,代理就可以控制在每一种情况下允许哪一种请求。

​ 我们知道需要写两个InvocationHandle(调用处理器),其中一个给拥有者使用,一个给非拥有者使用。我们设置当顾客正在看自己的bean时,使用的是OwerInvocationHandle,否则,当顾客看别人的bean时,使用的是InvocationHandle。对于InvocationHandle,可以这么理解:当代理的方法被调用时,代理就会把这个调用转发给InvocationHandle,但是这并不是通过调用InvocationHandle的响应方法做到的:

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
// 拥有者的InvocationHandler

// InvocationHandler在java.lan.reflect包中
import java.lang.reflect.*;

// 所有调用处理器都实现InvocationHandler接口
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean person;

// 将person传入构造器,并保持引用
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}

// 假设Proxy的setHotOrNotRating()方法被调用
// 然后,proxy就会接着去调用InvocationHandle的invoke()方法
// handler决定如何处理这个请求
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
// 如果方法是一个getter,就允许调用
return method.invoke(person, args);

} else if (method.getName().equals("setHotOrNotRating")) {
// 否则,如果方法是HotOrNotRating(), 因为不能给自己打分,所以就抛出异常,表示不允许
throw new IllegalAccessException();

} else if (method.getName().startsWith("set")) {
// 如果是setter,可以设置自己的信息,所以就给调用
return method.invoke(person, args);

}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null; //其他方法都是未定义的,返回null
}
}

对于NonOwnerInvocationHandler,它只能去调用setHotOrNotRating():

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
import java.lang.reflect.*;

public class NonOwnerInvocationHandler implements InvocationHandler {
PersonBean person;

public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
// 可以查看其他人的信息
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
// 可以给其他人评分
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
// 不可以设置别人的信息,所以返回异常
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}

创建Proxy类并实例化Proxy对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PersonBean getOwnerProxy(PersonBean person) {
// 我们利用Proxy类的静态newProxyInstance方法创建代理对象(Java反射机制)
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(), // 将personBean的类载入器当作参数
person.getClass().getInterfaces(), // 代理需要实现的接口
new OwnerInvocationHandler(person)); // 调用非拥有者的处理器
}

PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}

使用举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PersonBean joe = getPersonFromDatabase("Joe Javabean");  //从数据库中取出一个人
PersonBean ownerProxy = getOwnerProxy(joe); // 创建这个人的拥有者代理

try {
// 尝试用拥有者代理来给自己评分
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
// 如果给自己评分会出错
System.out.println("Can't set rating from owner proxy");
}

// 创建一个非拥有者的代理
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
try {
// 尝试用非拥有者代理来设置兴趣
nonOwnerProxy.setInterests("bowling, Go");
// 可以给别人评分
nonOwnerProxy.setHotOrNotRating(3);
} catch (Exception e) {
// 不可以给别人设置兴趣
System.out.println("Can't set interests from non owner proxy");
}

  动态代理和静态代理的区别是,静态代理的的代理类是我们自己定义好的,在程序运行之前就已经编译完成,但是动态代理的代理类是在程序运行时创建的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法,我们上面的例子中只有一个代理方法,如果还有很多的代理方法,就太麻烦了,我们来看下动态代理是怎么去实现的。

首先还是定义一个Person接口:

1
2
3
4
5
6
7
/**
* 创建person接口
*/
public interface Person {
//交作业
void giveTask();
}

接下来是创建需要被代理的实际类,也就是学生类:

1
2
3
4
5
6
7
8
9
10
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}

public void giveTask() {
System.out.println(name + "交语文作业");
}
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;

public StuInvocationHandler(T target) {
this.target = target;
}

/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}

那么接下来我们就可以具体的创建代理对象了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 代理类
*/
public class ProxyTest {
public static void main(String[] args) {

//创建一个实例对象,这个对象是被代理的对象
Person linqian = new Student("林浅");

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);

//创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

//代理执行交作业的方法
stuProxy.giveTask();
}
}
联系我

评论