腊月的季节

设计模式之代理模式

代理模式分类

首先代理模式,可以分为两种,一种是静态代理,一种是动态代理。
两种代理从虚拟机加载类的角度来讲,本质上都是一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。

静态代理

采用的方式就是我们手动的将这些行为换进去,然后让编译器帮我们编译,同时也就将字节码在原有类的基础上加入一些其他的东西或者替换原有的东西,产生一个新的与原有类接口相同却行为不同的类型。
举个例子吧!就拿数据库连接的例子来说。
数据库连接可以说是一个频繁的过程,如果经常去创建连接,关闭连接,那么这时候很浪费CPU以及内存。所以人们就想了一个数据库连接池,即创造一堆等待被使用的连接,等到用的时候就从池里取一个,不用了就放回去,数据库连接在整个应用启动期间,几乎是不关闭的,除非是超过了最大闲置时间。
还是不举数据库的例子了,这个有点复杂。
接口和抽象类都可以,他们两最大的区别就是抽象类可以包含属性。
代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象和目标对象具有统一的接口,以便可以再任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或者之后,执行某些操作,而非单纯的将调用传递给目标对象。
首先定义个抽象类,在其中定义代理类和目标类共有的接口operation()

1
2
3
public abstract class AbstractObject{
protected abstract void operation();
}

下面定义目标实现类:

1
2
3
4
5
6
public class RealObject extends AbstractObject {
@Override
protected void operation() {
System.out.println("do operation...");
}
}

下面是代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ProxyObject extends AbstractObject {
//对目标类的引用
private RealObject realObject;

public ProxyObject(RealObject realObject) {
this.realObject = realObject;
}

@Override
protected void operation() {
System.out.println("do something before real operation...");
if(realObject == null){
realObject = new RealObject();
}
realObject.operation();
System.out.println("do something after real operation...");
}
}

测试类:

1
2
3
4
5
6
public class ProxyTest {
public static void main(String[] args) {
AbstractObject proxy = new ProxyObject(new RealObject());
proxy.operation();
}
}

执行结果如下:

1
2
3
do something before real operation...
do operation...
do something after real operation...

就此可以看到,原来的目标类实现的时候传入了代理类,但也可以传入空,反正传入空,我也是控制了的,用if判断了一次,那么在调用目标类的operation的方法时,我们可以用代理类做一些小手脚,比如说加密啊,或者什么的,只是举个例子。在我们不能改变源码的情况下,我们可以实现相同的接口然后传入目标类的实例,在实现的时候做一些处理。专业点就是对于我们不关心的方法,全部委托给被代理的对象处理。自己处理我们关心的方法。

动态代理

动态代理是JDK自带的功能,它需要你去实现一个InvocationHandler接口,并且调用Proxy的静态方法去产生代理类。
这次我们应该理解什么是代理了,那么这次我就稍微复杂一点。

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
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;


public class ConnectionProxy implements InvocationHandler{

private Connection connection;

public ConnectionProxy(Connection connection) {
super();
this.connection = connection;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里判断是Connection接口的close方法的话
if (Connection.class.isAssignableFrom(proxy.getClass()) && method.getName().equals("close")) {
//我们不执行真正的close方法
//method.invoke(connection, args);
//将连接归还连接池
DataSource.getInstance().recoveryConnection(connection);
return null;
}else {
return method.invoke(connection, args);
}
}

public Connection getConnectionProxy(){
return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);
}

}

上面是我们针对connection写的动态代理,InvocationHandler接口只有一个invoke方法需要实现,这个方法是用来在生成的代理类用回调使用的,关于动态代理的原理一会做详细的分析,这里我们先只关注用法。很显然,动态代理是将每个方法的具体执行过程交给了我们在invoke方法里处理。而具体的使用方法,我们只需要创造一个ConnectionProxy的实例,并且将调用getConnectionProxy方法的返回结果作为数据库连接池返回的连接就可以了。
通常情况下,动态代理的使用是为了解决这样一种问题,就是我们需要代理一系列类的某一些方法,最典型的应用就是我们前段时间讨论过的springAOP,我们需要创造出一批代理类,切入到一系列类当中的某一些方法中。下面给出一个经常使用的动态代理方式。

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
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class DynamicProxy implements InvocationHandler{

private Object source;

public DynamicProxy(Object source) {
super();
this.source = source;
}

public void before(){
System.out.println("在方法前做一些事,比如打开事务");
}

public void after(){
System.out.println("在方法返回前做一些事,比如提交事务");
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//假设我们切入toString方法,其他其实也是类似的,一般我们这里大部分是针对特定的方法做事情的,通常不会对类的全部方法切入
//比如我们常用的事务管理器,我们通常配置的就是对save,update,delete等方法才打开事务
if (method.getName().equals("toString")) {
before();
}
Object result = method.invoke(source, args);
if (method.getName().equals("toString")) {
after();
}
return result;
}

public Object getProxy(){
return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);
}


}

上述我做了一些注释,其实已经说明一些问题,这个代理类的作用是可以代理任何类,因为它被传入的对象是Object,而不再是具体的类,比如刚才的Connection,这些产生的代理类在调用toString方法时会被插入before方法和after方法。
动态代理有一个强制性要求,就是被代理的类必须实现了某一个接口,或者本身就是接口,就像我们的Connection。
道理其实很简单,这是因为动态代理生成的代理类是继承Proxy类的,并且会实现被你传入newProxyInstance方法的所有接口,所以我们可以将生成的代理强转为任意一个代理的接口或者Proxy去使用,但是Proxy里面几乎全是静态方法,没有实例方法,所以转换成Proxy意义不大,几乎没什么用。假设我们的类没有实现任何接口,那么就意味着你只能将生成的代理类转换成Proxy,那么就算生成了,其实也没什么用,而且就算你传入了接口,可以强转,你也用不了这个没有实现你传入接口的这个类的方法。
具体的可以研究JDK源代码。

热评文章