关于java:拼多多面试这样跟面试官说代理面试官对我竖起了大拇指

2次阅读

共计 11508 个字符,预计需要花费 29 分钟才能阅读完成。

什么是代理

代理模式是罕用的 java 设计模式,他的特色是代理类与委托类有同样的接口,代理类次要负责为委托类预处理音讯、过滤音讯、把音讯转发给委托类,以及预先解决音讯等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象自身并不真正实现服务,而是通过调用委托类的对象的相干办法,来提供特定的服务。

​代理其实不仅仅是在软件开发畛域,在咱们的日常生活中也是时常可见。比方某 p2p 老板忽然携款带着小姨子跑路了,可怜了上面一堆的程序员背负一身房贷,上有老下有小,程序员只能被迫去申请劳动仲裁,劳动局就会为其指派一位代理律师全权负责程序员的仲裁事宜(PS:p2p 跑路仲裁拿回工资的可能性非常低,没让你把工资退回就算好的了)。那这外面就是应用了代理模式,因为在劳动仲裁这个流动中,代理律师会全权代理程序员。比方:房东要将房子发售,于是到房地产中介公司找一个中介(代理),由他来帮房东实现销售屋宇,签订合同、网签、贷款过户等等事宜。

代理模式


这是常见代理模式常见的 UML 示意图。
须要留神的有上面几点:

  1. 用户只关怀接口性能,而不在乎谁提供了性能。上图中接口是 Subject
  2. 接口真正实现者是上图的 RealSubject,然而它不与用户间接接触,而是通过代理。
  3. 代理就是上图中的 Proxy,因为它实现了 Subject 接口,所以它可能间接与用户接触。
  4. 用户调用 Proxy 的时候,Proxy 外部调用了 RealSubject。所以,Proxy 是中介者,它能够加强 RealSubject 操作。
  • 代理又能够分为动态代理和动静代理两种。咱们先来看下动态代理。

动态代理

电影是电影公司委托给影院进行播放的,然而影院能够在播放电影的时候,产生一些本人的经济收益,比方提供按摩椅,娃娃机(这个每次去电影院都会尝试下,基本上是夹不起来,有木有大神能够传授下窍门),卖爆米花、饮料(贵的要死,反正吃不起)等。咱们平时去电影院看电影的时候,在电影开始的阶段是不是常常会放广告呢?而后在影片开始完结时播放一些广告。
上面咱们通过代码来模仿下电影院这一系列的赚钱操作。
首先得有一个接口,通用的接口是代理模式实现的根底。这个接口咱们命名为 Movie,代表电影播放的能力。

package com.workit.demo.proxy;

public interface Movie {void play();
}
  • 接下来咱们要创立一个真正的实现这个 Movie 接口的类,和一个实现该接口的代理类。

真正的类 《美国队长》 电影:

package com.workit.demo.proxy;

public class CaptainAmericaMovie implements Movie {
    @Override
    public void play() {System.out.println("一般影厅正在播放的电影是《美国队长》");
    }
}

代理类:

package com.workit.demo.proxy;

public class MovieStaticProxy implements Movie {
    Movie movie;

    public MovieStaticProxy(Movie movie) {this.movie = movie;}

    @Override
    public void play() {playStart();
        movie.play();
        playEnd();}

    public void playStart() {System.out.println("电影开始前正在播放广告");
    }
    public void playEnd() {System.out.println("电影完结了,接续播放广告");
    }
}

测试类:

package com.workit.demo.proxy;

package com.workit.demo.proxy;

public class StaticProxyTest {public static void main(String[] args) {Movie captainAmericaMovie = new CaptainAmericaMovie();
        Movie movieStaticProxy = new MovieStaticProxy(captainAmericaMovie);
        movieStaticProxy.play();}
}

运行后果:

电影开始前正在播放广告
正在播放的电影是《美国队长》电影完结了,接续播放广告

当初能够看到,代理模式能够在不批改被代理对象的根底上,通过扩大代理类,进行一些性能的附加与加强。值得注意的是,代理类和被代理类应该独特实现一个接口,或者是独特继承某个类。这个就是是动态代理的内容,为什么叫做动态呢?因为它的类型是当时预约好的,比方下面代码中的 MovieStaticProxy 这个类。

长处
  • 代理模式在客户端与指标对象之间起到一个中介作用和爱护指标对象的作用
  • 代理对象能够扩大指标对象的性能
  • 代理模式能将客户端与指标对象拆散,在肯定水平上升高了零碎的耦合度。
毛病
  • 代理对象须要与指标对象实现一样的接口, 所以会有很多代理类, 类太多. 同时, 一旦接口减少办法, 指标对象与代理对象都要保护。

jdk 动静代理

