介绍:在一些情况下,我们需要在一个业务的前后来实现一些逻辑,这时候我们就可以考虑代理模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,并通过代理来对业务进行扩展。
例子:演艺圈中的艺人。但是,他们都会有一个经纪人。经纪人负责去接业务,分析业务价格,从中获取利益,再让演员去拍戏。可以看到,经纪人不演戏(根本不会^_^),但是我们可以通过他们让演员演戏。这里的经纪人就相当于代理模式中的代理类,在客户端和目标对象之间起到中介的作用。
优点:
1. 减少耦合
2. 实现业务的扩展
3. 保证原来代码的稳定
一、角色及作用划分
角色 | 作用 |
---|---|
主题接口 | 定义代理类和主题的公共对外方法 |
主题(被代理类–演员) | 实现业务逻辑的类 |
代理类(经纪人) | 用来代理和封装主题 |
客户端 | 使用代理类和主题接口完成一些工作 |
二、静态代理
简述:由程序员创建代理类,在程序运行前代理类的.class文件就已经存在,代理类和委托类的关系在编写代码的时候就已经决定。
场景:我们的成龙大哥和路人甲原来在演艺圈中又需要管钱又需要演戏
// 主题接口
public interface Actor {
public void doAction(int money);
}
// 主题:成龙大哥
public class BusinessImpl implements Business {
@Override
public void doAction(int money) {
System.out.println("收了" + money + ",开始工作");
}
}
// 主题:路人甲
public class PasserbyJIA implements Actor {
@Override
public void doAction(int money) {
System.out.println("收了" + money + ",开始工作");
}
}
有一天,成龙大哥出名了,他希望只接重头戏了,小戏份不能入眼。
好的,可能大家会说很简单,代码如下:
public void doAction() {
if (money < 50) {
System.out.println("钱不够,干个毛?");
} else {
System.out.println("收了" + money + ",开始工作");
}
}
可是,这样路人甲就不满了,我接不到重头戏啊,我还要靠演路人工作啊。可见,这种修改会对原来的所有演员的业务产生影响。
这样,我们可以为成龙大哥找个经纪人,让其来接业务,成龙大哥只管收钱演戏就行了:
public class ChenLongProxy implements Actor {
private Actor bi;
// 这里可以定义多个代理的角色:如JummpImpl等
public ChenLongProxy(Actor bi) {
this.bi = bi;
}
@Override
public void doAction(int money) {
if (money < 50) {
System.out.println("钱不够,干个毛?");
} else {
System.out.println("经纪人收了20");
bi.doAction(money - 20);
}
}
}
之后,我们在B公司的代码中使用代理类
public static void main(String[] args) {
ChenLong impl = new ChenLong();
// 引用变量定义为抽象角色类型
Actor bi = new ChenLongProxy(impl);
bi.doAction(30);
System.out.println("======");
bi.doAction(50);
}
输出如下:
钱不够,干个毛?
======
经纪人收了20
收了30,开始工作
三、动态代理
简述:代理类在程序运行时更具所提供的被代理类运用反射机制动态创建而成。下面分别说明两种动态代理方法—jdk动态代理和cglib动态代理(还有javassist动态代理,但网上说性能不佳,而且实现方式和cglib类似,这里就不详细说明)。
ps:JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
(1) jdk动态代理
基于JDK自带的动态代理方式需要实现InvocationHandler接口。
优点:JDK自带,底层实现使得动态类创建过程快。无需第三方jar包。
public class JDKProxy implements InvocationHandler {
private Object obj;
JDKProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
doBefore();
result = method.invoke(obj, args);
doAfter();
return result;
}
public void doBefore() {
System.out.println("do something before" + obj.getClass());
}
public void doAfter() {
System.out.println("do something after" + obj.getClass());
}
public static Object factory(Object obj) {
Class<?> cls = obj.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),
new JDKProxy(obj));
}
}
而我们使用的时候如下:
BusinessImpl b = new BusinessImpl();
Business bf = (Business) JDKProxy.factory(b);
bf.doAction();
jdk动态代理的时候,在我们Proxy.newProxyInstance需要传入的参数有类加载器,类所实现的所有接口,动态代理类。可是,如果我们的类没有实现任何接口又该怎么办?
(2) cglib动态代理
基于cglib的jar包的代理模式使用方法,使用之前需要导入cglib的jar包。
优点:JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
public class BusniessCGLIB {
public void doAction() {
System.out.println("工作");
}
}
// cglib动态代理类需实现MethodInterceptor接口
public class CGLIBProxy implements MethodInterceptor {
private Object obj;
public CGLIBProxy(Object obj) {
this.obj = obj;
}
public Object factory() {
Enhancer enhancer = new Enhancer();
// 通过字节码技术动态创建子类实例
enhancer.setSuperclass(obj.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("do before");
// 通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("do after");
return result;
}
}
调用方式类似
// CGLIB
BusniessCGLIB buso = new BusniessCGLIB();
buso.doAction();
CGLIBProxy cglib = new CGLIBProxy(buso);
BusniessCGLIB bus = (BusniessCGLIB) cglib.factory();
bus.doAction();
jdk动态代理创建代理类的性能优于cglib,但是在调用代理方法的时候,cglib性能远高于jdk。但是,一般来说创建对象的次数应该远小于方法调用,所以性能方面考虑重点应该为方法调用。或者我们在对象为单利的时候应该使用cglib比较合适。
四、代理模式和装饰者模式的区别
请参考装饰者模式中的最后一节——>http://blog.csdn.net/oDeviloo/article/details/52488283
更多模式: 一天一个设计模式—分类与六大原则