Graalvm aot编译springboot3.x webflux+r2dbc版

本文完整代码链接 https://github.com/coralloc8/springboot3-native.git

Graalvm基础自行去官网了解 https://www.graalvm.org/latest/docs/getting-started/

此文章包含如下功能节点,在下述功能节点上做AOT编译(基本能满足日常开发):

  • Opendoc:API接口文档,替代swagger
  • Webflux:响应式编程
  • ReactiveTransactionManager:异步事务、事务切面
  • R2dbc、proxy:异步jdbc链接,SQL日志自定义输出
  • Mapstruct:替代反射的方式做数据对象转换。dto转bo、vo转dto之类
  • Graalvm Feature:自定义Feature,运行时注册相关的类
  • Jackson:数据序列化/反序列化时类型转换,枚举转换之类
  • Aop:实现全局的日志打印
  • ErrorWebExceptionHandler:webflux 全局异常处理 可支持函数式编程方式
  • WebFilter:自定义各种filter,MDC,reactor数据透传请求上下文
  • Redis:异步redis读取 (2024-06-05 新增)

本文只对如何将springboot3.x项目进行aot编译说明,Graalvm版本 jdk17。


image.png

项目对springboot的版本依赖如下:

     <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.2.4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2023.0.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2023.0.0.0-RC1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

如果项目的父类不是spring-boot-starter-parent的话

    <parent>
        <groupId>org.spring.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
    </parent>

那么在项目中则需要添加如下profiles (spring-boot-starter-parent项目中默认已经配置下面的profiles)

    <!-- 自定义profile -->
    <profiles>
        <profile>
            <id>native</id>
            <build>
                <pluginManagement>
                    <plugins>
                        <plugin>
                            <groupId>org.graalvm.buildtools</groupId>
                            <artifactId>native-maven-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>add-reachability-metadata</id>
                                    <goals>
                                        <goal>add-reachability-metadata</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                        <plugin>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-maven-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>process-aot</id>
                                    <goals>
                                        <goal>process-aot</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                    </plugins>
                </pluginManagement>
            </build>
        </profile>
        <profile>
            <id>nativeTest</id>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-launcher</artifactId>
                    <scope>test</scope>
                </dependency>
            </dependencies>
            <build>
                <pluginManagement>
                    <plugins>
                        <plugin>
                            <groupId>org.graalvm.buildtools</groupId>
                            <artifactId>native-maven-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>native-test</id>
                                    <goals>
                                        <goal>test</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                        <plugin>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-maven-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>process-test-aot</id>
                                    <goals>
                                        <goal>process-test-aot</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                    </plugins>
                </pluginManagement>
            </build>
        </profile>
    </profiles>

Graalvm aot 编译目前对于springcloud有很多的限制


img_6.png

如官方所说,采用bootstrap.yml的方式走不通。因此得采用spring.config.import的方式来引入naco中的配置文件。

# 远端配置
spring:
  config:
    import:
      - optional:nacos:${spring.cloud.nacos.config.prefix}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}?preference=remote

preference的方式来自nacos官方 https://github.com/alibaba/spring-cloud-alibaba/pull/2459
需要在nacos配置文件中配置 spring.cloud.nacos.config.preference=remote
配置成preference=remote的话此时配置文件的优先级会变成 nacos>application-{active}.yml>application.yml

nacos的配置文件 (命名空间ID:spring-native 配置文件名:confi-dev.yaml)


image.png
server:
    port: 8090

coral:
    datasource:
        url: 192.168.29.66:3306

coral.native:
    username: xiaoxiao23
    sex: male男a

Graalvm编译不支持 lambda表达式。因此需要自行实现graalvm的feature来支持。

public class RuntimeRegistrationFeature implements Feature {


    private static final String PACKAGE_NAME = "com.coral.test.spring.natives";

    @Override
    public void duringSetup(DuringSetupAccess access) {
        //      扫描指定包下IService IRepository的字类(实现类),然后全部注册到graalvm Lambda 序列化中
        RuntimeRegistrationFeature.findClasses(PACKAGE_NAME, Set.of(IService.class, IRepository.class))
                .forEach(RuntimeSerialization::registerLambdaCapturingClass);
        RuntimeSerialization.register(SerializedLambda.class, IGetter.class);
    }