与动态代理类对照的是动静代理类,动静代理类的字节码在程序运行时由 Java 反射机制动静生成,无需程序员手工编写它的源代码。动静代理类不仅简化了编程工作,而且进步了软件系统的可扩展性,因为 Java 反射机制能够生成任意类型的动静代理类。java.lang.reflect 包中的 Proxy 类和InvocationHandler 接口提供了生成动静代理类的能力。

  • 接着下面的例子,刚看完《美国队长》不过瘾,还想持续去看一场《钢铁侠》。始终在一般影厅看电影感觉没啥意思,那就连忙去 VIP 影厅(至今不晓得长啥样子)体验一把。既然 实体店没体验过那就用代码来体验一次吧。创立一个 VIPMovie 电影接口
package com.workit.demo.proxy;
public interface VIPMovie {void vipPlay();
}

紧接着创立一个 VIP 影厅的播放实现类

package com.workit.demo.proxy;

public class IronManVIPMovie implements VIPMovie {
    @Override
    public void vipPlay() {System.out.println("VI 影厅正在播放的电影是《钢铁侠》");
    }
}

如果依照动态代理咱们是不是又要创立一个 VIP 影厅播放的代理实现类,这种形式咱们就不演示了。上面咱们来看看通过动静代理怎么来实现吧。

package com.workit.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object object;

    public MyInvocationHandler(Object object) {this.object = object;}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {playStart();
        Object invoke = method.invoke(object, args);
        playEnd();
        return invoke;
    }

    public void playStart() {System.out.println("电影开始前正在播放广告");
    }
    public void playEnd() {System.out.println("电影完结了,接续播放广告");
    }
}

MyInvocationHandler实现了 InvocationHandler 这个类,这个类是什么意思呢?大家不要慌乱,上面我会解释。而后,咱们就能够在 VIP 影厅看电影了。

package com.workit.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicProxyTest {public static void main(String[] args) {IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
        InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
        VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
                IronManVIPMovie.class.getInterfaces(), invocationHandler);
        dynamicProxy.vipPlay();}
}

输入后果:

电影开始前正在播放广告
VI 影厅正在播放的电影是《钢铁侠》电影完结了,接续播放广告

看到没有,咱们并没有像动态代理那样为 VIPMovie接口实现一个代理类,但最终它依然实现了雷同的性能,这其中的差异,就是之前探讨的动静代理所谓“动静”的起因。
咱们顺带把《美国队长》也用动静代理实现下吧。

package com.workit.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicProxyTest {public static void main(String[] args) {
        // VIP 影厅《钢铁侠》IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
        InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
        VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
                IronManVIPMovie.class.getInterfaces(), invocationHandler);
        dynamicProxy.vipPlay();
        
        // 一般影厅《美国队长》CaptainAmericaMovie captainAmericaMovie = new CaptainAmericaMovie();
        InvocationHandler invocationHandler1 = new MyInvocationHandler(captainAmericaMovie);
        Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie.class.getClassLoader(),
                CaptainAmericaMovie.class.getInterfaces(), invocationHandler1);
        dynamicProxy1.play();}
}

输入后果:

电影开始前正在播放广告
VI 影厅正在播放的电影是《钢铁侠》电影完结了,接续播放广告
电影开始前正在播放广告
正在播放的电影是《美国队长》电影完结了,接续播放广告

咱们通过 Proxy.newProxyInstance() 办法,却产生了 MovieVIPMovie两种接口的实现类代理,这就是动静代理的魔力。

JDK 动静代理到底是怎么实现的呢

动静代码波及了一个十分重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。具体怎么去创立代理类就不剖析了, 感兴趣的能够去看下源码。咱们间接看下生成的代理类。
如何查看生成的代理类?
在生成代理类之前加上以下代码(我用的 jdk1.8):

 // 新版本 jdk 产生代理类   
 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

如果上述代码加上不失效能够思考加下上面的代码:

// 老版本 jdk
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//  该设置用于输入 cglib 动静代理产生的类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");  

代码如下:

  public static void main(String[] args) {
        // 新版本 jdk 产生代理类
       System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        // VIP 影厅《钢铁侠》IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
        InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
        VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
                IronManVIPMovie.class.getInterfaces(), invocationHandler);
        dynamicProxy.vipPlay();

        // 一般影厅《美国队长》CaptainAmericaMovie captainAmericaMovie = new CaptainAmericaMovie();
        InvocationHandler invocationHandler1 = new MyInvocationHandler(captainAmericaMovie);
        Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie.class.getClassLoader(),
                CaptainAmericaMovie.class.getInterfaces(), invocationHandler1);
        dynamicProxy1.play();
        System.out.println("VIP 影厅《钢铁侠》代理类:"+dynamicProxy.getClass());
        System.out.println("一般影厅《美国队长》:"+dynamicProxy1.getClass());
    }

咱们能够看到后果

电影开始前正在播放广告
VI 影厅正在播放的电影是《钢铁侠》电影完结了,接续播放广告
电影开始前正在播放广告
正在播放的电影是《美国队长》电影完结了,接续播放广告
VIP 影厅《钢铁侠》代理类:class com.sun.proxy.$Proxy0
一般影厅《美国队长》:class com.sun.proxy.$Proxy1

