Spring源码-AOP(八)-创建代理

前言

上一篇结束了增强器的获取,接下来我们看下代理是如何创建的。



通过时序图可以看出该模块主要是由AbstractAutoProxyCreator和ProxyFactory来完成的相应功能,下面进行详细分析。

初始化操作

接上一章wrapIfNecessary()方法,我们看下createProxy()这个方法的内部实现
【AbstractAutoProxyCreator】

············

Object proxy = createProxy(
        bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
············
}

接下来我们看createProxy()的具体实现

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    //获取当前类中的属性
    proxyFactory.copyFrom(this);
    /**
     *决定对给定的bean是否使用targetClass,而不是他的接口代理
     *检查proxyTargetClass设置以及presserveTargetClass属性
     */

    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            //添加代理接口
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        //添加增强器
        proxyFactory.addAdvisor(advisor);
    }
    //设置要被代理的类
    proxyFactory.setTargetSource(targetSource);
    //定制代理
    customizeProxyFactory(proxyFactory);
    /**
     * 用来控制代理工厂被配置之后,是否允许修改通知
     * 缺省值为false()
     */
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(getProxyClassLoader());
}

对于代理类的创建及处理,spring委托给了ProxyFactory去处理,而在函数中主要是对ProxyFactory的初始化操作,包括如下

  • 1、获取当前类中的属性
  • 2、添加代理接口。
  • 3、封装Advisor并加入到proxyFactory中
  • 4、设置要代理的类
  • 5、当然在Spring中还未子类提供了定制的函数customizeProxyFactory,子类可以在此函数中对proxyFactory的进一步封装。
  • 6、进行获取代理操作

封装逻辑

其中,封装Advisor并加入到ProxyFactory中以及创建代理是两个相对繁琐的过程,可以通过ProxyFactory提供的addAdvisor方法直接将增强器置入代理创建工厂中,但是将拦截器封装为增强器还是需要一定的逻辑的。接下来我们看看buildAdvisors()的具体实现
【AbstractAutoProxyCreator】

protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {
   // Handle prototypes correctly...
   //解析出来所以的interceptorName
   Advisor[] commonInterceptors = resolveInterceptorNames();

   List<Object> allInterceptors = new ArrayList<>();
   if (specificInterceptors != null) {
       //加入拦截器
       allInterceptors.addAll(Arrays.asList(specificInterceptors));
       if (commonInterceptors.length > 0) {
           if (this.applyCommonInterceptorsFirst) {
               allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
           }
           else {
               allInterceptors.addAll(Arrays.asList(commonInterceptors));
           }
       }
   }
   if (logger.isDebugEnabled()) {
       int nrOfCommonInterceptors = commonInterceptors.length;
       int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
       logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
               " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
   }

   Advisor[] advisors = new Advisor[allInterceptors.size()];
   for (int i = 0; i < allInterceptors.size(); i++) {
       //拦截器进行封装转化为Advisor
       advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
   }
   return advisors;
}

【DefaultAdvisorAdapterRegistry】

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
   //如果要封装的对象本身就是Advisor类型的那么无需再做过多的处理
   if (adviceObject instanceof Advisor) {
       return (Advisor) adviceObject;
   }
   //因为此封装的方法只对Advisor与Advice两种类型的数据有效,如果不是将不能进行封装
   if (!(adviceObject instanceof Advice)) {
       throw new UnknownAdviceTypeException(adviceObject);
   }
   Advice advice = (Advice) adviceObject;
   if (advice instanceof MethodInterceptor) {
       // So well-known it doesn't even need an adapter.
       //如果是MethodInterceptor类型则使用DefaultPointcutAdvisor封装
       return new DefaultPointcutAdvisor(advice);
   }
   //如果存在Advisor的适配器那么也同样需要进行封装。
   for (AdvisorAdapter adapter : this.adapters) {
       // Check that it is supported.
       if (adapter.supportsAdvice(advice)) {
           return new DefaultPointcutAdvisor(advice);
       }
   }
   throw new UnknownAdviceTypeException(advice);
}

由于Spring中涉及过多的拦截器、通知器、增强方法等方式来对逻辑进行增强,所以非常有必要统一封装成Advisor来进行代理的创建,完成了增强的封装过程,那么解析最重要的移步就是代理的创建与获取了。
【ProxyFactory】

public Object getProxy(@Nullable ClassLoader classLoader) {
   return createAopProxy().getProxy(classLoader);
}

创建代理

【ProxyCreatorSupport】

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    //创建代理
    return getAopProxyFactory().createAopProxy(this);
}

该方法封装在AopProxyFactory 接口中,由实现类DefaultAopFactory完成相应实现



【DefaultAopFactory】

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //此处判断是JDKProxy的实现还是CGLIB的实现
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

到此已经完成了代理的创建。这里最终的实现是由JDKProxy或者CGLIB代理实现。这里需要注意三个判断条件

  • 1、optimize:用了控制通过CGLIB创建的代理是否使用激进的优化策略。除非完全了解AOP。否则不统计。目前这个属性也仅仅用于CGLIB。
  • 2、proxyTargetClass:这个属性为true时,目标本身被代理而不是目标类的接口,如果这个属性为true,CGLIB代理将被创建,设置方式<aop:aspectj-autoproxy proxy-target-class="true"/>
  • 3、hasNoUserSuppliedProxyInterfaces:是否存在代理接口。

