《java与设计模式》之代理模式

原文连接

简介

Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术。生活中的方方面面都可以虚拟到代码中。代理模式所讲的就是现实生活中的这么一个概念:中介。

代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

代理模式包含如下角色:

  • ISubject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口。
  • RealSubject:真实主题角色,是实现抽象主题接口的类。
  • Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

实现动态代理的关键技术是反射

静态代理

代理模式有几种,虚拟代理,计数代理,远程代理,动态代理。主要分为两类,静态代理和动态代理。静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
考虑这样的场景,管理员在网站上执行操作,在生成操作结果的同时需要记录操作日志,这是很常见的。此时就可以使用代理模式,代理模式可以通过聚合和继承两种方式实现:

/**方式一:聚合式静态代理 
 * @author Goser    (mailto:goskalrie@163.com) 
 * @Since 2016年9月7日 
 */  
//1.抽象主题接口  
public interface Manager {  
    void doSomething();  
}  
//2.真实主题类  
public class Admin implements Manager {  
    public void doSomething() {  
        System.out.println("Admin do something.");  
    }  
}  
//3.以聚合方式实现的代理主题  
public class AdminPoly implements Manager{  
    private Admin admin;  
     
    public AdminPoly(Admin admin) {  
        super();  
        this.admin = admin;  
    }  
   
    public void doSomething() {  
        System.out.println("Log:admin操作开始");  
        admin.doSomething();  
        System.out.println("Log:admin操作结束");  
    }  
}  
//4.测试代码  
        Admin admin = new Admin();  
        Manager m = new AdminPoly(admin);  
        m.doSomething();  
//方式二:继承式静态代理  
//与上面的方式仅代理类和测试代码不同  
//1.代理类  
public class AdminProxy extends Admin {  
    @Override  
    public void doSomething() {  
        System.out.println("Log:admin操作开始");  
        super.doSomething();  
        System.out.println("Log:admin操作开始");  
    }  
}  
//2.测试代码  
        AdminProxy proxy = new AdminProxy();  
        proxy.doSomething();  

聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
比如,在管理员操作的同时需要进行权限的处理,操作内容的日志记录,操作后数据的变化三个功能。三个功能的排列组合有6种,也就是说使用继承要编写6个继承了Admin的代理类,而使用聚合,仅需要针对权限的处理、日志记录和数据变化三个功能编写代理类,在业务逻辑中根据具体需求改变代码顺序即可。

动态代理

一般来说,对代理模式而言,一个主题类与一个代理类一一对应,这也是静态代理模式的特点。
但是,也存在这样的情况,有n各主题类,但是代理类中的“前处理、后处理”都是一样的,仅调用主题不同。也就是说,多个主题类对应一个代理类,共享“前处理,后处理”功能,动态调用所需主题,大大减小了程序规模,这就是动态代理模式的特点。

JDK动态代理

实现

//1. 抽象主题  
public interface Moveable {  
    void move()  throws Exception;  
}  
//2. 真实主题  
public class Car implements Moveable {  
    public void move() throws Exception {  
        Thread.sleep(new Random().nextInt(1000));  
        System.out.println("汽车行驶中…");  
    }  
}  
//3.事务处理器  
public class TimeHandler implements InvocationHandler {  
    private Object target;  
     
    public TimeHandler(Object target) {  
        super();  
        this.target = target;  
    }  
   
    /** 
     * 参数: 
     *proxy 被代理的对象 
     *method 被代理对象的方法 
     *args 方法的参数 
     * 返回: 
     *Object 方法返回值 
     */  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        long startTime = System.currentTimeMillis();  
        System.out.println("汽车开始行驶…");  
        method.invoke(target, args);  
        long stopTime = System.currentTimeMillis();  
        System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!");  
        return null;  
    }  
   
}  
//测试类  
public class Test {  
    public static void main(String[] args) throws Exception{  
        Car car = new Car();  
        InvocationHandler h = new TimeHandler(car);  
        Class<?> cls = car.getClass();  
        /** 
         *loader 类加载器 
         *interfaces 实现接口 
         *h InvocationHandler 
         */  
        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);  
        m.move();  
    }  
}  

