:::info 静态代理比动态代理更符合OOP原则,在日常开发中使用也较多。动态代理在开发框架时使用较多,例如大名鼎鼎的Spring。 :::

定义

为其他对象提供一种代理以控制对这个对象的访问。 :::info 代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和cglib动态代理。
静态代理:静态代理是指预先确定了代理与被代理者的关系 动态代理:动态代理本质上仍然是代理,情况与上面介绍的完全一样,只是代理与被代理人的关系是动态确定的。:::

使用场景

想在访问一个类时做一些控制。

代理模式应用

  • 远程代理:为一个对象在不同的地址空间提供局部代表,这样就可以隐藏一个对象存在于不同地址空间的事实。
  • 虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,这样就可以达到性能的最优化。(例:当我们打开一个很大的HTML文件时,里面可能有很多文字和图片,但是你还是可以很快的打开它,此时你所看到的是所有的文字,但图片却是一张一张的下载后才能看到,那些未打开的图片框,就是通过虚拟代理来替代了真实的图片。)(浏览器就是采用代理模式来这样优化下载的)
  • 安全代理:用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
  • 智能指引:是指当调用真实的对象时,代理处理另一些事。(例如计算真实对象的引用次数,这样对该对象没有引用时可以自动释放它;当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象前检查是否已经锁定它,以确保其他的对象不能改变它。他们都是通过代理在访问一个对象时附加一些内务处理。)

    UML

    角色定义

  • 业务类

  • 代理类

    优点

  • 职责清晰。

  • 高扩展性。
  • 智能化。

    缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

    业务场景

    王二狗公司老板突然在发工资的前一天带着小姨子跑路了,可怜二狗一身房贷,被迫提起劳动仲裁,劳动局就会为其指派一位代理律师全权负责二狗的仲裁事宜。那这里面就是使用了代理模式,因为在劳动仲裁这个活动中,代理律师会全权代理王二狗。

    静态代理

    王二狗的代理律师方文镜是在开庭前就确定的了。那映射到编程领域的话,就是指代理类与被代理类的依赖关系在编译期间就确定了,那么就是静态代理。

    ILawSuit 定义一个代表诉讼的接口

    1. public interface ILawSuit {
    2. /**
    3. * 提起诉讼
    4. */
    5. void submit(String proof);
    6. /**
    7. * 法庭辩护
    8. */
    9. void defend();
    10. }

    SecondDogWang 王二狗诉讼实现

    1. public class SecondDogWang implements ILawSuit {
    2. @Override
    3. public void submit(String proof) {
    4. System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    5. }
    6. @Override
    7. public void defend() {
    8. System.out.println(String.format("铁证如山,%s还钱","马旭"));
    9. }
    10. }

    ProxyLawyer 代理律师诉讼类

    代理律师持有王二狗的授权

    1. public class ProxyLawyer implements ILawSuit {
    2. /**
    3. * 持有要代理的那个对象
    4. */
    5. ILawSuit plaintiff;
    6. public ProxyLawyer(ILawSuit plaintiff) {
    7. this.plaintiff = plaintiff;
    8. }
    9. @Override
    10. public void submit(String proof) {
    11. plaintiff.submit(proof);
    12. }
    13. @Override
    14. public void defend() {
    15. plaintiff.defend();
    16. }
    17. }

    ProxyFactory 律师事务所 - 产生代理对象的静态代理工厂类

    1. public class ProxyFactory {
    2. public static ILawSuit getProxy() {
    3. return new ProxyLawyer(new SecondDogWang());
    4. }
    5. }

    Client

    1. public class Client {
    2. public static void main(String[] args) {
    3. ProxyFactory.getProxy().submit("工资流水在此");
    4. ProxyFactory.getProxy().defend();
    5. }
    6. }

    输出

    1. 老板欠薪跑路,证据如下:工资流水在此
    2. 铁证如山,马旭还钱

    可以看到,代理律师全权代理了王二狗的本次诉讼活动。那使用这种代理模式有什么好处呢,我们为什么不直接让王二狗直接完成本次诉讼呢?现实中的情况比较复杂,但是我可以简单列出几条:这样代理律师就可以在提起诉讼等操作之前做一些校验工作,或者记录工作。例如二狗提供的资料,律师可以选择的移交给法庭而不是全部等等操作,就是说可以对代理的对象做一些控制。例如二狗不能出席法庭,代理律师可以代为出席。。。

    动态代理

    王二狗的同事牛翠花开庭前没有确定她的代理律师,而是在开庭当天当庭选择了一个律师,映射到编程领域为这个关系是在运行时确定的。
    那既然动态代理没有为我们增强代理方面的任何功能,那我们为什么还要用动态代理呢,静态代理不是挺好的吗?凡是动态确定的东西大概都具有灵活性,强扩展的优势。上面的例子中如果牛翠花也使用静态代理的话,那么就需要再添加两个类。一个是牛翠花诉讼类,一个是牛翠花的代理律师类,还的在代理静态工厂中添加一个方法。而如果使用动态代理的话,就只需要生成一个诉讼类就可以了,全程只需要一个代理律师类,因为我们可以动态的将很多人的案子交给这个律师来处理。

    JDK动态代理

    在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler 接口、另一个则是 Proxy 类,这个类和接口是实现我们动态代理所必须用到的。

