Java 设计模式12——代理模式

https://rainmonth.github.io/posts/J210531.html

模式定义

给某一个对象(源对象)提供一个代理对象,对这个对象(源对象)的访问都通过这个代理对象来进行。这样的话对源对象的访问都可以通过代理对象来进行,代理对象可以在不改变源对象的基础上,控制对这个源对象的访问。这类似于明星(源对象)和明星经纪人(代理对象)的关系。某公司想请某明星A做形象代言人,于是就找到了明星A的经纪人B,B若代言费用合理,就允许其代言,不合理就不为其代言。隔离了外界对明星A的干扰。

代理实现方式

代理模式主要分三种:静态代理、动态代理和Cglib代理。

  • 静态代理,为代理的类在额外写一个类(或者用工具生成一个类),这个类实现了被代理的类的所有功能。这就导致了要为一个对象设置代理对象,必须就在对象类的基础上再写一个类,显得不够灵活;
  • 动态代理,动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程。由于动态生成的类已经继承了Proxy这个类,就不能在继承其他类了,从而只能通过接口的方式来实现了。(Java是单继承,多实现的)
  • Cglib代理,通过为被代理的类生成一个子类并重写被代理的方法实现代码的动态植入,因此,Cglib无法代理被final修饰的类或方法以及静态方法。

代理模式Demo示例

当然,明星A和他的经纪人B必须有一些一致性约定,经济人B必须知道明星A的一些原则(愿意代言什么类型的,愿意接什么类型的通告),所以A和B必须实现相同的接口。下面看看具体实现。

先定义A和B的一致性协定(两者都要实现的接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 接受的通告类型
* @author randy
* @date 2021/6/1 10:02 上午
*/
public interface IAcceptAnnounce {
/**
* 产品形象代言
* @param proxyPrice 代言费用
*/
void productProxy(long proxyPrice);

/**
* 舞蹈表演
* @param danceShowPrice 舞蹈表演费用
*/
void danceShow(long danceShowPrice);

/**
* 演唱歌曲
* @param singShowPrice 演唱歌曲费用
*/
void singShow(long singShowPrice);
}

再定义明星(源对象)A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 明星对象
* @author randy
* @date 2021/6/1 9:59 上午
*/
public class Star implements IAcceptAnnounce {
@Override
public void productProxy(long proxyPrice) {
System.out.println("productProxy");
}

@Override
public void danceShow(long danceShowPrice) {
System.out.println("danceShow");
}

@Override
public void singShow(long singShowPrice) {
System.out.println("singShow");
}
}

然后定义经纪人(代理对象)B,经纪人是可以直接联系他的明星的,所以B会持有A的引用,并且他还知道A会接受什么类型的通告

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
**
* 经纪人对象
* @author randy
* @date 2021/6/1 10:00 上午
*/
public class StartManager implements IAcceptAnnounce {

private Star mStar;

public StartManager(Star star) {
this.mStar = star;
}

@Override
public void productProxy(long proxyPrice) {
if (proxyPrice < 200000) {
System.out.println("小于20万的产品代言不接!");
return;
}

mStar.productProxy(proxyPrice);
}

@Override
public void danceShow(long danceShowPrice) {
if (danceShowPrice < 10000) {
System.out.println("小于10000元的演出不接!");
return;
}
mStar.danceShow(danceShowPrice);
}

@Override
public void singShow(long singShowPrice) {
if (singShowPrice < 5000) {
System.out.println("小于5000元的单曲费用不接!");
return;
}
mStar.singShow(singShowPrice);
}
}

再来写一个模拟调用的StartClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author randy
* @date 2021/6/1 10:06 上午
*/
public class StarClient {
public static void main(String[] args) {
Star star = new Star();

StarManager starManager = new StarManager(star);
// 一个300000的产品代言
starManager.productProxy(180000);

// 一个8000的舞蹈表演
starManager.danceShow(10000);

// 一个6000的歌曲演唱
starManager.singShow(6000);
}
}

Star和StarManager约定好通告的标准后,Star就不用关心具体接通告的事情了,而是交给StarManager来处理。这就是代理模式。

上面的这种事静态代理,它对Star和StarManager的要求较高,两个人必须都清楚的知道通告的标准(即实现IAcceptAnnounce接口,否则就会出现不和谐的情况)

静态代理

静态代理定义:代理对象持有被代理对象的引用,并且和代理对象实现了相同的接口,一切对被代理对象实现的接口方法的访问都通过代理对象来实现。

动态代理

动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程。

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
/**
* 动态代理
* @author randy
* @date 2021/6/1 11:25 上午
*/
public class StarProxy implements InvocationHandler {
Object obj;

public StarProxy(Object o) {
this.obj = o;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

/*
* 代理之前:doSomething
*/
Object result = method.invoke(obj, args);

/*
* 代理之后: doSomething
*/
return result;
}
}

改写上面的StarClient如下:

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
/**
* @author randy
* @date 2021/6/1 10:06 上午
*/
public class StarClient {
public static void main(String[] args) {
Star star = new Star();

System.out.println("采用静态代理实现:");
StarManager starManager = new StarManager(star);
// 一个300000的产品代言
starManager.productProxy(180000);

// 一个8000的舞蹈表演
starManager.danceShow(10000);

// 一个6000的歌曲演唱
starManager.singShow(6000);

System.out.println("采用动态代理实现:");
InvocationHandler handler = new StarProxy(star);

try {
IAcceptAnnounce acceptAnnounce = (IAcceptAnnounce) Proxy.newProxyInstance(handler.getClass().getClassLoader(), star.getClass().getInterfaces(), handler);

acceptAnnounce.productProxy(180000);
acceptAnnounce.danceShow(10000);
acceptAnnounce.singShow(6000);
} catch (Exception e) {
System.out.println(e);
}
}
}

Java动态代理的基础是反射,使用动态代理的好处:

  1. 减少了编程的工作量,如果需要实现多种代理处理逻辑,只需要写多个InvocationHandler即可,而不必写多个代理类;
  2. 系统的扩展性和可维护性增强,通常只需要修改代理处理器的处理逻辑即可。

使用动态代理要注意:被代理的对象必须要实现接口,代理对象不需要实现接口。

利用下面方法可以查看动态代理生成的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 将动态代理生成的类写入到 path 路径
*/
public static void writeClassToDisk(String path) {
byte[] classFile = ProxyGenerator.generateProxyClass("$proxy4", new Class[]{IAcceptAnnounce.class});
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

在main方法中调用writeClassToDisk("./Proxy4.class") 后就可以在项目根目录看到Proxy4.class文件了

Cglib代理

CglibCode Generation Library ,是一款开源的动态代理库,与JDK动态代理基于接口的代理机制不同,Cglib通过为被代理的类生成一个子类并重写被代理的方法实现代码的动态植入,因此,Cglib无法代理被final修饰的类或方法以及静态方法。

Cglib代理示例

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 通过Cglib实现代理
* @author randy
* @date 2021/6/1 1:50 下午
*/

class UserDao {

public UserDao() {
update();
}

public void update() {
System.out.println("UserDao.update()");
}

public void select() {
System.out.println("UserDao.select()");
}
}

class UpdateProxy implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method invoke...");
proxy.invokeSuper(obj, args);
System.out.println("After method invoke...");
return obj;
}
}