代码讲解:
在测试代码中,Proxy.newProxyInstance()方法需要3个参数:类加载器(要进行代理的类)、被代理类实现的接口,事务处理器。所以先实例化Car,实例化InvocationHandler的子类TimeHandler,将各参数传入Proxy的静态方法newProxyInstance()即可获得Car的代理类,前面的静态代理,代理类是我们编写好的,而动态代理则不需要我们去编写代理类,是在程序中动态生成的。

JDK动态代理步骤

  1. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
  2. 创建被代理的类及接口
  3. 调用Proxy的静态方法,创建一个代理类
  4. 通过代理调用方法

而为什么要进行如此操作,可以从Proxy和InvocationHandler的源码中找打答案。对源码不感兴趣的可以将下面的源码部分小节略过。

JDK动态代理原理与源码

newProxyInstance()方法的源码:

 public static Object newProxyInstance(ClassLoader loader,  
                                         Class<?>[] interfaces,  
                                         InvocationHandler h)  
        throws IllegalArgumentException{  
        if (h == null) {  
            throw new NullPointerException();  
        }  
        final Class<?>[] intfs = interfaces.clone();  
        final SecurityManager sm = System.getSecurityManager();  
        if (sm != null) {  
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
        }  
        /*查找或生成指定的代理类*/  
        Class<?> cl = getProxyClass0(loader, intfs);  
        /*用指定的调用处理程序调用它的构造函数.*/  
        try {  
        //获得类的构造函数  
            final Constructor<?> cons =cl.getConstructor(constructorParams);  
            final InvocationHandler ih = h;  
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {  
              //当需要代理的类实现了一个非public的接口时,因为这样的接口需要特殊的权限,因此调用doPrivilege(native 修饰的方法)创建代理实例。  
                return AccessController.doPrivileged(newPrivilegedAction<Object>() {  
                    public Object run() {  
                        return newInstance(cons,ih);  
                    }  
                });  
            } else {  
                return newInstance(cons,ih);  
            }  
        } catch (NoSuchMethodException e) {  
            throw new InternalError(e.toString());  
        }  
}  

可以看到,获得代理类的代码是
Class<?>cl = getProxyClass0(loader,intfs);
并由此获得代理类的构造函数,生成代理类的实例返回给该方法的调用者。
继续跟进getProxyClass0()方法:

/** 生成代理类。调用该方法前必须使用checkproxyaccess方法执行权限检查。*/  
    private static Class<?> getProxyClass0(ClassLoader loader,  
                                          Class<?>... interfaces) {  
    //检查实现的接口数,65535这个数字好特殊,端口数好像也是这个,这个数字是由虚拟机所决定的,2^16-1个  
    if (interfaces.length > 65535) {  
            throw new IllegalArgumentException("interface limit exceeded");  
        }  
        // 如果代理类已经通过实现给定接口的类加载器创建了,则返回缓存中的该类的副本;否则将通过ProxyClassFactory创建代理类  
        return proxyClassCache.get(loader, interfaces);  
}  

还是没有看到代理类是怎么生成的,只知道代理类是从proxyClassCache中取得的,这个变量是与缓存相关的一个对象,查看该变量的声明与初始化:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 

可以发现proxyClassCache是个用来缓存代理类的类变量,大家知道类变量的特点是与类一一对应,在一个虚拟机中类只有一个,对应着在一个虚拟机中类变量也只有一个,且在此处,在Proxy类被加载的时候就赋值了。在赋值操作的参数中有ProxyClassFactory()这么一个构造函数,这个是动态代理中的关键:生成代理类的类文件字节码。继续跟进去,找到代理类的生成之处了:

/** 根据给定的类加载器和接口数组生成代理类的工厂类*/  
private static final class ProxyClassFactory  
    implements BiFunction<ClassLoader,Class<?>[], Class<?>>  
{  
    // 所有代理类名称的前缀  
    private static final String proxyClassNamePrefix = "$Proxy";  
  
    //用于生成唯一代理类名称的下一个序号  
    private static final AtomicLong nextUniqueNumber = new AtomicLong();  
  
    @Override  
    public Class<?> apply(ClassLoader loader,Class<?>[] interfaces) {  
  
        Map<Class<?>, Boolean>interfaceSet = new IdentityHashMap<>(interfaces.length);  
        for (Class<?> intf : interfaces) {  
            /* 验证类加载器将此接口的名称解析为实际对象的名称。*/  
            Class<?> interfaceClass =null;  
            try {  
                interfaceClass = Class.forName(intf.getName(),false, loader);  
            } catch (ClassNotFoundException e) {  
            }  
            if (interfaceClass != intf) {  
                throw new IllegalArgumentException(  
                    intf + " is not visible from classloader");  
            }  
            /* 验证类对象确实是一个接口。*/  
            if (!interfaceClass.isInterface()) {  
                throw new IllegalArgumentException(  
                   interfaceClass.getName() + " is not an interface");  
            }  
            /*确保接口唯一*/  
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {  
                throw new IllegalArgumentException(  
                    "repeated interface: " + interfaceClass.getName());  
            }  
        }  
  
        String proxyPkg = null;     // 代理类的包名  
  
        /*记录非公开代理接口的包,以便将代理类定义在同一个包中。确认所有非公共代理接口都在同一个包中。*/  
        for (Class<?> intf : interfaces) {  
            int flags = intf.getModifiers();  
            if (!Modifier.isPublic(flags)) {  
                String name =intf.getName();  
                int n = name.lastIndexOf('.');  
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
                if (proxyPkg == null) {  
                    proxyPkg = pkg;  
                } else if (!pkg.equals(proxyPkg)) {  
                    throw new IllegalArgumentException(  
                        "non-public interfaces fromdifferent packages");  
                }  
            }  
        }  
  
        if (proxyPkg == null) {  
            // 如果没有非公开的代理接口,使用com.sun.proxy作为包名  
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
        }  
  
        /* 生成代理类名的序号*/  
        long num = nextUniqueNumber.getAndIncrement();  
        //生成全类名  
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  
        /*生成代理类字节码 */  
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces);  
        try {  
            return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);  
        } catch (ClassFormatError e) {  
            throw new IllegalArgumentException(e.toString());  
        }  
    }  

在ProxyClassFactory中,可以看到产生代理类的具体逻辑,大致上是,根据传递的被代理类及其实现的接口生成代理类的字节码加载到缓存中,但是加载到缓存中只是一个.java文件也不能用,所以底层还有编译等操作。到这里,可以大致的看清JDK中动态代理的面孔了,实现的步骤为:

  1. 创建代理类的源码;
  2. 对源码进行编译成字节码;
  3. 将字节码加载到内存;
  4. 实例化代理类对象并返回给调用者;

底层的代码我们看不到,但是我们可以查看其生成的字节码:

//获得字节码的测试方法  
    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy1", Car.class.getInterfaces());   
        FileOutputStream out = null;   
        try {   
            out = new FileOutputStream(System.getProperty("user.dir") + "\\$Proxy1.class");   
            out.write(classFile);   
            out.flush();   
        } catch (Exception e) {   
            e.printStackTrace();   
        } finally {   
            try {   
                out.close();   
            } catch (IOException e) {   
                e.printStackTrace();   
            }   
        }  
   
//生成的字节码:  
importcn.com.goser.proxy.imooc.staticproxy.Moveable;  
importjava.lang.reflect.InvocationHandler;  
importjava.lang.reflect.Method;  
importjava.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;  
   