InvocationHandler 接口是给动态代理类实现的,负责处理被代理对象的操作的,而 Proxy 是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。

原理

·首先Jdk的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范。然后通过Proxy类产生的代理对象调用被代理对象的操作,而这个操作又被分发给InvocationHandler接口的 invoke方法具体执行

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

此方法的参数含义如下

  • proxy:代表动态代理对象
  • method:代表正在执行的方法
  • args:代表当前执行方法传入的实参
  • 返回值:表示当前执行方法的返回值

例如上面牛翠花案例中,我们使用Proxy类的newProxyInstance()方法生成的代理对象proxy去调用了proxy.submit(“工资流水在此”);操作,那么系统就会将此方法分发给invoke().其中proxy对象的类是系统帮我们动态生产的,其实现了我们的业务接口ILawSuit

CuiHuaNiu 牛翠花诉讼类

  1. public class CuiHuaNiu implements ILawSuit {
  2. @Override
  3. public void submit(String proof) {
  4. System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
  5. }
  6. @Override
  7. public void defend() {
  8. System.out.println(String.format("铁证如山,%s还牛翠花血汗钱","马旭"));
  9. }
  10. }

DynProxyLawyer 牛翠花代理诉讼类

  1. public class DynProxyLawyer implements InvocationHandler {
  2. /**
  3. * 被代理的对象
  4. */
  5. private Object target;
  6. public DynProxyLawyer(Object obj) {
  7. this.target = obj;
  8. }
  9. @Override
  10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11. System.out.println("案件进展:" + method.getName());
  12. Object result = method.invoke(target, args);
  13. return result;
  14. }
  15. }

ProxyFactory 律师事务所 - 代理工厂

  1. public class ProxyFactory {
  2. public static Object getDynProxy(Object target) {
  3. InvocationHandler handler = new DynProxyLawyer(target);
  4. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
  5. }
  6. }

Client

  1. public class Client {
  2. public static void main(String[] args) {
  3. ILawSuit proxy = (ILawSuit) ProxyFactory.getDynProxy(new CuiHuaNiu());
  4. proxy.submit("工资流水在此");
  5. proxy.defend();
  6. }
  7. }

输出

  1. 案件进展:submit
  2. 老板欠薪跑路,证据如下:工资流水在此
  3. 案件进展:defend
  4. 铁证如山,马旭还牛翠花血汗钱

CGLIB动态代理

由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。

原理

CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB缺点:对于final方法,无法进行代理**。**

XuanLao 轩佬诉讼类

  1. public class XuanLao {
  2. public void submit(String proof) {
  3. System.out.println(String.format("老板欠薪跑路,证据如下:%s", proof));
  4. }
  5. public void defend() {
  6. System.out.println(String.format("铁证如山,%s还轩佬血汗钱", "马旭"));
  7. }
  8. }

DynProxyLawyer 轩佬代理律师类

  1. public class DynProxyLawyer implements MethodInterceptor {
  2. @Override
  3. public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
  4. if (method.getName().equals("submit")) {
  5. System.out.println("案件提交成功,证据如下:" + Arrays.asList(params));
  6. }
  7. Object result = methodProxy.invokeSuper(o, params);
  8. return result;
  9. }
  10. }

ProxyFactory 律师事务所 - 代理工厂

  1. public class ProxyFactory {
  2. public static Object getGcLibDynProxy(Object target) {
  3. Enhancer enhancer = new Enhancer();
  4. enhancer.setSuperclass(target.getClass());
  5. enhancer.setCallback(new DynProxyLawyer());
  6. Object targetProxy = enhancer.create();
  7. return targetProxy;
  8. }
  9. }

Client

  1. public class Client {
  2. public static void main(String[] args) {
  3. XuanLao cProxy = (XuanLao) ProxyFactory.getGcLibDynProxy(new XuanLao());
  4. cProxy.submit("工资流水在此");
  5. cProxy.defend();
  6. }
  7. }

输出

  1. 案件提交成功,证据如下:[工资流水在此]
  2. 老板欠薪跑路,证据如下:工资流水在此
  3. 铁证如山,马旭还轩佬血汗钱

可见,通过cgLib对没有实现任何接口的类做了动态代理,达到了和前面一样的效果。这里只是简单的讲解了一些cgLib的使用方式,有兴趣的可以进一步了解其比较高级的功能,例如回调过滤器(CallbackFilter)等。

框架案例

Sping AOP