1、定义
代理模式(Proxy Pattern):为其他对象提供一种代理以控制这个对象的访问。
2、形式
代理模式有好几种不同的形式,下面我们针对不同的形式分别进行讲解。
1)静态代理
通用类图如下所示:
其中
Subject
为抽象主题角色,定义了一个普通的业务类型,既可以是抽象类也可以是接口;
RealSubject
为具体主题角色,是被代理的角色,也是业务逻辑的具体执行者;
Proxy
为代理主题角色,负责代理真实角色,该类把所有抽象主题定义的方法限制委托给真实主题角色实现,并在真实主题角色处理完毕前后做预处理和善后处理工作(添加了新的行为);
通用源码为:
示例:
Subject
public interface Subject {
public void request();
}
RealSubject
public class RealSubject implements Subject {
public void request() {
//业务处理逻辑
}
}
Proxy
public class Proxy implements Subject {
//要代理的实现类
public RealSubject realSubject;
//默认被代理者
public Proxy(){
this.subject = new RealSubject();
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
public void before(){
//do something
}
//善后处理
public void after(){
//do something
}
}
示例:租房子,本来需要租客(省略)直接找到租户 (Host
) 租房子 (Rent
),现在租户为了方便将出租交给了中介 (Proxy
) 处理。
房屋出租
public interface Rent{
public void rent();
}
租户角色
public class Host implements Rent{
public void rent(){
system.out.println("租房子");
}
}
中介角色
public class Proxy implements Rent{
private Host host;
public Proxy(){}
public Proxy(Host host){
this.host = host;
}
public void rent(){
seeHouse();
host.rent();
fare();
}
}
//预处理:带租客看房
public void seeHouse(){
}
//善后处理:获得中介费
public void fare(){
}
客户角色
public class Client{
public static void main(String[] args){}
//创建真实角色
Host host = new Host();
//将真实角色进行代理
Proxy proxy = new Proxy(host);
//通过代理角色进行业务操作
proxy.rent();
}
2)普通代理
用户必须知道代理角色才能访问真实角色,也即客户端只能访问代理角色,无法访问真实角色。
示例代码如下:
Subject
public interface Subject {
public void request();
}
RealSubject
public class RealSubject implements Subject {
String name = "";
//限制谁能创建对象
public RealSubject(Subject realSubject, String name) throws Exception{
if(realSubject == null){
throw new Exception("不能创建真实角色!");
}else {
this.name = name;
}
}
public void request() {
//业务处理逻辑
System.out.println(this.name + "submits request");
}
}
Proxy
public class Proxy implements Subject {
//要代理的实现类
public Subject subject = null;
//默认被代理者
public Proxy(String name){
try {
this.subject = new RealSubject(this, name);
} catch (Exception e) {
e.printStackTrace();
}
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
public void before(){
//do something
System.out.println("preHandleMethod");
}
//善后处理
public void after(){
//do something
System.out.println("postHandleMethod");
}
}
Client 场景类
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy("lee");
proxy.request();
}
}
在该模式下,调用者只知道代理而不知道真实角色是谁,屏蔽了真实角色的变更对高层模块的影响,因此该模式适用于对扩展性较高的场合。
3)强制代理
只能通过真实角色查找到指定的代理才能访问真实角色,也即无法直接访问真实角色,也无法通过new一个代理访问,只能通过真实对象指定的代理进行访问。
示例代码如下:
Subject
public interface Subject {
public void request();
}
RealSubject
public class RealSubject implements Subject {
private String name = "";
public Subject proxy = null;
public RealSubject(String name) {
this.name = name;
}
public void request() {
//业务处理逻辑
if(this.isProxy())
System.out.println(this.name + " submits request");
else
System.out.println("请使用指定的代理访问!");
}
public Subject getProxy(){
this.proxy = new Proxy(this);
return this.proxy;
}
private boolean isProxy(){
if(this.proxy == null){
return false;
}else {
return true;
}
}
}
Proxy
public class Proxy implements Subject {
//要代理的实现类
public Subject subject = null;
//默认被代理者
public Proxy(Subject subject){
this.subject = subject;
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
public void before(){
//do something
System.out.println("preHandleMethod");
}
//善后处理
public void after(){
//do something
System.out.println("postHandleMethod");
}
}
Client
public class Client {
public static void main(String[] args) {
//下面两种方式均无法访问真实对象的方法
// Proxy proxy = new Proxy(new RealSubject("lee"));
//Subject subject = new RealSubject("lee");
Subject proxy = new RealSubject("lee").getProxy();
proxy.request();
}
}
强制代理的核心就是要从真实角色查到代理角色进行访问,高层模块必须通过真实角色提供的getProxy()
方法获得一个代理对象来进行访问。
4)动态代理
动态代理在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。简单来说,动态代理就是静态代理的升级版:静态代理需要为每一个真实角色指定一个代理,而动态代理的是接口,而真实的代理对象是在运行时确定的。
从上面的解释可以看出,动态代理的被代理类必须实现一个接口,但还有一些技术(如CGLIB)不需要接口也可以实现动态代理。
示例代码:以租房为例
抽象角色
public interface Rent {
void rent();
}
真实角色
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
代理角色
public class ProxyInvocationHandler implements InvocationHandler {
//代理的是接口
private Rent rent;
public ProxyInvocationHandler (Rent rent){
this.rent = rent;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
//proxy:代理类
//method:代理类调用处理程序对象的方法
//调用方法并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}
//代理类特有方法
public void seeHouse(){
System.out.println("带租客看房");
}
public void fare(){
System.out.println("收中介费");
}
}
客户角色
public class Client {
public static void main(String[] args) {
//真实对象
Host host = new Host();
//生成代理实例
ProxyInvocationHandler pih = new ProxyInvocationHandler(host);
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
这里的动态代理使用了位于java.lang.reflect
包下提供的Proxy
类和InvocationHandler
接口,通过这两个可以生成JDK动态代理类或动态代理对象。代理对象代理的是接口,这也是动态代理的核心。从本质上来说,动态代理是通过反射实现的。
动态代理也是一个非常流行的技术—AOP的核心,AOP(面向横切面编程)对系统的设计和编码有非常大的影响,对于日志、事务、权限等一系列与业务逻辑分离的操作都推迟到系统设计阶段之后用AOP的方式切入进去。
接下来我们看看动态代理的通用类图:
动态代理类实现InvocationHandler
接口代理真实角色,通知接口抽象出通知方法,通过动态代理切入业务逻辑。
代码框架如下:
Subject
public interface Subject {
public void doSomething(String str);
}
RealSubject
public class RealSubject implements Subject {
public void doSomething(String str) {
System.out.println("do something " + str);
}
}
MyInvocationHandler 动态代理的Handler类
public class MyInvocationHandler implements InvocationHandler {
public Object target = null;
public MyInvocationHandler(Object obj){
this.target = obj;
}
//代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.target, args);
}
}
IAdvice 前置通知接口
public interface IAdvice {
public void exec();
}
BeforeAdvice
public class BeforeAdvice implements IAdvice{
public void exec() {
System.out.println("前置通知操作!");
}
}
DynamicProxy 动态代理类
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
//寻找JoinPoint连接点
if(true){
(new BeforeAdvice()).exec();
}
return (T) Proxy.newProxyInstance(loader, interfaces, h);
}
}
Client 场景类
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
InvocationHandler handler = new MyInvocationHandler(subject);
//代理该类的所有接口
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
proxy.doSomething("Finish!");
}
}
上面的动态代理类是一个通用类,可以继承该类获得一个具体的动态代理对象,以简化代码。代码修改如下:
SubjectDynamicProxy 动态代理类
public class SubjectDynamicProxy extends DynamicProxy{
public static <T> T newProxyInstance(Subject subject){
ClassLoader load = subject.getClass().getClassLoader();
Class<?>[] classes = subject.getClass().getInterfaces();
InvocationHandler handler = new MyInvocationHandler(subject);
return (T) Proxy.newProxyInstance(loader, classes, handler);
}
}
Client 场景类
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);
proxy.doSomething("Finish!");
}
}
这样我们便得到了一个简单的AOP框架。
动态代理弥补了静态代理的缺点,由于代理的是接口,因此动态代理可以代理多个类,并且可以轻松完成公共业务的扩展。
3、优缺点
- 职责清晰:真实的角色只需要关注实际的业务逻辑,不同关系其他非本职责的事务(例如输出日志等),而是通过后期的代理来完成。
- 高扩展性:真实主题角色的变化不会影响代理类的使用;可以通过切面轻松完成业务逻辑的扩展。
- 智能化:动态代理可以实现 ‘智能’ 的接口代理。
4、使用场景
代理模式使用的场景非常多,如果在业务中存在很多的中间过程(例如输出日志、事务管理、执行验证等),就可以用代理模式来实现。类比到现实世界,打官司只管答辩,请律师帮忙搞定中间的复杂步骤;或是租房子只管交房收钱,把中间事宜交给中介进行管理等等,都体现了代理模式的思想。Spring框架中的AOP思想就是基于动态代理模式,详情可参考Spring框架。
5、小结
代理模式为控制对象的访问提供了一个有效的方式,在许多框架中都有应用,可以结合框架去仔细体会它的精妙之处。