    /**
     * 找到某个包下面指定的父类的所有子类
     *
     * @param packageName  包名
     * @param superClasses 父类
     * @return 子类集合
     */
    private static Set<Class<?>> findClasses(String packageName, Set<Class<?>> superClasses) {
        Set<Class<?>> classes = new HashSet<>();
        for (Class<?> superClass : superClasses) {
            classes.addAll(ClassUtil.scanPackageBySuper(packageName, superClass));
        }
        return classes;
    }

}

Graalvm不支持java中的反射操作,需要自行通过 native-image-agent来对java jar生成 native-image映射文件。
常规操作是先将 普通的springboot项目打包成 jar可执行文件,然后再用以下命令来生成 native-image 文件

java  -agentlib:native-image-agent=config-output-dir=./config -jar target/spring-native-test-1.0.0-SNAPSHOT.jar

config-output-dir 为生成的文件所保存的目录。
执行完上述命令后,jar文件应该正常运行起来了,此时访问 http://localhost:8090/doc.html 进入到项目api文档。最保险的做法是将文档中的所有api接口调用一遍,包括进入到首页时的一些静态文件,强制刷新首页。这样才能让native探针充分捕捉到项目所需要的native-image文件。操作完成后,jar进程就可以杀掉了。

此时查看 config目录,应该会发现生成了一系列文件。


image.png

这些文件中需要说明的是jni文件中需要增加如下配置,不然编译过程中会报dns空值异常。

{
  "name":"sun.net.dns.ResolverConfigurationImpl",
  "fields":[{"name":"os_nameservers"}, {"name":"os_searchlist"}]
}

本文生成native-image映射文件采用的另外一种方式。
通过spring-boot-maven-plugin插件来配置生成映射文件。如此只需要执行
spring-boot:start spring-boot:stop即可。
start启动项目后,访问项目doc.html接口文档,仍然需要将文档中的所有api接口调用一遍,包括进入到首页时的一些静态文件,强制刷新首页。让native探针充分捕捉到项目所需要的native-image文件。
stop最终杀掉项目。

   <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <jvmArguments>
                        -Dfile.encoding=UTF-8
                        --add-opens java.base/java.lang=ALL-UNNAMED
                        --add-opens java.base/java.io=ALL-UNNAMED
                        --add-opens java.base/java.math=ALL-UNNAMED
                        --add-opens java.base/java.net=ALL-UNNAMED
                        --add-opens java.base/java.nio=ALL-UNNAMED
                        --add-opens java.base/java.security=ALL-UNNAMED
                        --add-opens java.base/java.text=ALL-UNNAMED
                        --add-opens java.base/java.time=ALL-UNNAMED
                        --add-opens java.base/java.util=ALL-UNNAMED
                        --add-opens java.base/jdk.internal.access=ALL-UNNAMED
                        --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
                        -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/
                    </jvmArguments>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

上述两种方式生成的映射文件最终都需要放在项目 resources目录下。在resources目录下新建 META-INF/native-image 目录,然后将上述生成的映射文件全部放进去。

image.png

springboot process-aot编译也会生成native-image映射文件。保存目录在META-INF/native-image/{groupId}/{artifactId}/下。因此在native-maven-plugin插件中新增如下编译配置。

<buildArg>
  -H:ReflectionConfigurationResources=META-INF/native-image/reflect-config.json,META-INF/native-image/com.coral.test/spring-native-test/reflect-config.json 
  -H:ResourceConfigurationResources=META-INF/native-image/resource-config.json,META-INF/native-image/com.coral.test/spring-native-test/resource-config.json
  -H:DynamicProxyConfigurationResources=META-INF/native-image/proxy-config.json,META-INF/native-image/com.coral.test/spring-native-test/proxy-config.json 
  -H:JNIConfigurationResources=META-INF/native-image/jni-config.json
</buildArg>

接下来正式开始AOT编译。


image.png

image.png

image.png

步骤执行完后会发现在target目录下生成了jar文件和exe文件

image.png

instrument.dll文件是因为引入了nacos。所以生成了该文件。

本文的项目中已经配置完整的编译。因此可以采用如下命令一键编译。

mvn -Pnative -Pdev native:compile-no-fork -f pom.xml

