OpenFeign源碼

OpenFeign其實還是負責通信的,在使用過程中,讓開發者可以面向接口開發的方式進行遠程調用,那么:

  1. OpenFeign的注解是如何被掃描解析的呢?
  2. 注解標注的接口注入到IoC中的實例是什么?
  3. OpenFeign的上下文是如何構建的呢?
  4. 遠程通信是如何實現的呢?與RestTemplate有關系么?

帶著以上的問題,準備開始Feign的源碼,同樣的 在Spring的生態下,切入點還是那幾個,EnableFeignClientsFeignAutoConfiguration,其實 如果看一眼spring-cloud-openfeign-core里面的spring.factories就發現 這里面其實有好多AutoConfiguration。

OpenFeign初始化

@EnableFeignClients

這個注解

/**
 * Scans for interfaces that declare they are feign clients (via
 * {@link org.springframework.cloud.openfeign.FeignClient} <code>@FeignClient</code>).
 * Configures component scanning directives for use with
 * {@link org.springframework.context.annotation.Configuration}
 * <code>@Configuration</code> classes.
 * 掃描所有標注了@FeignClient的接口 將其配置成配置類
 */
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    ....
}

這時候 關注點就應該在FeignClientsRegistrar

FeignClientsRegistrar

雖然沒有在任何的配置中看到 實例化它,但是它實例化的還是很早的,在 org.springframework.context.annotation.ConfigurationClassParser#processImports

registerBeanDefinitions

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    ....
    // 重點關注這里 ImportBeanDefinitionRegistrar#registerBeanDefinitions
    // 兩個參數分別是StandardAnnotationMetadata, DefaultListableBeanFactory
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
                                        BeanDefinitionRegistry registry) {
        
        // 如果注解中的defaultConfiguration不為空 則將其注入到IoC容器
        registerDefaultConfiguration(metadata, registry);
        // 這里是注入FeignClient的地方
        registerFeignClients(metadata, registry);
    }
    ....
}

registerFeignClients

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    ....
    public void registerFeignClients(AnnotationMetadata metadata,
                                     BeanDefinitionRegistry registry) {

        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        // 獲取 @EnableFeignClients 所有的配置信息
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        // 獲取 @EnableFeignClients.clients 的值
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 這個集合必然不為空 分別取了value、basePackages、basePackageClasses
            // 如果那三個為空 則將標注了@EnableFeignClients 的類所在的路徑加入到這個集合
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        } else {
            for (Class<?> clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }
        
        // 遍歷 并生成代理類 并注入到IoC容器
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@FeignClient can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                // @FeignClient的value值
                String name = getClientName(attributes);
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注入標注了@FeignClient的接口
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
    ....
    
}

registerFeignClient

這里面沒啥可說的 就是將被FeignClient標注的接口注入到IoC容器 注入的是 FeignClientFactoryBean

目前這個階段是初始化,實例化的時候會調用FeignClientFactoryBean.getObject()

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
        
    ....
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        // 校驗了 fallback和fallbackFactory
        validate(attributes);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        // FeignClient并無contextId的配置 這里取的還是name
        String contextId = getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

        // has a default, won't be null. default is true
        boolean primary = (Boolean) attributes.get("primary");

        beanDefinition.setPrimary(primary);

        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    ....
}

FeignClientFactoryBean#getTarget

首先要清楚FeignClientFactoryBean的實例里面在上面的步驟已經將它里面的值做了初始化。

而在doGetBean時 會調用getObject方法 ,這里如果不清楚,可以看案例關于FactoryBean

其次就是理解getTarget做了哪些事情:

  1. 生成遠程代理
  2. 解析接口模板,例如@GetMapping等
  3. 處理負載均衡 集成Ribbon
  4. 包裝熔斷 繼承Hystrix
<T> T getTarget() {
    // 這個就是feign的上下文 與spring 容器的上下文隔離開了
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // 構建一個Feign的Builder
    Feign.Builder builder = feign(context);

    // 這里判斷的是 @FeignClient 的url是否做了配置
    if (!StringUtils.hasText(url)) {
        if (!name.startsWith("http")) {
            url = "http://" + name;
        }
        else {
            url = name;
        }
        url += cleanPath();
        // 這里做負載均衡
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(type, name, url));
    }
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
        url = "http://" + url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        if (client instanceof FeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(type, name, url));
}

FeignClientFactoryBean#feign

只是構建了Feign.Builder,其實就是將一些參數記錄。

