代理-你可能需要知道这些

代理在我们日常开发中是一个很常见的知识点,也是我们面试中经常被问到的内容,本本博文带大家来学习和分析下代理的相关内容。

1. 概念

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子.

其中的明星我们一般称为委托类,或者称之为被代理类;而明星的经纪人我们称之为代理类。

代理的优点:

  1. 隐藏委托类的实现。
  2. 解耦,在不改委托类的实现下添加一些额外操作。

2. 分类

根据运行前委托类是否存在,我们将代理分为两类:

  • 静态代理
  • 动态代理

2.1 静态代理

代理类在程序运行前已经存在的代理方式称之为静态代理。

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

2.1.1 实例

/**
 * 接口
 */
public interface IUserDao {

    void save();
}

/**
 * 接口实现
 * 目标对象
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

/**
 * 代理对象,静态代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("开始事务...");
        target.save();//执行目标对象的方法
        System.out.println("提交事务...");
    }
}

优缺点:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

  • 因为代理对象需要与被代理对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,被代理对象与代理对象都要维护。

那么有没有什么方法,可以解决静态代理的缺点呢?有,动态代理。

2.2 动态代理

代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。

Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。
这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。

2.2.1 实现

  • 新建委托类
  • 实现InvocationHandler,这是负责代理类和委托类的中间类必须实现的接口。
  • 通过Proxy类实现新建代理类对象
  1. 新建委托类
public interface Operate {

    public void operateMethod1();

    public void operateMethod2();

    public void operateMethod3();
}

public class OperateImpl implements Operate {

    @Override
    public void operateMethod1() {
        System.out.println("Invoke operateMethod1");
        sleep(110);
    }

    @Override
    public void operateMethod2() {
        System.out.println("Invoke operateMethod2");
        sleep(120);
    }

    @Override
    public void operateMethod3() {
        System.out.println("Invoke operateMethod3");
        sleep(130);
    }

    private static void sleep(long millSeconds) {
        try {
            Thread.sleep(millSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 实现InvocationHandler
public class TimingInvocationHandler implements InvocationHandler {

    private Object target;

    public TimingInvocationHandler() {}

    public TimingInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object obj = method.invoke(target, args);
        System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
        return obj;
    }
}

  • target属性表示委托类对象。
  • InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个
public Object invoke(Object proxy, Method method, Object[] args)

函数需要去实现,参数:

  • proxy表示下面通过 Proxy.newProxyInstance() 生成的代理类对象。
  • method表示代理对象被调用的函数。
  • args表示代理对象被调用的函数的参数。

调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数。这里我们在invoke实现中添加了开始结束计时,其中还调用了委托类对象target的相应函数,这样便完成了统计执行时间的需求。
invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。

  1. 生成代理对象
public class Main {
    public static void main(String[] args) {
        // create proxy instance
        TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
        Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
                timingInvocationHandler));

        // call method of proxy instance
        operate.operateMethod1();
        System.out.println();
        operate.operateMethod2();
        System.out.println();
        operate.operateMethod3();
    }
}
  • 这里我们先将委托类对象new OperateImpl()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
  • 然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandler的invoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new OperateImpl()相应的 method(是不是有点类似静态代理)。

2.2.2 newProxyInstance

下面我们来分下Proxy.newProxyInstance方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • loader表示类加载器
  • interfaces表示委托类的接口,生成代理类时需要实现这些接口
  • h是InvocationHandler实现类对象,负责连接代理类和委托类的中间类

我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。

3. 动态代理原理

3.1 生成的动态代理类代码

下面是上面示例程序运行时自动生成的动态代理类代码。

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

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

  public final void operateMethod1()
    throws 
  {
    try
    {
      h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void operateMethod2()
    throws 
  {
    try
    {
      h.invoke(this, m5, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void operateMethod3()
    throws 
  {
    try
    {
      h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

从中我们可以看出动态生成的代理类是以$Proxy为类名前缀,继承自Proxy,并且实现了Proxy.newProxyInstance(…)第二个参数传入的所有接口的类。
如果代理类实现的接口中存在非 public 接口,则其包名为该接口的包名,否则为com.sun.proxy。
其中的operateMethod1()、operateMethod2()、operateMethod3()函数都是直接交给h去处理,h在父类Proxy中定义为
protected InvocationHandler h;
即为Proxy.newProxyInstance(…)第三个参数。
所以InvocationHandler的子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。

3.2 生成动态代理类原理

  1. newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            return newInstance(cons, h);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException | InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

从上面的代码我们可以看到,调用了getProxyClass0得到动态代理类,然后将h传入了动态代理类。

  1. getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 判断委托类的接口数量,超载直接抛出异常
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 声明代理类对象
        Class<?> proxyClass = null;

        /* collect interface names to use as key for proxy class cache */
        String[] interfaceNames = new String[interfaces.length];

        // for detecting duplicates
        Set<Class<?>> interfaceSet = new HashSet<>();

      /**
        * 入参 interfaces 检验,包含三部分
        * (1)是否在入参指定的 ClassLoader 内
        * (2)是否是 Interface
        * (3)interfaces 中是否有重复
        */
        for (int i = 0; i < interfaces.length; i++) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            String interfaceName = interfaces[i].getName();
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != interfaces[i]) {
                throw new IllegalArgumentException(
                    interfaces[i] + " is not visible from class loader");
            }

            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }

            /*
             * Verify that this interface is not a duplicate.
             */
            if (interfaceSet.contains(interfaceClass)) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
            interfaceSet.add(interfaceClass);

            interfaceNames[i] = interfaceName;
        }

        /*
         * Using string representations of the proxy interfaces as
         * keys in the proxy class cache (instead of their Class
         * objects) is sufficient because we require the proxy
         * interfaces to be resolvable by name through the supplied
         * class loader, and it has the advantage that using a string
         * representation of a class makes for an implicit weak
         * reference to the class.
         */
        // 以接口名对应的 List 作为缓存的 key
        List<String> key = Arrays.asList(interfaceNames);

        /*
         * Find or create the proxy class cache for the class loader.
         */
        /*
        * loaderToCache 是个双层的 Map
        * 第一层 key 为 ClassLoader,第二层 key 为 上面的 List,value 为代理类的弱引用
        */
        Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
            /*
             * This mapping will remain valid for the duration of this
             * method, without further synchronization, because the mapping
             * will only be removed if the class loader becomes unreachable.
             */
        }

        /*
         * Look up the list of interfaces in the proxy class cache using
         * the key.  This lookup will result in one of three possible
         * kinds of values:
         *     null, if there is currently no proxy class for the list of
         *         interfaces in the class loader,
         *     the pendingGenerationMarker object, if a proxy class for the
         *         list of interfaces is currently being generated,
         *     or a weak reference to a Class object, if a proxy class for
         *         the list of interfaces has already been generated.
         */
        /*
         * 以上面的接口名对应的 List 为 key 查找代理类,如果结果为:
         * (1) 弱引用,表示代理类已经在缓存中
         * (2) pendingGenerationMarker 对象,表示代理类正在生成中,等待生成完成通知。
         * (3) null 表示不在缓存中且没有开始生成,添加标记到缓存中,继续生成代理类
         */
        synchronized (cache) {
            /*
             * Note that we need not worry about reaping the cache for
             * entries with cleared weak references because if a proxy class
             * has been garbage collected, its class loader will have been
             * garbage collected as well, so the entire cache will be reaped
             * from the loaderToCache map.
             */
            do {
                Object value = cache.get(key);
                if (value instanceof Reference) {
                    proxyClass = (Class<?>) ((Reference) value).get();
                }
                if (proxyClass != null) {
                    // proxy class already generated: return it
                    return proxyClass;
                } else if (value == pendingGenerationMarker) {
                    // proxy class being generated: wait for it
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                        /*
                         * The class generation that we are waiting for should
                         * take a small, bounded time, so we can safely ignore
                         * thread interrupts here.
                         */
                    }
                    continue;
                } else {
                    /*
                     * No proxy class for this list of interfaces has been
                     * generated or is being generated, so we will go and
                     * generate it now.  Mark it as pending generation.
                     */
                    cache.put(key, pendingGenerationMarker);
                    break;
                }
            } while (true);
        }

        try {
            String proxyPkg = null;     // package to define proxy class in

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            /*
             * 如果 interfaces 中存在非 public 的接口,则所有非 public 接口必须在同一包下面,后续生成的代理类也会在该包下面
              */
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].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 from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use the default package.
                proxyPkg = "";
            }

            {
                // Android-changed: Generate the proxy directly instead of calling
                // through to ProxyGenerator.
                List<Method> methods = getMethods(interfaces);
                Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                validateReturnTypes(methods);
                List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

                Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

                /*
                 * Choose a name for the proxy class to generate.
                 */
                final long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                // 动态生成代理类的字节码
                // 最终调用 sun.misc.ProxyGenerator.generateClassFile() 得到代理类相关信息写入 DataOutputStream 实现
                proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                        exceptionsArray);
            }
            // add to set of all generated proxy classes, for isProxyClass
            proxyClasses.put(proxyClass, null);

        } finally {
            /*
             * We must clean up the "pending generation" state of the proxy
             * class cache entry somehow.  If a proxy class was successfully
             * generated, store it in the cache (with a weak reference);
             * otherwise, remove the reserved entry.  In all cases, notify
             * all waiters on reserved entries in this cache.
             */
            synchronized (cache) {
                if (proxyClass != null) {
                    cache.put(key, new WeakReference<Class<?>>(proxyClass));
                } else {
                    cache.remove(key);
                }
                cache.notifyAll();
            }
        }
        return proxyClass;
    }