在编译过程中会报各种类文件编译时机不对的问题。有的类需要在build阶段,有的类需要在run阶段初始化。类似下面配置。哪个类有问题就增加哪个类。
如下述错误

Error: Classes that should be initialized at run time got initialized during image building:
 ch.qos.logback.core.util.StatusPrinter was unintentionally initialized at build time. To see why ch.qos.logback.core.util.StatusPrinter got initialized use --trace-class-initialization=ch.qos.logback.core.util.StatusPrinter
ch.qos.logback.classic.Logger was unintentionally initialized at build time. To see why ch.qos.logback.classic.Logger got initialized use --trace-class-initialization=ch.qos.logback.classic.Logger
ch.qos.logback.classic.Level was unintentionally initialized at build time. To see why ch.qos.logback.classic.Level got initialized use --trace-class-initialization=ch.qos.logback.classic.Level
ch.qos.logback.core.status.StatusBase was unintentionally initialized at build time. To see why ch.qos.logback.core.status.StatusBase got initialized use --trace-class-initialization=ch.qos.logback.core.status.StatusBase
ch.qos.logback.core.util.Loader was unintentionally initialized at build time. To see why ch.qos.logback.core.util.Loader got initialized use --trace-class-initialization=ch.qos.logback.core.util.Loader
ch.qos.logback.core.CoreConstants was unintentionally initialized at build time. To see why ch.qos.logback.core.CoreConstants got initialized use --trace-class-initialization=ch.qos.logback.core.CoreConstants
ch.qos.logback.core.status.InfoStatus was unintentionally initialized at build time. To see why ch.qos.logback.core.status.InfoStatus got initialized use --trace-class-initialization=ch.qos.logback.core.status.InfoStatus
org.slf4j.LoggerFactory was unintentionally initialized at build time. To see why org.slf4j.LoggerFactory got initialized use --trace-class-initialization=org.slf4j.LoggerFactory
To see how the classes got initialized, use --trace-class-initialization=ch.qos.logback.core.util.StatusPrinter,ch.qos.logback.classic.Logger,ch.qos.logback.classic.Level,ch.qos.logback.core.status.StatusBase,ch.qos.logback.core.util.Loader,ch.qos.logback.core.CoreConstants,ch.qos.logback.core.status.InfoStatus,org.slf4j.LoggerFactory
  <buildArg>
                            <!-- logback -->
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.DefaultProcessor$1
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.ChainedModelFilter$1
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.ImplicitModelHandler$1
                            --initialize-at-build-time=ch.qos.logback.core.CoreConstants
                            --initialize-at-build-time=ch.qos.logback.core.subst.Token
                            --initialize-at-build-time=ch.qos.logback.core.subst.Parser$1
                            --initialize-at-build-time=ch.qos.logback.core.subst.NodeToStringTransformer$1
                            --initialize-at-build-time=ch.qos.logback.core.util.Duration
                            --initialize-at-build-time=ch.qos.logback.core.util.Loader
                            --initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter
                            --initialize-at-build-time=ch.qos.logback.core.status.StatusBase
                            --initialize-at-build-time=ch.qos.logback.core.status.InfoStatus
                            --initialize-at-build-time=ch.qos.logback.core.status.WarnStatus
                            --initialize-at-build-time=ch.qos.logback.classic.Level
                            --initialize-at-build-time=ch.qos.logback.classic.Logger
                            --initialize-at-build-time=ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules
                            <!-- slf4j -->
                            --initialize-at-build-time=org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.slf4j.MDC,org.slf4j.impl.StaticLoggerBinder
                            <!-- netty -->
                            --initialize-at-run-time=io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler,io.netty.handler.codec.http2.Http2ServerUpgradeCodec
                            <!-- -->
                            --initialize-at-run-time=sun.net.dns.ResolverConfigurationImpl
                            --initialize-at-build-time=cn.hutool.core.util.ClassLoaderUtil
                            --initialize-at-build-time=cn.hutool.core.convert.BasicType
                            --initialize-at-build-time=cn.hutool.core.util.CharsetUtil
                            <!-- features -->
                            --features=com.coral.test.spring.natives.core.feature.RuntimeRegistrationFeature
                        </buildArg>