产生了两个代理类别离是 $Proxy0$Proxy1
上面们来看下 ” 钢铁侠 ” 的代理类$Proxy0

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.workit.demo.proxy.VIPMovie;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements VIPMovie {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
        }
    }

    public final void vipPlay() throws  {
        try {super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.workit.demo.proxy.VIPMovie").getMethod("vipPlay");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());
        }
    }
},

通过上述代码咱们能够看到 $Proxy0 extends Proxy implements VIPMovie继承了 Proxy 且实现了VIPMovie 接口,这也就是为什么 jdk 动静代理必须基于接口,java 是单继承的。
而后再看下代理类实现的办法:

 public final void vipPlay() throws  {
        try {super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

这个 supper.h.invoke Proxy 中的 h 的 invoke 办法,即 InvocationHandler.invoke 也就是下面 MyInvocationHandler.invok e 办法,至此整个流程就清晰了。这就是 jdk 的动静代理。

cglib 动静代理

下面说 jdk 动静代理只能基于接口,那么如果是类要动静代理怎么办呢?cglib 动静代理就可解决对于类的动静代理。
上面咱们来创立一个“《美国队长 2》”

package com.workit.demo.proxy;

public class CaptainAmerica2MovieImpl {public void play(){System.out.println("正在播放的电影是《美国队长 2》");
    }
}

引入 cglib pom 依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

创立一个自定义 MethodInterceptor。

package com.workit.demo.proxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {playStart();
        Object object = methodProxy.invokeSuper(o, objects);
        playEnd();
        return object;
    }

    public void playStart() {System.out.println("电影开始前正在播放广告");
    }

    public void playEnd() {System.out.println("电影完结了,接续播放广告");
    }
}

测试类

package com.workit.demo.proxy;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {public static void main(String[] args) {
        // // 在指定目录下生成动静代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
        // 创立 Enhancer 对象,相似于 JDK 动静代理的 Proxy 类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        // 设置指标类的字节码文件
        enhancer.setSuperclass(CaptainAmerica2MovieImpl.class);
        // 设置回调函数
        enhancer.setCallback(new CglibProxyInterceptor());
        // 这里的 creat 办法就是正式创立代理类
        CaptainAmerica2MovieImpl captainAmerica2Movie = (CaptainAmerica2MovieImpl)enhancer.create();
        // 调用代理类的 play 办法
        captainAmerica2Movie.play();
        System.out.println("cglib 动静代理《美国队长 2》:"+captainAmerica2Movie.getClass());
    }
}

输入后果:

电影开始前正在播放广告
正在播放的电影是《美国队长 2》电影完结了,接续播放广告
cglib 动静代理《美国队长 2》:class com.workit.demo.proxy.CaptainAmerica2MovieImpl$$EnhancerByCGLIB$$5c3ddcfe

咱们看下最终创立的代理类生成的 play 办法

public class CaptainAmerica2MovieImpl$$EnhancerByCGLIB$$5c3ddcfe extends CaptainAmerica2MovieImpl implements Factory {public final void play() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {var10000.intercept(this, CGLIB$play$0$Method, CGLIB$emptyArgs, CGLIB$play$0$Proxy);
        } else {super.play();
        }
    }

从代理对象反编译源码能够晓得,代理对象继承于 CaptainAmerica2MovieImpl,拦截器调用intercept() 办法,
intercept()办法由自定义 CglibProxyInterceptor 实现,所以,最初调用 CglibProxyInterceptor 中的 intercept() 办法,从而实现了由代理对象拜访到指标对象的动静代理实现。

  • CGlib 是一个弱小的, 高性能, 高质量的 Code 生成类库。它能够在运行期扩大 Java 类与实现 Java 接口。
  • 用 CGlib 生成代理类是指标类的子类。
  • 用 CGlib 生成 代理类不须要接口。
  • 用 CGLib 生成的代理类重写了父类的各个办法。
  • 拦截器中的 intercept 办法内容正好就是代理类中的办法体。

总结

  • 代理分为动态代理和动静代理两种。
  • 动态代理,代理类须要本人编写代码写成。
  • 动静代理有 jdk 和 cglib,代理类通过 Proxy.newInstance()或者ASM 生成。
  • 动态代理和动静代理的区别是在于要不要开发者本人定义 Proxy 类。

动静代理通过 Proxy 动静生成 proxy class,然而它也指定了一个 InvocationHandler 或者 MethodInterceptor的实现类。

  • 代理模式实质上的目标是为了加强现有代码的性能。

完结

  • 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来, 我会对其加以修改。
  • 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
  • 感谢您的浏览, 非常欢送并感谢您的关注。

参考
https://blog.csdn.net/m0_3731…
https://www.cnblogs.com/cC-Zh…
https://www.jianshu.com/p/453…

本文由博客群发一文多发等经营工具平台 OpenWrite 公布

正文完
 0