ProxyPattern- 代理模式

语言: CN / TW / HK

代理模式

代理模式(Proxy Pattern):一般是指一个对象为另一个对象提供一种代理,从而通过代理对象来控制非代理对象的访问,代理对象在客户端和目标对象之间起到中介作用。

代理模式属于结构型设计模式,可以分为静态代理和动态代理两种类型,而动态代理中又分为 JDK 动态代理和 CGLIB 代理两种。

1. 静态代理模式

标准的静态代理模式需要定义一个接口,然后代理对象与被代理对象都需要实现标准接口,并重写标准接口中定义的方法,被代理对象本身需要实现真正的业务逻辑,而代理对象中一般是在调用被代理对象的前后新增一些其它逻辑处理。

示例:我们以买火车票来举例说明。

  • 标准接口 Travel.java

package cn.liangyy.proxy.staticproxy;
/** * 标准接口 */public interface Travel { /** * 买火车票 */ void buyTrainticket();}

复制代码

  • 被代理对象 TravelPerson.java

package cn.liangyy.proxy.staticproxy;
/** * 被代理对象 */public class TravelPerson implements Travel { /** * 买火车票 */ @Override public void buyTrainticket() { System.out.println("西安到成都"); System.out.println("早上10:00出发"); }}

复制代码

  • 代理类 TravelAgency.java

package cn.liangyy.proxy.staticproxy;
/** * 代理类 */public class TravelAgency implements Travel { private TravelPerson travelPerson; //被代理对象
public TravelAgency(TravelPerson travelPerson) { this.travelPerson = travelPerson; }
/** * 买火车票 */ @Override public void buyTrainticket() { before(); //调用被代理对象的原方法 this.travelPerson.buyTrainticket(); after(); }
private void before(){ System.out.println("付定金"); }
private void after(){ System.out.println("付尾款"); }}

复制代码

  • 测试 TestStaticProxy.java

package cn.liangyy.proxy.staticproxy;
/** * 静态代理模式-测试 */public class TestStaticProxy { public static void main(String[] args) { TravelAgency travelAgency = new TravelAgency(new TravelPerson()); travelAgency.buyTrainticket(); }}

复制代码

这就是一个静态代理的实现方式,可以看到还是非常方便的,但是静态代理却有其局限性:

代理对象需要显示的声明被代理对象,如果说后面想要修改代理对象,则需要修改源代码,不符合开闭原则。被代理对象如果新增了其它方法(接口新增了方法),那么代理对象也需要同步修改,不便于后期维护。所以为了解决静态代理的局限性,就有了动态代理。

2. 动态代理模式

2.1 JDK 动态代理

JDK 动态代理是 JDK 内置的一种动态代理的实现方式,使用 JDK 动态代理必须满足两个条件:

  • 定义的代理对象必须要实现 java.lang.reflect.InvocationHandle 接口

  • 被代理对象必须要显示的实现至少一个接口

示例:

  • 标准接口 Travel.java

package cn.liangyy.proxy.jdkproxy;
/** * 标准接口 */public interface Travel { /** * 买火车票 */ void buyTrainticket();}

复制代码

  • 被代理对象 JdkTravelPerson.java

package cn.liangyy.proxy.jdkproxy;
/** * 被代理类 */public class JdkTravelPerson implements Travel { /** * 买火车票 */ @Override public void buyTrainticket() { System.out.println("西安到成都"); System.out.println("早上10:00出发"); }}

复制代码

  • 代理类 JdkTravelAgency.java

package cn.liangyy.proxy.jdkproxy;
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
/** * 代理类 */public class JdkTravelAgency implements InvocationHandler { //被代理对象,即示例中的 JdkTravelPerson private Object target;
//动态获取代理对象 public Object getInstance(Object target){ //target就是被代理对象 this.target = target; Class<?> clazz = target.getClass(); //创建并返回代理对象 return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target,args); after(); return obj; }
private void before(){ System.out.println("付定金"); }
private void after(){ System.out.println("付尾款"); }}

复制代码

  • 测试 TestJdkProxy.java

package cn.liangyy.proxy.jdkproxy;
/** * JDK动态代理模式-测试 */public class TestJdkProxy { public static void main(String[] args) { Travel travel = (Travel) new JdkTravelAgency().getInstance(new JdkTravelPerson()); travel.buyTrainticket(); }}

复制代码

2.2 CGLIB 动态代理

CGLIB 是通过继承被代理对象来实现,和 JDK 动态代理需要实现 InvocationHandler 接口一样,CGLIB 也要求代理对象必须要实现 MethodInterceptor 接口,并重写其唯一的方法 intercept。

CGLIB jar包下载地址

  • 这里注意:我们建议需要下载 cglib-nodep-3.3.0.jar ,然后将此 jar 包导入项目中就可以了。如果下载的是 cglib-3.3.0.jar ,那么还需要下载 asm.jar ,因为 CGLIB 在运行期间是使用 ASM 框架写 Class 字节码的。

示例:

  • 被代理对象 CglibTravelPerson.java

package cn.liangyy.proxy.cglibproxy;
/** * 被代理对象类 */public class CglibTravelPerson { public void buyTrainticket() { System.out.println("cglib:西安到成都"); System.out.println("cglib:早上9:00出发"); }}

复制代码

  • 代理对象 CglibTravelAgency.java ,实现 MethodInterceptor 接口

package cn.liangyy.proxy.cglibproxy;
import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * 代理对象 */public class CglibTravelAgency implements MethodInterceptor { public Object getInstance(Class<?> clazz){ Enhancer enhancer = new Enhancer();//相当于 JDK 动态代理中的 Proxy 类 enhancer.setSuperclass(clazz);//设置为即将生成的代理类的父类 enhancer.setCallback(this);//设置回调对象 return enhancer.create();//相当于JDK动态代理的 Proxy.newProxyInstance 方法,生成新的字节码文件,并加载到 JVM 中 }

/** * * @param o - CBLIG 生成的代理对象 * @param method - 被代理对象中被拦截的方法 * @param objects - 方法中的参数 * @param methodProxy - 代理对象中对应的方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o,objects); after(); return obj; }
private void before() { System.out.println("付定金"); }
private void after() { System.out.println("付尾款"); }}

复制代码

  • 测试 TestCglibProxy.java

package cn.liangyy.proxy.cglibproxy;
/** * cglib动态代理模式-测试 */public class TestCglibProxy { public static void main(String[] args){ CglibTravelPerson cglibTravelPerson = (CglibTravelPerson) new CglibTravelAgency().getInstance(CglibTravelPerson.class); cglibTravelPerson.buyTrainticket(); }}

复制代码

JDK 和 CGLIB 动态代理对比

  • JDK 动态代理是通过接口来实现,CGLIB 则是通过继承被代理对象来实现,也就是说如果使用 JDK 动态代理,则要求被代理对象一定要有接口;而如果使用 CGLIB 动态代理,因为是通过继承来实现,所以无法代理 final 和 private 修饰的方法。

  • JDK 和 CGLIB 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,CGLIB 代理实现更复杂,生成代理类的效率比 JDK 代理低,但是因为 JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,故而 CGLib 执行效率更高。

代理模式优点

  • 代理模式能将代理对象与真实被调用的目标对象分离,降低了系统的耦合度,所以扩展性比较好。可以通过代理来增强被代理对象的功能。

代理模式缺点

  • 设计模式通用缺点,代理模式也会造成系统设计中类的数量增加。

  • 通过代理来调用时,请求速度会变慢,对系统性能有一定影响。