创建代理的方法

根据之前的分析得知,SpringAop动态生成代理有两种方式:JDK和CGLIB。

  • 1、目标对象实现了接口,默认采用JDK动态代理实现,也可强制使用CGLIB。
  • 2、若没有实现接口,必须采用CGLIB,Spring会自动在JDK动态代理和CGLIB之间转换。而且,jdk动态接口只能对实现了接口类生产代理,而不能针对类。CGLIB是针对类实现代理的,覆盖其中的方法,是继承实现,所以该类或方法最后不要声明诚final。
    这里需要注意:
    AopProxy才是生产代理的主要位置,而前面看到的ProxyFactory在AopProxy代理对象和IOC容器配置之间仅仅是一个桥梁作用。AopProxy代理对象可以由JDK或者CGLIB生产,如下图

JDK动态代理

在JDK代理的使用中,其实现类是JdkDynamicAopProxy,而JdkDynamicAopProxy类最为核心的是InvocationHandler接口。而在JdkDynamicAopProxy的方法中最为重要的有以下三个

  • 构造函数
  • invoke
  • getProxy()

构造函数

【JdkDynamicAopProxy】

public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
    Assert.notNull(config, "AdvisedSupport must not be null");
    if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
        throw new AopConfigException("No advisors and no TargetSource specified");
    }
    this.advised = config;
}

从源码可以看出构造函数主要用于值的传递
【JdkDynamicAopProxy】

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

该方法主要用于获取生成的代理对象,是必不可少的方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        //equals方法的处理
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        //hash方法的处理
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        /**
         * 有时候目标对象内部的自我调用将无法实施切面中的增强
         * 则需要通过属性暴露代理
         */
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.
        //获取当前方法的拦截器
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            //如果没有发现任何拦截器那么直接调入切点方法
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...
            //将拦截器封装在ReflectiveMethodInvocation
            //以便于使用期processd进行链接表拦截器
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            //执行拦截器链
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        //返回结果
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

上面函数主要工作是创建了一个拦截器链,并使用ReflectiveMethodInvocation类进行了链的封装,而在ReflectiveMethodInvocation类的proceed方法中实现了拦截器的逐一调用,接下来看看proceed方法怎么实现?
【ReflectiveMethodInvocation】

public Object proceed() throws Throwable {
    //  We start with an index of -1 and increment early.
    //执行完所有增强后执行切点方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    //获取下一个要执行的拦截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        //动态匹配
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            //不匹配则不执行拦截器
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        //将this作为参数传递保证当前实例中调用连的执行
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

在proceed方法中,ReflectiveMethodInvocation主要职责是维护了一个链接调用的计数器,记录着当前调用链接的位置,以便链可以有序的执行,那么在这个翻翻中并没有之前维护的各种增强的顺序,而是将工作委托给各个增强器,使各个增强器在内部进行逻辑实现

CGLIB代理

字节码生成代理:
CGLIB是一个强大的高性能的代码生成包。SpringAOP中完成CGLIB是通过委托给CglibAopProxy实现的,这个类的入口是getProxy。

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // Validate the class, writing log messages as necessary.
        //验证class
        validateClassIfNecessary(proxySuperClass, classLoader);

        // Configure CGLIB Enhancer...
        //创建及配置Enhancer
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
        //设置拦截器
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);

        // Generate the proxy class and create a proxy instance.
        //生成代理类以及创建代理
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                this.advised.getTargetClass() + "]: " +
                "Common causes of this problem include using a final class or a non-visible class",
                ex);
    }
    catch (Throwable ex) {
        // TargetSource.getTarget() failed
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}

CGLIB的实现最重要的就是EnHancer。以上函数完整的阐述了Spring中的Enhancer过程,这块是通过getCallBacks方法设置拦截器链的。接下来我们看看getCallBacks方法。

getCallbacks

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // Parameters used for optimization choices...
    //属性的处理
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // Choose an "aop" interceptor (used for AOP calls).
    //将拦截器封装在DYnamicAdvisedInterceptor中
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
    }
    else {
        targetInterceptor = isStatic ?
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
    }

    // Choose a "direct to target" dispatcher (used for
    // unadvised calls to static targets that cannot return this).
    Callback targetDispatcher = isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp();

    Callback[] mainCallbacks = new Callback[] {
            //将拦截器加入callback中
            aopInterceptor,  // for normal advice
            targetInterceptor,  // invoke target without considering advice, if optimized
            new SerializableNoOp(),  // no override for methods mapped to this
            targetDispatcher, this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimizations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap<>(methods.length);

        // TODO: small memory optimization here (can skip creation for methods with no advice)
        for (int x = 0; x < methods.length; x++) {
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(methods[x].toString(), x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}

至此,AOP的源码分析完成,下一篇我们会整体梳理下相关流程。

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

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,183评论 11 349
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 一> 代理模式 概述 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式;即通过代理访问目标对象。...
    奋斗的老王阅读 1,101评论 0 50
  • 前段时间朋友圈被天王的演唱会刷屏,由于种种原因没买到票的朋友也纷纷发出感慨。而我却拒绝了一张来之不易的内场票。原因...
    萧以澈阅读 330评论 0 2
  • 信息時代來了,各種傳播方式百花齊放,各類信息漫天飛舞。其中,網絡的影響最大,遠遠超過了電視廣播和報刊。不過...
    湖畔渔夫阅读 142评论 0 0