最终生成的完整编译文件为

          <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <configuration>
                    <!-- imageName用于设置生成的二进制文件名称 -->
                    <imageName>${project.artifactId}</imageName>
                    <!-- mainClass用于指定main方法类路径 -->
                    <mainClass>com.coral.test.spring.natives.MainApplication</mainClass>
                    <!--                    <useArgFile>true</useArgFile>-->
                    <skip>false</skip>
                    <!-- 如果要启用调试信息的生成,请在插件配置中提供以下内容-->
                    <debug>false</debug>
                    <!--启用详细输出-->
                    <verbose>false</verbose>
                    <!--
                     -H:ConfigurationFileDirectories=:指定配置文件的直接目录,多个项目之间用逗号分隔。在该目录中按默认方式的命名的 json 配置文件都可以被自动识别。
                     -H:ConfigurationResourceRoots=:指定配置资源的根路径,多个项目之间用逗号分隔。配置文件不仅可以被当作外部文件读取,也可以被当作 resource 资源读取。这种方式适用于读取存放在 jar 文件中的配置文件。
                     -H:XXXConfigurationFiles=:指定某一种类型的配置文件,多个项目之间用逗号分隔。这里的 XXX 可以是 Reflection、DynamicProxy、Serialization、SerializationDeny、Resource、JNI 或 PredefinedClasses。
                     -H:XXXConfigurationResources=:指定某一种类型的配置资源的路径,多个项目之间用逗号分隔。这里的 XXX 可以是 Reflection、DynamicProxy、Serialization、SerializationDeny、Resource、JNI 或 PredefinedClasses。
                     -->
                    <buildArgs combine.children="append">
                        <buildArg>
                            --verbose
                            -Djavax.xml.accessExternalDTD=all
                            -Dfile.encoding=UTF-8
                            -H:+AddAllCharsets
                            -H:+ReportExceptionStackTraces
                            <!--                            -H:+PrintClassInitialization-->
                            --allow-incomplete-classpath
                            --report-unsupported-elements-at-runtime

                        </buildArg>
                        <!-- 反射 -->
                        <buildArg>
                            -H:ReflectionConfigurationResources=META-INF/native-image/reflect-config.json,META-INF/native-image/com.coral.test/spring-native-test/reflect-config.json
                            -H:ResourceConfigurationResources=META-INF/native-image/resource-config.json,META-INF/native-image/com.coral.test/spring-native-test/resource-config.json
                            -H:DynamicProxyConfigurationResources=META-INF/native-image/proxy-config.json,META-INF/native-image/com.coral.test/spring-native-test/proxy-config.json
                            -H:JNIConfigurationResources=META-INF/native-image/jni-config.json
                        </buildArg>
                        <buildArg>
                            <!-- logback -->
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.DefaultProcessor$1
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.ChainedModelFilter$1
                            --initialize-at-build-time=ch.qos.logback.core.model.processor.ImplicitModelHandler$1
                            --initialize-at-build-time=ch.qos.logback.core.CoreConstants
                            --initialize-at-build-time=ch.qos.logback.core.subst.Token
                            --initialize-at-build-time=ch.qos.logback.core.subst.Parser$1
                            --initialize-at-build-time=ch.qos.logback.core.subst.NodeToStringTransformer$1
                            --initialize-at-build-time=ch.qos.logback.core.util.Duration
                            --initialize-at-build-time=ch.qos.logback.core.util.Loader
                            --initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter
                            --initialize-at-build-time=ch.qos.logback.core.status.StatusBase
                            --initialize-at-build-time=ch.qos.logback.core.status.InfoStatus
                            --initialize-at-build-time=ch.qos.logback.core.status.WarnStatus
                            --initialize-at-build-time=ch.qos.logback.classic.Level
                            --initialize-at-build-time=ch.qos.logback.classic.Logger
                            --initialize-at-build-time=ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules
                            <!-- slf4j -->
                            --initialize-at-build-time=org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.slf4j.MDC,org.slf4j.impl.StaticLoggerBinder
                            <!-- netty -->
                            --initialize-at-run-time=io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler,io.netty.handler.codec.http2.Http2ServerUpgradeCodec
                            <!-- -->
                            --initialize-at-run-time=sun.net.dns.ResolverConfigurationImpl
                            --initialize-at-build-time=cn.hutool.core.util.ClassLoaderUtil
                            --initialize-at-build-time=cn.hutool.core.convert.BasicType
                            --initialize-at-build-time=cn.hutool.core.util.CharsetUtil
                            <!-- features -->
                            --features=com.coral.test.spring.natives.core.feature.RuntimeRegistrationFeature
                        </buildArg>
                        <buildArg>
                            --add-opens java.base/java.lang=ALL-UNNAMED
                            --add-opens java.base/java.io=ALL-UNNAMED
                            --add-opens java.base/java.math=ALL-UNNAMED
                            --add-opens java.base/java.net=ALL-UNNAMED
                            --add-opens java.base/java.nio=ALL-UNNAMED
                            --add-opens java.base/java.security=ALL-UNNAMED
                            --add-opens java.base/java.text=ALL-UNNAMED
                            --add-opens java.base/java.time=ALL-UNNAMED
                            --add-opens java.base/java.util=ALL-UNNAMED
                            --add-opens java.base/jdk.internal.access=ALL-UNNAMED
                            --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
                        </buildArg>
                    </buildArgs>
                </configuration>

            </plugin>


