1、定义
Provide a surrogate or placeholder for another object to control access to it.
为其他对象提供一种代理以控制对这个对象的访问。
2、类图
3、角色
代理模式也叫委托模式,它是一项基本的设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。代理模式可以提供非常好的访问控制。
Subject :抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
RealSubject:具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
Proxy:代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
4、例子
public interface ISubject {
public void method1();
public void method2();
public void method3();
}
public class RealSubjectA implements ISubject {
private String name;
public RealSubjectA(String name) {
this.name = name;
}
@Override
public void method1() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method1");
}
@Override
public void method2() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method2");
}
@Override
public void method3() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method3");
}
}
public class RealSubjectProxy implements ISubject {
private ISubject mISubject;
public RealSubjectProxy(ISubject iSubject) {
this.mISubject = iSubject;
}
@Override
public void method1() {
this.mISubject.method1();
}
@Override
public void method2() {
this.mISubject.method2();
}
@Override
public void method3() {
this.mISubject.method3();
}
}
使用:
ISubject iSubject = new RealSubjectA("zlm");
RealSubjectProxy proxy = new RealSubjectProxy(iSubject);
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
5、优缺点
优点:
a、职责清晰。真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰
b、高扩展性。具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
c、智能化。动态代理可以体现。
6、扩展
设计模式中的代理分为“普通代理”和“强制代理”两种。
普通代理
他的要求就是客户端只能访问代理角色,而不能访问真实角色,也就是场景类不能再直接new一个对象了,他必须由代理来模拟场景。
我们需要修改两个实现类的构造函数即可。
RealSubject:
public class RealSubjectA implements ISubject {
private String name;
//构造函数限制谁能创建对象,并同时传递name..当然还可以 有其他的限制,比如类名必须为Proxy类等,
public RealSubjectA(ISubject iSubject,String name) throws Exception {
if (iSubject == null) {
throw new Exception("can not create RealSubject");
} else {
this.name = name;
}
}
@Override
public void method1() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method1");
}
@Override
public void method2() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method2");
}
@Override
public void method3() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method3");
}
}
Proxy:
public class RealSubjectProxy implements ISubject {
private ISubject mISubject;
//仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,
public RealSubjectProxy(String name) {
try {
this.mISubject = new RealSubjectA(this,name);
} catch (Exception e) {
// TODO: 2018/6/22 异常处理
}
}
@Override
public void method1() {
this.mISubject.method1();
}
@Override
public void method2() {
this.mISubject.method2();
}
@Override
public void method3() {
this.mISubject.method3();
}
}
使用:
ISubject proxy = new RealSubjectProxy("zlm");
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
运行结果完全相同。在该模式下,
调用者只知代理而不用知道真实的角色RealSubject是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案。
强制代理
改造如下:
ISubject:
public interface ISubject {
public void method1();
public void method2();
public void method3();
//每个角色都可以拿到自己的代理类
public ISubject getProxy();
}
RealSubjectProxy:
public class RealSubjectProxy implements ISubject {
private ISubject mISubject = null;
//仅仅修改了构造函数,传递进来一个用户
public RealSubjectProxy(ISubject iSubject) {
this.mISubject = iSubject;
}
@Override
public void method1() {
this.mISubject.method1();
}
@Override
public void method2() {
this.mISubject.method2();
}
@Override
public void method3() {
this.mISubject.method3();
}
@Override
public ISubject getProxy() {
return this;
}
}
RealSubjectA:
public class RealSubjectA implements ISubject {
private String name;
private ISubject iSubject = null;
public RealSubjectA(String name) {
this.name = name;
}
@Override
public void method1() {
if (this.isProxy()) {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method1");
} else {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method1" + "\t" + "请使用指定代理访问!");
}
}
@Override
public void method2() {
if (this.isProxy()) {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method2");
} else {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method2" + "\t" + "请使用指定代理访问!");
}
}
@Override
public void method3() {
if (this.isProxy()) {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method3");
} else {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method3" + "\t" + "请使用指定代理访问!");
}
}
@Override
public ISubject getProxy() {
this.iSubject = new RealSubjectProxy(this);
return this.iSubject;
}
//校验是否是代理访问
private boolean isProxy() {
if (this.iSubject == null) {
return false;
} else {
return true;
}
}
}
运行:
RealSubjectA realSubjectA = new RealSubjectA("zlm");
ISubject proxy = realSubjectA.getProxy();
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
7、代理个性化
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。
增加一个接口,IProxy:
public interface IProxy {
public void methodIProxy();
}
RealSubjectProxy:
public class RealSubjectProxy implements ISubject,IProxy {
private ISubject mISubject = null;
//仅仅修改了构造函数,传递进来一个用户
public RealSubjectProxy(ISubject iSubject) {
this.mISubject = iSubject;
}
@Override
public void method1() {
this.mISubject.method1();
}
@Override
public void method2() {
this.mISubject.method2();
}
@Override
public void method3() {
this.mISubject.method3();
}
@Override
public ISubject getProxy() {
return this;
}
@Override
public void methodIProxy() {
System.out.println("新加了一个接口,实现了其中的方法!");
}
}
就只用修改这一个类,实现接口,其他了类不用变!
使用:
RealSubjectA realSubjectA = new RealSubjectA("zlm");
RealSubjectProxy proxy = (RealSubjectProxy) realSubjectA.getProxy();
proxy.method1();
proxy.method2();
proxy.method3();
proxy.methodIProxy();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
I/System.out: 新加了一个接口,实现了其中的方法!
代理类不仅仅是可以有自己的运算方法,通常的情况下代理的职责并不一定单一,它可以组合其他的真实角色,也可以实现自己的职责。
代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。
当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系。
8、动态代理
什么是动态代理?
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理那个对象。面向横切面编程(AOP)的核心就是采用了动态代理机制。
接口和实现类很简单,定义需要实现的方法即可。
ISubject:
public interface ISubject {
public void method1();
public void method2();
public void method3();
}
RealSubjectA:
public class RealSubjectA implements ISubject {
private String name;
public RealSubjectA(String name) {
this.name = name;
}
@Override
public void method1() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method1");
}
@Override
public void method2() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method2");
}
@Override
public void method3() {
System.out.println(this.name + "\t" + this.getClass().getSimpleName() + "\t" + "method3");
}
}
定义一个动态代理类RealSubjectHI,实现InvocationHandler接口,构造方法传入需要代理的对象。
public class RealSubjectHI implements InvocationHandler {
//被代理的实例
Object obj = null;
//我要代理谁
public RealSubjectHI(Object obj) {
this.obj = obj;
}
//invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(this.obj, args);
return invoke;
}
}
其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我 们来详细讲解一下InvocationHandler接口,动态代理是根据被代理的接口生成所有的方法, 也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过 InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由 InvocationHandler接管实际的处理任务。
调用:
//定义具体实现类
ISubject iSubject = new RealSubjectA("zlm");
//获得类的classload
ClassLoader classLoader = iSubject.getClass().getClassLoader();
//定义一个handler
InvocationHandler invocationHandler = new RealSubjectHI(iSubject);
//动态产生一个代理者
ISubject proxy = (ISubject) Proxy.newProxyInstance(classLoader, new Class[]{ISubject.class}, invocationHandler);
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
注意:
我们既没有创建代理类,也没有实现ISubject接口,这就是动态代理。
如果我们想在一个方法调用后随即触发一些逻辑操作怎么办呢?简单,只需要改变动态代理Handler类,在invoke方法中添加一段的代码即可,请看:
public class RealSubjectHI implements InvocationHandler {
//被代理的实例
Object obj = null;
//我要代理谁
public RealSubjectHI(Object obj) {
this.obj = obj;
}
//invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(this.obj, args);
if (method.getName().equalsIgnoreCase("method1")) {
System.out.println("调用了method1方法,可以在此处添加逻辑!");
}
return invoke;
}
}
调用方法并无变化:
//定义具体实现类
ISubject iSubject = new RealSubjectA("zlm");
//获得类的classload
ClassLoader classLoader = iSubject.getClass().getClassLoader();
//定义一个handler
InvocationHandler invocationHandler = new RealSubjectHI(iSubject);
//动态产生一个代理者
ISubject proxy = (ISubject) Proxy.newProxyInstance(classLoader, new Class[]{ISubject.class}, invocationHandler);
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: zlm RealSubjectA method1
I/System.out: 调用了method1方法,可以在此处添加逻辑!
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
很棒啊,有木有!这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通 过AOP的方式切过去。
很简单,两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的 逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。
AOP及动态代理抽取
在这里插入了较多的AOP术语,如在什么地方(连接点)执行什么行为(通知)。我们 在这里实现了一个简单的横切面编程。
我们来看通知Advice,也就是我们要切入的类,接口和实现如代码清单如下:
/**
* Created by ZLM on 2018/7/2.
* Describe 前置通知接口
*/
public interface IAdvice {
public void exec();
}
/**
* Created by ZLM on 2018/7/2.
* Describe 前置通知实现
*/
public class BeforeIAdvice implements IAdvice {
@Override
public void exec() {
System.out.println("我是前置通知!我被执行了!");
}
}
因此我们抽象动态代理类:
/**
* Created by ZLM on 2018/7/2.
* Describe 动态代理类
*/
public class DynamicProxy<T> {
public static <T> T getNewProxyInstance(ClassLoader classLoader, Class<?>[] tClass, InvocationHandler invocationHandler) {
//寻找JoinPoint连接点,AOP框架使用元数据定义
if (true) {
//执行一个前置通知
new BeforeIAdvice().exec();
}
//执行目标,返回结果
return (T) Proxy.newProxyInstance(classLoader, tClass, invocationHandler);
}
}
在返回动态代理对象之前,执行我们要切入的类的方法。
执行变成了这样:
//定义具体实现类
ISubject iSubject = new RealSubjectA("zlm");
//获得类的classload
ClassLoader classLoader = iSubject.getClass().getClassLoader();
//定义一个handler
InvocationHandler invocationHandler = new RealSubjectHI(iSubject);
//产生一个动态代理
ISubject proxy = DynamicProxy.getNewProxyInstance(classLoader, iSubject.getClass().getInterfaces(), invocationHandler);
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: 我是前置通知!我被执行了!
I/System.out: zlm RealSubjectA method1
I/System.out: 调用了method1方法,可以在此处添加逻辑!
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
Proxy.newProxyInstance(classLoader, tClass, invocationHandler);该方法是重新生成了一个对象,为什么要重新生成?你要使用代理呀,注意 c.getInterfaces()这句话, 这是非常有意思的一句话,是说查找到该类的所有接口,然后实现 接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象。 于是我们知道一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现。
我们注意到上面的动态代理类DynamicProxy是一个通用类,不具有业务意义,我们可以在再产生一个实现类简化代码。
/**
* Created by ZLM on 2018/7/2.
* Describe 具有业务逻辑的动态代理类
*/
public class SubjectDynamicProxy extends DynamicProxy {
public static <T> T getNewProxyInstance(T t) {
ClassLoader classLoader = t.getClass().getClassLoader();
Class<?>[] interfaces = t.getClass().getInterfaces();
InvocationHandler invocationHandler = new RealSubjectHI(t);
//执行目标,返回结果
return getNewProxyInstance(classLoader, interfaces, invocationHandler);
}
}
调用 so easy:
ISubject iSubject = new RealSubjectA("zlm");
ISubject proxy = SubjectDynamicProxy.getNewProxyInstance(iSubject);
proxy.method1();
proxy.method2();
proxy.method3();
输出:
I/System.out: 我是前置通知!我被执行了!
I/System.out: zlm RealSubjectA method1
I/System.out: 调用了method1方法,可以在此处添加逻辑!
I/System.out: zlm RealSubjectA method2
I/System.out: zlm RealSubjectA method3
欢迎关注我的公众号OpenShare,专注移动开发和大数据技术学习和分享,也希望通过公众号,促进自己学习和进步。技术博文会同步分享至公众号,欢迎订阅!