本文主要给大家介绍代理模式的定义,用途以及如何实现代理。“代理”一词,相信大家在日常生活中也是经常听到的吧,例如:某个人有多套房子,希望链家帮他出租或者销售出去。又或者某个大明星不想参演某个电视剧,助理帮忙拒绝。这时链家和助理其实就充当的是代理的作用。
定义与特点
给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问。通俗来讲代理对象就是代替真实对象与其它对象进行交互,由代理对象来调用真实对象进行处理。
在日常中,我们很多时候也会使用到代理模式,不一定是在框架中使用,其它地方也会使用到。例如:某个复杂而且代码老旧的项目,为了使它正常的运行,太多的时候,我们都不敢直接修改源码,如果上文依赖不是特别强的情况下,就可以使用代理模式动态扩展,依照项目情况决定。
使用代理模式有很多的好处,总结三点:1.职责清晰。代理对象代替真实对象与其它对象交互,真实对象只需要关注自己的业务逻辑。2.扩展性强。代理对象可以在原对象的实现上增添额外的内容,而不需要修改原有对象。这里是典型的“对扩展开放,对修改关闭”原则。3.保护作用。代理对象能够很好的隐藏真实对象的实现内容。
既然存在优点,那么也有缺点,例如:1.额外的开销。本来之前我们访问一个对象直接调用该对象就可以完成任务,现在加上一层代理,需要额外的代码量,增加复杂度。2.可维护性降低。过多的使用代理模式,可能导致系统中相似的类增多,不利于维护。
分类
从实现方式来讲,代理模式可以分为两大类:静态代理和动态代理。动态代理又分为JDK动态代理和CGLIB动态代理。其中静态代理和JDK动态代理都是针对接口编程,CGLIB动态代理针对具体类。大家应该都比较熟悉Spring吧,其中SpringAop就采用代理模式,当我们的真实对象使用接口就使用JDK动态代理,若使用具体类就采用CGLIB动态代理。下面我们来具体介绍一下这三种方式的实现。
静态代理
静态代理的实现,需要三种对象:抽象对象(真实对象的接口),真实对象(实现抽象对象),代理对象(实现抽象对象)。实现如下:
//共同的接口:StaticProxyInterface
public interface StaticProxyInterface {
void save();
}
//真实对象
class RealObject implements StaticProxyInterface {
@Override
public void save() {
System.out.println("------保存数据----------");
}
}
//代理对象
class ProxyObject implements StaticProxyInterface {
//真实对象
private StaticProxyInterface realObject;
public ProxyObject(StaticProxyInterface realObject) {
this.realObject = realObject;
}
@Override
public void save() {
System.out.println("保存开始时间:" + System.currentTimeMillis());
this.realObject.save();
System.out.println("保存结束时间:" + System.currentTimeMillis());
}
}
测试:
StaticProxyInterface realObject = new RealObject();
ProxyObject proxyObject = new ProxyObject(realObject);
proxyObject.save();
从上述实现可以发现,在实现静态代理时,真实对象与代理对象都需要实现同一接口,另外需要使用组合的方式将真实对象添加到代理对象中。这样实现的方式有一个很大的优点,代理对象可以代理实现该接口的所有子类并且在原有的基础上扩展功能。但是,这样实现也有很大的缺点,如果接口增加方法,那么代理类必须也实现相同的方法,这样系统则会出现许多类似的对象,不易于维护。所以静态代理在日常项目中使用相对较少。
代理模式的重点就在于动态代理,所以接下来的两种实现方式才是我们关注的重点。
JDK动态代理
实现JDK动态代理,我们需要创建接口,真实对象,代理对象。其中接口,真实对象我们还是使用上述静态代理代码中的内容,代理对象的创建如下:
class ProxyObject {
//真实对象
private StaticProxyInterface realObject;
public ProxyObject(StaticProxyInterface realObject) {
this.realObject = realObject;
}
public Object newProxyInstance() {
return Proxy.newProxyInstance(realObject.getClass().getClassLoader(), realObject.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("保存开始时间:" + System.currentTimeMillis());
//利用反射执行目标对象的方法并返回代理对象
Object invoke = method.invoke(realObject, args);
System.out.println("保存结束时间:" + System.currentTimeMillis());
return invoke;
}
});
}
}
JDK动态代理创建代理对象不需要实现同一接口,另外代理对象的创建我们需要依赖java.lang.reflect.Proxy类来进行,该类有一个特别重要的方法:
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h);
- ClassLoader loader:指定当前目标对象的类加载器。
- Class<?>[] interfaces:指定当前目标对象实现的接口的类型。
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的invoke方法并返回代理对象,我们的逻辑都是在该方法中实现。
使用JDK动态代理,可以在运行时动态的为StaticProxyInterface的子类创建代理对象,扩展性强。在这里,我们的接口只有一个方法save(),假设现在该接口有多个方法,多个方法都需要在原有的基础上扩展,这个时候可以在invoke方法中做如下判断,伪代码:
String name=method.getName();
if("".equals(name)){
//TODO
}else if("".equals(name)){
//TODO
}else{
//TODO
}
JDK动态代理依赖接口,底层采用反射机制调用目标对象方法。
CGLIB动态代理
CGLIB动态代理与JDK动态代理最大的区别在于,前者依赖具体类,后者依赖接口。要想使用CGLIB动态代理,我们必须引入CGLIB的依赖包,例如:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
示例如下:
//目标对象
public class Shopping {
public void shop() {
System.out.println("-------我要买买买-------");
}
}
//实现MethodInterceptor 对目标对象进行拦截
class ProxyShopping implements MethodInterceptor {
private Object realObject;
public ProxyShopping(Object realObject) {
this.realObject = realObject;
}
//创建代理对象
public Object newProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Shopping.class);//设置父类,也就是继承Shopping
enhancer.setCallback(this);//设置回调,也就是对目标对象进行拦截
return enhancer.create();//动态生成字节码,创建代理对象
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-------我要带信用卡---------");
Object invoke = method.invoke(realObject, objects);
System.out.println("-------信用卡刷爆了---------");
return invoke;
}
}
测试:
Shopping shopping = new Shopping();
Shopping proxyInstance= (Shopping)new ProxyShopping(shopping).newProxyInstance();
proxyInstance.shop();
使用CGLIB动态代理,不需要有目标接口,有具体的实现类就可以,但是目标类中的static以及final方法是不能得到代理的。CGLIB动态代理采用的是ASM(字节码编辑类库)动态生成字节码技术实现,JDK动态代理采用的是反射增强目标对象的功能。关于这两种方式,并不能强调某种代理方式的性能更加好,只能说在相应的情形下采用相应的方式,就好比与Spring Aop中,针对bean中有接口就采用JDK动态代理,只有具体实现类的就采用CGLIB动态代理,所以我们不必太去纠结两者的性能,合理使用即可。
总结
代理模式,在日常开发中,都是极其常见的。常常分为两种类型:静态代理和动态代理(JDK动态代理、CGLIB动态代理)。使用代理模式,我们可以在不修改原有类的情况下,扩展其功能,完全符合开闭原则;另外目标对象只需要关注自己的业务逻辑,符合单一职责原则。如何选择代理模式,大家可以根据目前项目情形,合理的选择。在Spring Aop中,利用代理模式处理日志、异常、发送邮件、短信,是很常见的业务。以上内容属于个人的意见,若有不对的地方,望大家指点。