protected Feign.Builder feign(FeignContext context) {
    
    // get方法 = context.getInstance(contextId, type)
    // 其中 context就是第一個參數 type是第二個參數 
    // contextId是FeignClientsRegistrar.registerFeignClient方法里面設置的
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    // 記錄日志的 this.logger != null ? this.logger : new Slf4jLogger(type)
    Logger logger = loggerFactory.create(type);

    // @formatter:off
    Feign.Builder builder = get(context, Feign.Builder.class)
        // required values
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
    // @formatter:on

    // 配置相關的內容 logger encoder decoder...
    configureFeign(context, builder);

    return builder;
}

FeignClientFactoryBean#loadBalance

名字雖然叫loadBalance,但是這里并沒有做負載均衡,第一行代碼 Client client = getOptional(context, Client.class);,是從IoC容器中獲取Client的Bean,這時觸發了IoC容器對該Bean的實例化,代碼如下:

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    // client = org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
    Client client = getOptional(context, Client.class);
    if (client != null) {
        // 這里構造的client
        builder.client(client);
        // targeter = org.springframework.cloud.openfeign.HystrixTargeter
        // 即使在項目中沒有引入hystrix 也是這個類
        Targeter targeter = get(context, Targeter.class);
        // 這里面做動態代理的構建 與 模板方法的解析
        return targeter.target(this, builder, context, target);
    }

    throw new IllegalStateException(
        "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

HystrixTargeter#target

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
                    FeignContext context, Target.HardCodedTarget<T> target) {
    // 沒有引入hystrix 時 feign = Feign$Builder 所以就走到了這里
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        // 這里等于 build().newInstance(target),build構建了`ReflectiveFeign`
        // 然后這個newInstance是feign.ReflectiveFeign#newInstance
        return feign.target(target);
    }
    feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
        : factory.getContextId();
    SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
    if (setterFactory != null) {
        builder.setterFactory(setterFactory);
    }
    Class<?> fallback = factory.getFallback();
    if (fallback != void.class) {
        return targetWithFallback(name, context, target, builder, fallback);
    }
    Class<?> fallbackFactory = factory.getFallbackFactory();
    if (fallbackFactory != void.class) {
        return targetWithFallbackFactory(name, context, target, builder,
                                         fallbackFactory);
    }

    return feign.target(target);
}

這里的代碼跳轉是這樣的,return feign.target(target); 點進去是feign.Feign.Builder#target(feign.Target<T>),而方法的內容只有return build().newInstance(target);,而build方法 除了實例化了幾個類,同時將Builder的一些參數做了傳遞,

這里做了一件事就是把client傳過去了。

最后返回了ReflectiveFeign,所以直接看feign.ReflectiveFeign#newInstance

ReflectiveFeign#newInstance

這里就是通過JDK的動態代理 代理所有的接口了

public <T> T newInstance(Target<T> target) {

    // value 是 feign.SynchronousMethodHandler 這一步是模板解析
    // 這一步初始化了 SynchronousMethodHandler里面的client
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            continue;
        } else if (Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    // create方法等于new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

OpenFeign遠程調用

從初始化的流程來看 feign.MethodHandlerfeign.SynchronousMethodHandler,而java.lang.reflect.InvocationHandlerfeign.ReflectiveFeign.FeignInvocationHandler,然后在調用方法時 首先要進入的就是feign.ReflectiveFeign.FeignInvocationHandler#invoke

ReflectiveFeign.FeignInvocationHandler#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 這里時過濾掉Object相關的方法
    if ("equals".equals(method.getName())) {
        try {
            Object otherHandler =
                args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return equals(otherHandler);
        } catch (IllegalArgumentException e) {
            return false;
        }
    } else if ("hashCode".equals(method.getName())) {
        return hashCode();
    } else if ("toString".equals(method.getName())) {
        return toString();
    }
    // dispatch是Map<Method, MethodHandler> (value是SynchronousMethodHandler)
    return dispatch.get(method).invoke(args);
}

SynchronousMethodHandler#executeAndDecode

調用鏈為:

feign.SynchronousMethodHandler#invokefeign.SynchronousMethodHandler#executeAndDecode。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
        // 重點在這里 client=org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
        // 在不做特殊配置的情況下,LoadBalancerFeignClient.delegate=feign.Client$Default
        // 用的還是HttpURLConnection。
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 12
        response = response.toBuilder()
            .request(request)
            .requestTemplate(template)
            .build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
        return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
                                        metadata.returnType(),
                                        elapsedTime);

    try {
        if (!resultFuture.isDone())
            throw new IllegalStateException("Response handling not done");

        return resultFuture.join();
    } catch (CompletionException e) {
        Throwable cause = e.getCause();
        if (cause != null)
            throw cause;
        throw e;
    }
}

LoadBalancerFeignClient#execute

到這里能看到一些負載均衡的字眼了 但是追進去會看到好多RxJava的代碼,這種反應式代碼 只能靠猜。

