简介
日常生活中我们经常会碰到代理模式,例如我们找房产中介帮我们介绍房子,找代购来帮我们购买商品,找保洁帮我们打理房间,我们在无形中就运用到了代理模式。
在软件开发中,也有一种设计模式可以提供与房产中介与代购等的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为“代理”的第三者来实现间接访问,该设计模式就被称为代理模式。
代理模式是一种使用率非常高的模式,其定义如下:
Provide a surrogate or placeholder for another object to control access to it.
其主要意思就是为其他对象提供一种代理以控制对这个对象的访问。
代理模式的通用类图如下所示:
代理模式包含如下三个角色:
- Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
- RealSubject具体主题角色:也叫作被委托角色、被代理角色。是业务逻辑的具体执行者。
- Proxy代理主题角色:也叫作委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕后做预处理和善后处理的工作。
在Java语言中常用的代理有静态代理和动态代理,我们先来简单看一下JDK实现的静态代理
JDK静态代理
我们通过一段代码来介绍静态代理:
Subject抽象主题角色代码:
public interface Subject {
public void request();
}
RealSubject具体主题角色代码:
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject : request()");
}
}
Proxy代理主题角色代码:
public class SubjectProxy implements Subject {
// 目标代理对象
private Subject subject = new RealSubject();
@Override
public void request() {
System.out.println("SubjectProxy : request()");
// 调用目标方法前的处理方法
PreRequest();
// 调用目标对象的方法
subject.request();
// 调用目标方法后的处理方法
PostRequest();
}
public void PreRequest() {
System.out.println("SubjectProxy : PreRequest()");
}
public void PostRequest() {
System.out.println("SubjectProxy : PostRequest()");
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 创建代理对象, 并使用接口对其进行引用
Subject subject = new SubjectProxy();
// 调用目标方法
subject.request();
}
}
运行结果:
SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()
静态代理的实现很简单,SubjectProxy代理类持有一个RealSubject的目标对象,通过代理类可以来间接访问该目标对象。虽然静态代理简单易懂,但是,静态代理中的代理类和目标对象紧密地耦合在了一起,如果Subject的另外一个实现类也需要进行代理控制,那我们就需要重新写一个代理类,这样就会产生需要重复代码,不能做到代码复用;而且,一旦接口增加方法,那么目标对象与代理对象都需要进行维护。而使用动态代理就能够解决上述问题。
JDK动态代理
我们先来看如何通过动态代理实现与静态代理一样的功能,源码如下:
Subject抽象主题角色代码:
public interface Subject {
public void request();
}
RealSubject具体主题角色代码:
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject : request()");
}
}
Proxy代理主题角色代码:
public class SubjectProxy implements InvocationHandler {
private Subject subject;
public Object getInstance(Subject subject) {
this.subject = subject;
Class clazz = subject.getClass();
Object obj = Proxy.newProxyInstance(subject.getClass().getClassLoader(), clazz.getInterfaces(), this);
return obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("SubjectProxy : request()");
// 调用目标方法前的处理方法
PreRequest();
// 调用目标方法
method.invoke(subject, args);
// 调用目标方法后的处理方法
PostRequest();
return null;
}
public void PreRequest() {
System.out.println("SubjectProxy : PreRequest()");
}
public void PostRequest() {
System.out.println("SubjectProxy : PostRequest()");
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 创建被代理对象
Subject subject = new RealSubject();
// 创建代理对象, 并使用接口对其进行引用
Subject subjectProxy = (Subject) (new SubjectProxy()).getInstance(subject);
// 调用目标方法
subjectProxy.request();
}
}
运行结果:
SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()
可以看到,通过动态代理实现了与静态代理一样的功能,同时,如果Subject的另外一个实现类也需要进行代理控制,那么我们只需要将客户端代码中的
Subject subject = new RealSubject();
修改为
Subject subject = new AnotherRealSubject();
即可,不用修改其余代码,动态代理是将代码中横向切面的逻辑剥离了出来,对该部分逻辑实现了代码复用。
同时,若接口增加了方法,那么我们只需要对目标对象进行维护,而代理对象并没有实现接口,所以我们不用修改代理对象。很好地解决了静态代理的问题。
但是,动态代理也有如下缺点:
- 动态代理的实现比较复杂且难以理解;
- 动态代理要求代理对象必须实现某个接口;
- 动态代理会为接口中声明的所有方法添加上相同的逻辑,不够灵活;
这只是JDK动态代理所存在的一些缺陷,动态代理还可以使用一些其他开源库来实现,如使用CGLIB库就可以解决上述问题。
cglib动态代理
cglib - Byte Code Generation Library是一个强大的、高性能的代码生成库。cglib不要求代理对象必须实现某个接口,而且cglib很灵活,它可以为目标对象的不同方法添加上不同的逻辑,它广泛地被使用于多种AOP框架中,例如Spring AOP、dynaop等,为它们提供方法的interception(拦截)。cglib源码托管在github中:https://github.com/cglib/cglib
cglib的组成结构如下:
cglib底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了cglib库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
下面,我们通过一个例子来看一下cglib相比于JDK实现动态代理的强大之处。
首先,我们需要在工程的POM文件中引入cglib的dependency,这里我们使用的是3.2.6版本:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.6</version>
</dependency>
程序源码
RealSubject具体主题角色代码:
public class RealSubject {
public void request() {
System.out.println("RealSubject : request()");
}
}
RealSubject并没有实现任何接口。
Proxy代理主题角色代码:
public class SubjectProxy implements MethodInterceptor {
// 目标对象
private Object target;
public Object getInstance(Object target) {
this.target = target;
// 工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调方法
enhancer.setCallback(this);
// 创建子类,即代理对象
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("SubjectProxy : request()");
PreRequest();
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
PostRequest();
return returnValue;
}
public void PreRequest() {
System.out.println("SubjectProxy : PreRequest()");
}
public void PostRequest() {
System.out.println("SubjectProxy : PostRequest()");
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 创建被代理对象
RealSubject realSubject = new RealSubject();
// 创建代理对象, 并使用接口对其进行引用
RealSubject realSubjectProxy = (RealSubject) (new SubjectProxy()).getInstance(realSubject);
// 调用目标方法
realSubjectProxy.request();
}
}
运行结果:
SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()
可以看到,通过cglib实现的动态代理同样可以实现与JDK动态代理一样的功能,但是cglib不要求被代理类必须实现某一接口,cglib能够实现的功能远不至此,它还可以实现很多强大的功能,如修改方法返回值、对特定的方法进行拦截、对bean进行复制、修改bean属性等多种操作,这里就不再具体介绍,有兴趣的可以自己查看相关资料。
总结
首先为大家介绍了什么是代理模式,并使用简单的例子为大家介绍了静态代理、JDK动态代理和cglib动态代理,并分析了静态代理、动态代理和cglib各自的优缺点,使大家对代理模式有了一些大致的了解。我们后续会对JDK动态代理的实现,以源码的角度来进行详细讲解。
参考资料
秦小波:《设计模式之禅》