设计模式文章陆续更新
java单例模式
java工厂模式
java状态模式
这几天在看一些框架源码时看到了一个很奇妙的设计模式,有种熟悉个感觉,一时想不出是什么模式,后面经过了解才知道是动态代理,就这样带着好奇心学习了这个模式,更深入了解代理会发现不仅有静态和动态,还有很多其他的代理类别,果然兴趣是最好的老师,效率不错,下面是我一些总结.
一起来体验下,你也会发现,原来你是这样的代理.
什么是代理?
在<大话设计模式>中说到,代理模式,为其他对象提供一种代理以控制对这个对象的访问.
下面通过一个例子,说明下.
商家需要搞活动,请了陈奕迅过来商演唱歌,那么商家需要跟陈奕迅进行
面谈->签合同->首付款->安排行程->唱歌->收尾款
.
-
在不使用代理的情况下是这样的.
可以看到陈奕迅
好像很忙,除了要唱歌
还要做很多的交互,这样我的爱豆不是要忙死了. -
使用代理模式
通过代理人来处理一些琐碎的事情后,陈奕迅
就只要负责他独有的功能唱歌
就行了,这样间接的访问对象,有效的减轻了一些重复的操作.
代理的核心角色
我们可以将代理模式分为三大角色:
- 抽象角色
-
代理角色
和真实角色
的公共方法都会在这里定义.
-
- 真实角色
- 供给
代理角色
用,这里实现了抽象角色
的业务逻辑. - 关注真正的业务逻辑
- 供给
- 代理角色
-
真实角色
的代理,根据真实角色的业务逻辑来实现
抽象角色
的抽象方法,并可以附件自己的操作.
-
java中的代理
代理模式(Proxy)是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题.
它也是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.
在java中动态代理机制以巧妙的方式实现了代理模式的设计理念,因此java中也分为动态代理
和静态代理
.
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- 反射机制,JDK自带的动态代理
- 通过
InvocationHandler
(处理器接口)- 使用invoke方法实现对
真实角色
的代理访问. - 每次通过
Proxy
生产代理类对象时,都要指定的处理器对象.
- 使用invoke方法实现对
- java动态性这块可以深入看看,反射与javassist
静态代理(StaticProxy)
借用Eason-S
大神的类图,静态代理UML类图
talk is cheap,根据上面陈奕迅的案例
来写个demo
- 抽象角色
/*
* 明星和代理人的公共接口
*/
public interface StarInterface {
// 面谈
void interview();
// 签合同
void signContract();
// 首付款
void firstPayment();
// 安排行程
void plan();
// 唱歌
void sing();
//收尾款
void finalPayment();
}
- 真实角色
/*
* 明星实现类
*/
public class EasonStar implements StarInterface {
private final String STAR_NAME = "陈奕迅:";
@Override
public void sing() {
System.out.println(STAR_NAME+"唱歌");
}
@Override
public void finalPayment() {
System.out.println(STAR_NAME+"收尾款");
}
@Override
public void signContract() {
System.out.println(STAR_NAME+"签合同");
}
@Override
public void plan() {
System.out.println(STAR_NAME+"安排行程");
}
@Override
public void interview() {
System.out.println(STAR_NAME+"面谈");
}
@Override
public void firstPayment() {
System.out.println(STAR_NAME+"预付款");
}
}
- 代理角色
public class ProxyStar implements StarInterface {
//私有化被代理角色
private StarInterface star;
private final String PROXY_NAME = "代理人:";
/*
* 使用接口的方式来指向真实角色(多态特性)
*/
public ProxyStar(StarInterface star) {
super();
this.star = star;
}
@Override
public void sing() {
// 唱歌是明星的特有方法,代理是没有的,因此需要明星自己来
// 调用陈奕迅的唱歌方法...
star.sing();
}
@Override
public void finalPayment() {
System.out.println(PROXY_NAME+"签尾款");
}
@Override
public void signContract() {
System.out.println(PROXY_NAME+"签合同");
}
@Override
public void plan() {
System.out.println(PROXY_NAME+"安排行程");
}
@Override
public void interview() {
System.out.println(PROXY_NAME+"面谈");
}
@Override
public void firstPayment() {
System.out.println(PROXY_NAME+"预付款");
}
}
- 客户端类
public class Client {
public static void main(String[] args) {
//找到陈奕迅
EasonStar realStar = new EasonStar();
//找到代理人,专门为陈奕迅代理
ProxyStar proxyStar = new ProxyStar(realStar);
//代理人来完成
proxyStar.interview();
proxyStar.signContract();
proxyStar.firstPayment();
proxyStar.plan();
//这里调用的是 EasonStar的sing方法
proxyStar.sing();
proxyStar.finalPayment();
}
}
输出结果:
代理人:面谈
代理人:签合同
代理人:预付款
代理人:安排行程
陈奕迅:唱歌
代理人:签尾款
从上面静态代理demo中,你会发现无论代理角色
或真实角色
都需要实现接口,并且将真实角色
的细节向调用方完全隐藏,可以看下EasonStar
类里面有很多方法,但最终被掉用的只有sing()
,其他都是代理角色
来调用的.
动态代理(DynamicProxy)
动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建.
看下代码.
由于抽象角色
和真实角色
的代码跟上面静态代理是一样的这里就直接给出代理角色
和客户端类的代码.
- 代理角色
/**
* 动态代理角色 主要代理真实角色的方法,
* 被调用的方法都会走 invoke()方法,可以在该方法中处理真实角色的业务逻辑
*
* @author relicemxd
*
*/
public class StarHandler implements InvocationHandler {
//私有的被代理角色
private Object star;
private final String PROXY_NAME = "代理人:";
/*
* Obj 传入的是需要被代理的真实角色
* 并且通过下面的反射技术获取到要代理的行为
*/
public StarHandler(Object star) {
super();
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 所有掉代理类的方法都会走这里
// TODO 1. 在转调具体目标对象之前,可以执行一些预处理逻辑
System.out.println(PROXY_NAME + "面谈");
System.out.println(PROXY_NAME + "签合同");
System.out.println(PROXY_NAME + "预付款");
System.out.println(PROXY_NAME + "安排行程");
Object invoke = null;
// 因为proxy每调用的方法都会走这里, 因此就可以通过 invoke的特性来做一些逻辑判断
if (method.getName().equals("sing")) {
// TODO 2. 转调具体目标对象的方法
// 只有当代理对象调用到了sing的方法,才进入
invoke = method.invoke(star, args);
}
// TODO 3.在转调具体目标对象之后,可以执行一些后处理逻辑
System.out.println(PROXY_NAME + "收尾款");
return invoke;
}
}
- 客户端类
public class Client {
public static void main(String[] args) {
// 陈奕迅准备要找代理(真实角色)
EasonStar star = new EasonStar();
// 找到了这个代理人代理(代理角色)
StarHandler handler = new StarHandler(star);
// jdk提供的代理实例
StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), // 类加载器
new Class[] { StarInterface.class }, // 这里必须是接口,否则会报错
handler);// 代理类
// 调用interview 会进入StarHandler类的`invoke方法`,
// 但是interview方法不会被执行
// proxyStar.interview();
// 这里调用的是 EasonStar的sing方法
proxyStar.sing();
// proxyStar.finalPayment();
}
}
通过动态代理可以看出对抽象角色
我们无需知道他是什么时候创建的,也不用知道改接口实现了什么,并且实现的是InvocationHandler
接口,接口的唯一方法invoke()
用于处理真实角色
的逻辑.
静态代理vs动态代理?
可以从代理的接口和创建过程来分析下他们的不同之处,进一步了解下代理模式.
代理中的接口
- 共同之处
- 都会在
代理角色
中会创建一个私有的成员变量 - 都需要通过接口来实现代理,主要利用java多态的特性
- 不同之处
- 静态代理的
真实角色
和代理角色
都会实现同一接口(抽象角色),动态代理则只有真实角色
实现了接口. - 静态代理利用了java的多态特性来实现代理模式,而动态代理巧妙的使用了jdk的反射机制来完成代理,也因此两者区别在于
代理角色
的实现方式不一样,看下面这条描述. - 静态代理的
代理角色
实现的是抽象角色
这个接口,而动态代理实现的是jdk的内置接口InvocationHandler
.
- 静态代理的
创建代理的过程
-
共同之处
- 两者的创建原理一致,需要通过创建
代理角色
来处理真实角色
的一些业务逻辑.
- 两者的创建原理一致,需要通过创建
-
不同之处
- 静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程.
- 静态代理我们知根知底,要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的).
- 在客户端中静态代理利用接口的多态来调用被代理方法,而动态代理则比较复杂通过
Proxy.newProxyInstance
来创建一个代理实例从而进行代理.这里有人会问使用的不是反射吗?也没跟接口有关联,其实同样也是使用了多态.使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可.
优缺点是什么?
优点:
拓展新好
动态代理,不需要更改原有的代码,能在运行过程中根据接口的类型动态的调用真实角色
,符合开闭原则.解耦
代理角色
可以说是一个中介,隔离了客户端和真实角色
.
缺点:
代码量大
静态代理,需要接口和类,有比较多的重复代码,降低了维护性.编译效率?
动态代理,使用的是反射机制相对效率会降低,但实际差别如何,见下面测试代码
.代码可读性
都是对于接口实现进行代理,因此代码的可读性都不会很好.
两者的效率如何?
下面我对代理sing()
方法进行了代理测试.
public class Client {
public static void main(String[] args) throws Exception {
// 陈奕迅准备要找代理(真实角色)
EasonStar star = new EasonStar();
long start = System.currentTimeMillis();
int threadNum = 10;
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
dynamicProy(star);// 163
// staticProxy(star);//96
}
latch.countDown();
}
}).start();
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("总共耗时:" + (end - start));
}
public static void dynamicProy(EasonStar star) {
// 找到了这个代理人代理(代理角色)
StarHandler handler = new StarHandler(star);
// jdk提供的代理实例
StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), // 类加载器
new Class[] { StarInterface.class }, // 这里必须是接口,否则会报错
handler);// 代理类
proxyStar.sing();
}
public static void staticProxy(EasonStar star) {
// 找到代理人,专门为陈奕迅代理
ProxyStar proxyStar = new ProxyStar(star);
proxyStar.sing();
}
}
输出结果:
dynamicProy(star);// 163ms
staticProxy(star);// 96ms
静态代理的效率稍微会比动态代理快一些,不过也没有差别很大,因此在选择代理模式类别时,最好还是根据项目需求来筛选出合适的代理模式.
代理的应用场景?
那么代理在哪里会用到呢?如果你的项目有这几个方面的需求可以考虑使用.
• 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
• 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
• 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
• 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
代理的分类
当然除了我最常用的静态,动态代理之外根据代理的实现与目标不同还可以分成下面几种代理,具体见其他代理模式
- 安全代理:
屏蔽对真实角色
的直接访问. - 远程代理:
通过代理角色
远程方法调用(RMI) - 延迟加载:
先加载轻量级代理角色
,真正需要再加载真实角色
. - 虚拟代理
允许内存开销较大的对象在需要的时候创建.只有我们真正需要这个对象的时候才创建. - 保护代理
为不同的客户提供不同级别的目标对象访问权限.
如,你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文洁时不能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开.
项目中有没使用过呢?
在android中目前很热门的一个网路工具retrofit
源码中也使用了动态代理.
public T create(final Class service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T)
//这里是不是是曾相识呢
Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadMethodHandler(method).invoke(args);
}
});
}
参考:
java静态代理与动态代理
公共技术点之 Java 动态代理
设计模式(结构型)之代理模式
每天设计模式-代理模式
静态代理VS动态代理