public final class $Proxy1 extends Proxy  
  implements Moveable  
{  
  private static Method m1;  
  private static Method m3;  
  private static Method m0;  
  private static Method m2;  
   
  public $Proxy1(InvocationHandler paramInvocationHandler)  
    throws  
  {  
    super(paramInvocationHandler);  
  }  
   
  public final boolean equals(Object paramObject)  
    throws  
  {  
    try  
    {  
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
    }  
    catch (RuntimeException localRuntimeException)  
    {  
      throw localRuntimeException;  
    }  
    catch (Throwable localThrowable)  
    {  
    }  
    throw new UndeclaredThrowableException(localThrowable);  
  }  
   
  public final void move()  
    throws Exception  
  {  
    try  
    {  
      this.h.invoke(this, m3, null);  
      return;  
    }  
    catch (Exception localException)  
    {  
      throw localException;  
    }  
    catch (Throwable localThrowable)  
    {  
    }  
    throw new UndeclaredThrowableException(localThrowable);  
  }  
   
  public final int hashCode()  
    throws  
  {  
    try  
    {  
      return ((Integer)this.h.invoke(this, m0, null)).intValue();  
    }  
    catch (RuntimeException localRuntimeException)  
    {  
      throw localRuntimeException;  
    }  
    catch (Throwable localThrowable)  
    {  
    }  
    throw new UndeclaredThrowableException(localThrowable);  
  }  
   
  public final String toString()  
    throws  
  {  
    try  
    {  
      return (String)this.h.invoke(this, m2, null);  
    }  
    catch (RuntimeException localRuntimeException)  
    {  
      throw localRuntimeException;  
    }  
    catch (Throwable localThrowable)  
    {  
    }  
    throw new UndeclaredThrowableException(localThrowable);  
  }  
   
  static  
  {  
    try  
    {  
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
      m3 = Class.forName("cn.com.goser.proxy.imooc.staticproxy.Moveable").getMethod("move", new Class[0]);  
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
      return;  
    }  
    catch (NoSuchMethodExceptionlocalNoSuchMethodException)  
    {  
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
    }  
    catch (ClassNotFoundExceptionlocalClassNotFoundException)  
    {  
    }  
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
  }  
}  

生成的字节码比较长,但是在字节码中最关键的信息是代理类的声明:
public final class $Proxy1 extends Proxy
可以看到生成的代理类是继承了Proxy类的,这就是说明了为什么使用JDK动态代理不能实现继承式动态代理,原因是Java不允许多继承,而生成的代理类本身就已经继承了Proxy类。
至此,JDK的动态代理的使用及底层原理分析完毕,揭下动态代理的神秘面纱,果然是枚美女。
至于最底层的native方法是怎么动态生成代理类的字节码我们也可以简单的模拟一下,先分析下模拟的步骤:首先要生成一段代理类的源码,然后将源码编译后生成代理类的实例返回给调用者。依据此步骤开始编写我们的模拟代码:

/** 
 * JDK java.lang.reflect.Proxy的模拟 
 * @author Goser    (mailto:goskalrie@163.com) 
 * @Since 2016年9月7日 
 */  