class SelectProxy implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("StartTime=[" + System.currentTimeMillis() + "]");
proxy.invokeSuper(obj, args);
System.out.println("EndTime=[" + System.currentTimeMillis() + "]");
return obj;
}
}

class ProxyCallbackFilter implements CallbackFilter {

@Override
public int accept(Method method) {
if ("select".equals(method.getName())) {
return 0;// 对应于Enhancer的Callbacks的索引值为0
}
return 1;// 对应于Enhancer的Callbacks的索引值为1
}
}

public class UserDaoDemo {

public Object getTarget() {

SelectProxy selectProxy = new SelectProxy();
UpdateProxy updateProxy = new UpdateProxy();

CallbackFilter callbackFilter = new ProxyCallbackFilter();

Enhancer enhancer = new Enhancer();
// 设置要代理的类为 UserDao
enhancer.setSuperclass(UserDao.class);
// 设置回调(即MethodInterceptor的实现类)
enhancer.setCallbacks(new Callback[]{selectProxy, updateProxy});
// 设置dialing过滤器
enhancer.setCallbackFilter(callbackFilter);
// 设置不代理构造函数里面的方法
enhancer.setInterceptDuringConstruction(false);
return enhancer.create();
}


public static void main(String[] args) {
// 加上这句,就可以在项目相应的目录看到生成的代理类的内容了
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "Interview/target/cglib");
UserDao helloObj = (UserDao) new UserDaoDemo().getTarget();
helloObj.update();
helloObj.select();
}
}

模式类图

这里的类图值得是静态代理的类图。

总结

代理模式时Java中很常见的模式,很多成熟的框架都应用该模式如AOP,Retrofit等,它允许在不修改被代理对象的情况下对代理对象进行一些扩展。

相关参考

  1. Idea 如何让一个module支持maven?

    新建一个pom.xml文件,然后直接在该文件上右键,选择 Add as maven project即可,这个pom.xml文件需要符合maven项目文件的一般格式。

  2. Cglib的使用?

    Cglib可实现代理,它与Java本身支持的动态代理不同,Java本身的代理要求被代理的对象必须实现一个或多个接口,Cglib没有这个要求,他可以代理普通对象。它的限制是不能代理final修饰的对象或方法(因为它是通过编写一个被代理类的子类来实现的,如果被代理的类或方法被final所修饰,则Cglib无法代理)

  3. ASM的概念?

    ASM请参考官网