本文主要介绍:通用的代理模式(即静态代理),Java动态代理,CGLib动态代理。
模式背景
在某些场景下,处于某些原因,一个系统模块不希望或者不能直接访问另一个模块。比如现有的项目下,已经有了一个接口,但是他只提供了服务。在后续的开发中,发现有一些其他的模块需要调用它,但是需要对这个接口进行一个修改。但是使用方的不同,对接口所需要的修改的形式也不同(可能模块A需要对调用添加日志拦截,模块B需要对调用进行权限校验)。此时我们就可以引入一个第三放的代理类来完成。
代理模式应该属于思想相对简单的设计模式,应用范围也极广。
定义&概念
给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
代理模式是一种对象结构型模式。
原理
代理模式就是通过在系统中引入一个新的代理对象。唯一需要注意一点:在标准的设计模式中,被代理的对象需要有接口的实现。并且这种实现的方式称为静态代理。
组成要素
- 抽象主题角色(Subject)
- 申明真实角色和代理角色的共同接口,让真实角色和代理角色符合里式替换原则,任何使用真实角色的地方都可以使用代理角色。客户端主要使用这个抽象进行编程。
- 代理主题角色(Proxy)
- 内部包含对真实角色的引用,来操作真实对象。有一个和真实角色一样的接口,用来可以替换真实角色。
- 真实主题角色(RealSubject)
- 真正的那个被代理的对象。
UML
实现
抽想主题角色
public interface Subject {
void request();
}
真实主题角色
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("subject is working...");
}
}
代理角色
public class Proxy implements Subject {
// 引用的真实主题
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
// 代码前加强
public void preRequest() {
System.out.println("prepare...");
}
@Override
public void request() {
preRequest();
subject.request();
postRequest();
}
// 代码后加强
public void postRequest() {
System.out.println("finish...");
}
}
客户端
Subject s = new Proxy(new RealSubject());
s.request();
装饰模式区别
代理模式主要是为真实角色增加一些全新的职责,如权限控制,缓冲处理等,这些职责和真实角色的原始职责不是同一个领域的职责。它的目的主要是控制外部对对象的访问。
装饰模式是对原有职责的一个扩展,扩展的职责属于同一领域的。它的目的主要是为对象扩充功能。
优缺点
优点
- 协调调用者和被调用者,一定程度的对系统解耦,符合迪米特法则。
- 对于新需求,只需要添加代理类,不要修改源代码。符合开闭原则。
缺点
- 加入了一个代理类,增加了系统的复杂度。
- 静态代理,一个真实对象,就需要有一个静态代理类与之对应。
扩展
代理模式一些其他的知识点
代理模式种类
代理模式可以分为很多种类(随便看看就成):
- 远程代理
- 不同进程(地址空间)之间的代理。进程可以分布在不同的主机上,通过网络RPC进行通信。
- 虚拟代理
- 创建一个资源消耗大的对象,可以先创建一个较小的代理对象,具体对象等需要的时候再创建。
- 保护代理
- 控制对一个对象的访问,可以给不同级别的用户提供权限。
- 缓冲代理
- 为一个目标操作提供临时缓存,以便更多客户端可以共享这个结果。
- 智能引用代理
- 记录对象被调用的次数等。
Java动态代理
上面说到,静态代理类的缺点:每一个需要被代理的实体类,都需要编写一个代理类。这样无疑会大大加重系统复杂度。
思考一下:所谓代理,我们完全可以剥离出RealSubject! 就好比商城试穿衣服,每一个人穿了衣服的人就是一个被代理的类,但是人是人,衣服是衣服,这个衣服可以给很多人去穿,我们不需要为每个人去单独私人定制造一件衣服,然后才能试穿。**我们将代理的类的功能性剥离出来,形成一个模板,然后通过传入实体对象动态的给其创建出代理类。**这样,我们只需要实现一个代理模板,就可以满足很多不同的实体对象了,而不是为这些实体对象一一创建代理类。
java有一种机制是在系统运行中动态创建代理类。使用的反射机制,通常代理的是一个接口下面的所有的类,因为他只能代理接口。
实现
就是实现一个代理类,然后使用的时候,像这个代理类传入对象通过反射动态创建代理对象。
public class JavaProxy implements InvocationHandler {
private Subject subject;
public JavaProxy(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke java proxy");
if ("request".equals(method.getName())) {
System.out.println("invoke request method");
return method.invoke(subject, args);
} else {
System.out.println("调用的其他方法");
return method.invoke(subject, args);
}
}
}
客户端
//JDK动态代理
Subject real = new RealSubject();
//传入实体
JavaProxy proxy = new JavaProxy(real);
//创建代理对象
Subject proxyClass = (Subject) java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, proxy);
proxyClass.request();
原理
涉及java两个关键的类:
-
java.lang.reflect.Proxy
- 用来生成代理类和对象的
-
java.lang.reflect.InvocationHandler
- 代理的实现逻辑。
缺点
- 要求原实体对象(
RealSubject)必须实现接口。
注意事项
- Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库。
CGLIB动态代理
CGLIB是一个Java字节码的生成工具,它会为原类动态生成一个被代理类的子类。
实现
也需要先实现一个抽象的代理层。
public class ProxyInterceptor implements MethodInterceptor {
private Object object;
public ProxyInterceptor(Object object) {
this.object = object;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("invoke cglib proxy");
if ("request".equals(method.getName())) {
System.out.println("invoke request method [cglib]");
methodProxy.invokeSuper(o, args);
} else {
System.out.println("调用的其他方法");
methodProxy.invokeSuper(o, args);
}
return null;
}
}
一个没有接口的被代理类
public class CGLibRealSubject {
public void request() {
System.out.println("cglib subject is working...");
}
}
客户端使用
//CGLIB动态代理
ProxyInterceptor interceptor = new ProxyInterceptor(new CGLibRealSubject());
CGLibRealSubject cgLibRealSubject = (CGLibRealSubject) Enhancer.create(CGLibRealSubject.class,interceptor);
cgLibRealSubject.request();
附
如有代码和文章问题,还请指正!感谢!