r2dbc的使用说明: https://docs.spring.io/spring-data/r2dbc/docs/current-SNAPSHOT/reference/html/#r2dbc.core


redis 编译相关

java.lang.UnsatisfiedLinkError: jdk.jfr.internal.JVM.getHandler(Ljava/lang/Class;)Ljava/lang/Object; [symbol: Java_jdk_jfr_internal_JVM_getHandler or Java_jdk_jfr_internal_JVM_getHandler__Ljava_lang_Class_2]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:152) ~[na:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:53) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.internal.JVM.getHandler(Native Method) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.internal.Utils.getHandler(Utils.java:449) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.internal.MetadataRepository.getHandler(MetadataRepository.java:174) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.internal.MetadataRepository.register(MetadataRepository.java:135) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.internal.MetadataRepository.register(MetadataRepository.java:130) ~[na:na]
        at jdk.jfr@17.0.10/jdk.jfr.FlightRecorder.register(FlightRecorder.java:136) ~[na:na]
        at io.lettuce.core.event.connection.JfrConnectionCreatedEvent.<clinit>(JfrConnectionCreatedEvent.java) ~[auth-service:6.3.1.RELEASE/12e6995]
        at java.base@17.0.10/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[auth-service:na]
        at java.base@17.0.10/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[auth-service:na]
        at io.lettuce.core.event.jfr.JfrEventRecorder.createEvent(JfrEventRecorder.java:108) ~[na:na]
 reactor.core.Exceptions 304 warn - throwIfFatal detected a jvm fatal exception, which is thrown and logged below:java.lang.NoClassDefFoundError: Could not initialize class io.lettuce.core.event.connection.JfrConnectionCreatedEvent
        at java.base@17.0.10/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
        *__checkpoint 鈬?Handler com.coral.test.spring.natives.controller.EventLogController#findEventLog(String) [DispatcherHandler]
Original Stack Trace:
                at java.base@17.0.10/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
                at java.base@17.0.10/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
                at io.lettuce.core.event.jfr.JfrEventRecorder.createEvent(JfrEventRecorder.java:108)

解决方案 (2024-06-05 官方说jfr在windows上暂不支持)
2024-06-12 经测试在centos7上确实不会报此错误

https://github.com/oracle/graal/issues/8623
https://www.graalvm.org/latest/reference-manual/native-image/guides/build-and-run-native-executable-with-jfr/
https://github.com/oracle/graal/issues/5558

另外一种方式是将jfr功能禁用掉(2024-06-17)
https://github.com/redis/lettuce/wiki/Connection-Events#java-flight-recorder-events-since-61

io.lettuce.core.event.jfr.EventRecorderHolder类中该变量的

private static final String JFR_ENABLED_KEY = "io.lettuce.core.jfr";

private static final boolean JFR_ENABLED = Boolean.parseBoolean(SystemPropertyUtil.get(JFR_ENABLED_KEY, "true"));

因此需要设置

System.setProperty("io.lettuce.core.jfr", "false"); 

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

推荐阅读更多精彩内容