@Override
public Response execute(Request request, Request.Options options) throws IOException {
    try {
        URI asUri = URI.create(request.url());
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);

        IClientConfig requestConfig = getClientConfig(options, clientName);
        // FeignLoadBalancer.executeWithLoadBalancer 其實是父類AbstractLoadBalancerAwareClient
        return lbClient(clientName)
            .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
            throw io;
        }
        throw new RuntimeException(e);
    }
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(new ServerOperation<T>() {
            @Override
            public Observable<T> call(Server server) {
                URI finalUri = reconstructURIWithServer(server, request.getUri());
                S requestForServer = (S) request.replaceUri(finalUri);
                try {
                    // 最后回調到這里
                    // org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
                    return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                } 
                catch (Exception e) {
                    return Observable.error(e);
                }
            }
        }).toBlocking()
        .single(); // 執行之后可以走到com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
    } catch (Exception e) {
        Throwable t = e.getCause();
        if (t instanceof ClientException) {
            throw (ClientException) t;
        } else {
            throw new ClientException(e);
        }
    }

}

總結

這里針對開篇提出的問題做一個總結

  1. OpenFeign的注解是如何被掃描解析的呢?
  2. 注解標注的接口注入到IoC中的實例是什么?
  3. OpenFeign的上下文是如何構建的呢?
  4. 遠程通信是如何實現的呢?與RestTemplate有關系么?

OpenFeign的注解是如何被掃描解析的呢?

通過實現接口ImportBeanDefinitionRegistrar然后注入實例到IoC容器的。

注解標注的接口注入到IoC中的實例是什么?

就是一個JDK的動態代理java.lang.reflect.Proxy,其中java.lang.reflect.Proxy#h的值是ReflectiveFeign$FeignInvocationHandler,如下圖所示:

OpenFeign的上下文是如何構建的呢?

public class FeignContext extends NamedContextFactory<FeignClientSpecification>

遠程通信是如何實現的呢?與RestTemplate有關系么?

其實就是這個類org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient,這里用到了委派模式,delegate再默認情況下是feign.Client$Default,里面的通信方式是java.net.HttpURLConnection,可以切換到HttpClient或者OkHttp3

RestTemplate默認也是基于java.net.HttpURLConnection封裝的一個工具類,Feign中沒有直接使用RestTemplate,他們應該屬于平級關系,不存在誰使用了誰。

疑問點

如何整合的Ribbon?

LoadBalancerFeignClient

和單獨看Ribbon的源碼還有聯系么?

有關系 但是不太一樣了,首先說明一點,Feign 一定要引入spring-cloud-starter-netflix-ribbon,否則在org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance就會報錯。因此,其實分析的Ribbon的源碼過程依舊會走一遍,且Feign比Ribbon 早配置。

為啥說不太一樣了呢?因為用了Feign就不需要@LoadBalanceRestTemplate了,所以請求的時候不會再被LoadBalancerInterceptor攔截。后面負載均衡那里還會走到Ribbon的com.netflix.loadbalancer.BaseLoadBalancer#chooseServer

如何切換請求方式

org.springframework.cloud.openfeign.ribbon.OkHttpFeignLoadBalancedConfiguration

org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration

org.springframework.cloud.openfeign.loadbalancer.DefaultFeignLoadBalancerConfiguration

Spring的知識點

ImportBeanDefinitionRegistrar

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                        BeanDefinitionRegistry registry) {

        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(Hello.class.getName());

        registry.registerBeanDefinition("hello", beanDefinition);
    }

}

class Hello {
    // 省略 getter/setter/toString()
    private String name;
}
// 非Spring Boot的環境下,此注解必須有  不然IoC容器中無Hello
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
        System.out.println(context.getBean(Hello.class));
    }
}

關于FactoryBean

本案例參考FeignClientFactoryBean,將一個接口做出一個代理對象 同時注入到Spring IoC容器中去。

public class TestFactoryBean implements FactoryBean<IHello> {

    @Override
    public IHello getObject() {

        return (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class<?>[]{IHello.class}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName());
                return "hello FactoryBean";
            }
        });
    }

    @Override
    public Class<?> getObjectType() {

        return IHello.class;
    }

}

interface IHello {

    String sayS();

    Integer sayI();
}
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        TestFactoryBean factoryBean = new TestFactoryBean();

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(IHello.class, () -> factoryBean.getObject()).getBeanDefinition();

        registry.registerBeanDefinition("hello", beanDefinition);
    }

}
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);

        IHello bean = context.getBean(IHello.class);
        System.out.println(bean);
        System.out.println(bean.sayS());
        // 這里會報錯 因為方法的返回值是int 但是Proxy里面粗暴返回的String
        System.out.println(bean.sayI());
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容