4. CGLib实现代理

4.1 与JDK代理区别

JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final

4.2 对比

JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,它有几个要求

  • 实现InvocationHandler
  • 使用Proxy.newProxyInstance产生代理对象
  • 被代理的对象必须要实现接口
    使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。

CGLib 必须依赖于CGLib的类库,Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。针对接口编程的环境下推荐使用JDK的代理。从执行效率上看,Cglib动态代理效率较高。在Hibernate中的拦截器其实现考虑到不需要其他接口的条件Hibernate中的相关代理采用的是CGLib来执行。

4.3 CGLib实例

实例:

/**   
 * CGLibProxy动态代理类的实例   
 *     
 *    
 */    
public class CGLibProxy implements MethodInterceptor {    
    
    private Object targetObject;// CGLib需要代理的目标对象    
    
    public Object createProxyObject(Object obj) {    
        this.targetObject = obj;    
        Enhancer enhancer = new Enhancer();    
        enhancer.setSuperclass(obj.getClass());    
        enhancer.setCallback(this);    
        Object proxyObj = enhancer.create();    
        return proxyObj;// 返回代理对象    
    }    
    
    public Object intercept(Object proxy, Method method, Object[] args,    
            MethodProxy methodProxy) throws Throwable {    
        Object obj = null;    
        if ("addUser".equals(method.getName())) {// 过滤方法    
            checkPopedom();// 检查权限    
        }    
        obj = method.invoke(targetObject, args);    
        return obj;    
    }    
    
    private void checkPopedom() {    
        System.out.println(".:检查权限  checkPopedom()!");    
    }    
}    


// 调用

UserManager userManager = (UserManager) new CGLibProxy()    
                .createProxyObject(new UserManagerImpl());   

5. 结束语

详细大家通过上面的学习,已经对代理有了一个更深层次的认识,代理在AOP开发中特别有用,让我们在设计和开发中把代理用起来吧。

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

推荐阅读更多精彩内容

  • Java代理和动态代理机制分析和应用 概述 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个...
    丸_子阅读 2,994评论 6 57
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,151评论 11 349
  • 《转》JAVA动态代理(JDK和CGLIB) 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接...
    奈何心善阅读 259评论 0 0
  • 不要打我,不要打我。 你只是一只丑小鸭,还想和我们一起玩,你活腻歪了吧。狠以后别来烦我们。 为什么,所有人都不喜欢...
    青诡阅读 165评论 0 1
  • 蒲公英恋上树的落寞,树恋上浮云的自在,也许初相遇,已是场注定了的安排。 每一次倾心,最初总是不经意的邂逅。...
    玄米_1225阅读 209评论 1 4