public class Proxy {  
    private static final String  RT = "\r\n";  
    public static Object newProxyInstance() throws Exception{  
        //声明一段源码  
        String sourceCode =  
        "packagecn.com.goser.proxy.jdk.simulate;"+ RT +  
        "importcn.com.goser.proxy.imooc.staticproxy.Admin;" + RT +  
        "importcn.com.goser.proxy.imooc.staticproxy.Manager;" + RT +  
        "//以聚合方式实现的代理主题" + RT +  
        "public class $Proxy0 implementsManager{" + RT +  
        "   privateAdmin admin;" + RT +  
        "   public$Proxy0(Admin admin) {" + RT +  
        "       super();" + RT +  
        "       this.admin= admin;" + RT +  
        "   }" + RT +  
        "   publicvoid doSomething() {" + RT +  
        "       System.out.println(\"Log:admin操作开始\");" + RT +  
        "       admin.doSomething();" + RT +  
        "       System.out.println(\"Log:admin操作结束\");" + RT +  
        "   }" + RT +  
        "}";  
        String filename = System.getProperty("user.dir") + "/src/main/java/cn/com/goser/proxy/jdk/simulate/$Proxy0.java";  
        File file = new File(filename);  
        //使用org.apache.commons.io.FileUtils.writeStringToFile()将源码写入磁盘  
        //编写到处,可以运行一下程序,可以在当前目录中看到生成的.java文件  
        FileUtils.writeStringToFile(file,sourceCode);  
        //获得当前系统中的编译器  
        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();  
        //获得文件管理者  
        StandardJavaFileManager fileMgr =complier.getStandardFileManager(null, null, null);  
        Iterable its =fileMgr.getJavaFileObjects(filename);  
        //编译任务  
        CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its);  
        //开始编译,执行完可在当前目录下看到.class文件  
        task.call();  
        fileMgr.close();  
        //load到内存  
        ClassLoader loader = ClassLoader.getSystemClassLoader();  
        Class cls = loader.loadClass("cn.com.goser.proxy.jdk.simulate.$Proxy0");  
        //生成代理类对象  
        Constructor ct = cls.getConstructor(Admin.class);  
        return ct.newInstance(new Admin());  
    }  
}  
class test{  
    public static void main(String[] args) throws Exception {  
        Manager m = (Manager)Proxy.newProxyInstance();  
        m.doSomething();  
    }  
}  

运行测试代码,结果和手工编写的结果一致,完成了JDK中动态代理的实现模拟。

cglib动态代理

前面分析到,因为Java只允许单继承,而JDK生成的代理类本身就继承了Proxy类,因此,使用JDK实现的动态代理不能完成继承式的动态代理,但是我们可以使用cglib来实现继承式的动态代理。
大名鼎鼎的Spring中就含有cglib动态代理,在此也以Spring中自带的cglib完成动态代理的实现:

//1.具体主题  
public class Train{  
    public void move(){  
        System.out.println("火车行驶中…");  
    }  
}  
//2.生成代理  
public class CGLibProxy implements MethodInterceptor {  
    private Enhancer enhancer = new Enhancer();  
    public Object getProxy(Class<?> clazz){  
        enhancer.setSuperclass(clazz);  
        enhancer.setCallback(this);  
        return enhancer.create();  
    }  
    /** 
     * 拦截所有目标类方法的调用 
     * 参数: 
     * obj目标实例对象 
     *method 目标方法的反射对象 
     * args方法的参数 
     * proxy代理类的实例 
     */  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        //代理类调用父类的方法  
        System.out.println("日志开始");  
        proxy.invokeSuper(obj, args);  
        System.out.println("日志结束");  
        return null;  
    }  
}  
//3.测试  
public class Test {  
    public static void main(String[] args) {  
        CGLibProxy proxy = new CGLibProxy();  
        Train t = (Train) proxy.getProxy(Train.class);  
        t.move();  
    }  
}  

小结
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。class与interface的区别本来就模糊,在java8中更是增加了一些新特性,使得interface越来越接近class,当有一日,java突破了单继承的限制,动态代理将会更加强大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 3,214评论 2 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 现在的人生活压力都比较大, 很多人会出现失眠, 因此现在的年轻人都喜欢在睡觉前听一下音乐, 认为这能促进睡眠, 但...
    睡法儿阅读 4,651评论 0 0
  • “不要误会,我不是针对个人,我是说在座的都是垃圾” 每次打开HR邮箱看到应聘开发的简历时,脑子里浮现都是这句话。我...
    Lyle__阅读 11,817评论 216 165
  • x君是我的好友,今年大四,因为专业的原因需要外出培训,地点离学校很远,他同另外两个舍友便商量着一起租个房子。 青岛...
    皮皮昕阅读 406评论 5 8