详解SpringBoot如何自定义Starter

目录
  • 阅读收获
  • 本章源码下载
  • 什么是Starter
  • 为什么使用Starter
  • Springboot自动配置
  • spring.factories
  • Starter开发常用注解
  • Full全模式和Lite轻量级模式
  • Starter命名规范
  • 开发Starter
    • 1. 创建Starter项目
    • 2. 添加依赖
    • 3. 编写属性类
    • 4. 自定义业务类
    • 5. 编写自动配置类
    • 6. 编写spring.factories
    • 7. 编写配置提示文件(非必须)
  • 测试Starter
    • 1. 前置环境
    • 2. 添加依赖
    • 3. 测试类
    • 4. 修改配置文件
    • 5. 运行程序打印
    • 6. 查看自动配置类生效的方法

阅读收获

学会自定义Spring-Boot-Starter

理解SpringBoot自动配置原理

本章源码下载

开发starter

测试starter

什么是Starter

Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的Bean根据环境( 条件)进行自动配置。

使用者只需要依赖相应功能的Starter,无需做过多的配置和依赖,Spring Boot就能自动扫描并加载相应的模块并设置默认值,做到开箱即用

为什么使用Starter

在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。

如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,并在starter中设置好默认值,复用的时候只需要将其在pom中引用依赖即可,Spring Boot为我们完成自动装配,做到开箱即用。

Springboot自动配置

SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,Spring Boot就能自动扫描各个jar包下classpath路径的spring.factories文件,加载自动配置类信息,加载相应的bean信息并启动相应的默认配置。

Spring Boot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。

大家可以看看我之前写的一篇文章,详细介绍了springboot自动配置的流程:一文搞懂SpringBoot自动配置原理

spring.factories

Spring Boot会默认扫描跟启动类平级的包,如果我们的Starter跟启动类不在同一个主包下,需要通过配置spring.factories文件来配置生效,SpringBoot默认加载各个jar包下classpath路径的spring.factories文件,配置的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration

Starter开发常用注解

注解使用已经大大方便我们开发,再也不需要写xml配置文件了,SpringBoot经过查找spring.factories文件,加载自动配置类,而自动配置类中定义了各种运行时判断条件,如@ConditionalOnMissingBean(A.class)等,只要ioc容器中没有指定的A类型的bean信息,该配置文件才会生效。

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

1.属性映射注解

  • @ConfigurationProperties :配置文件属性值和实体类的映射
  • @EnableConfigurationProperties:和@ConfigurationProperties配合使用,把@ConfigurationProperties修饰的类加入ioc容器。

2.配置bean注解

  • @Configuration :标识该类为配置类,并把该类注入ioc容器
  • @Bean :一般在方法上使用,声明一个bean,bean名称默认是方法名称,类型为返回值。

3.条件注解

@Conditional:是根据条件类创建特定的Bean,条件类需要实现Condition接口,并重写matches接口来构造判断条件。

@ConditionalOnBean :容器中存在指定bean,才会实例化一个Bean

@ConditionalOnMissingBean:容器中不存在指定bean,才会实例化一个Bean

@ConditionalOnClass:系统中有指定类,才会实例化一个Bean

@ConditionalOnMissingClass:系统中没有指定类,才会实例化一个Bean

@ConditionalOnExpression:当SpEl表达式为true的时候,才会实例化一个Bean

@AutoConfigureAfter :在某个bean完成自动配置后实例化这个bean

@AutoConfigureBefore :在某个bean完成自动配置前实例化这个bean

@ConditionalOnJava :系统中版本是否符合要求

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化

@ConditionalOnResource:类路径下是否存在指定资源文件

@ConditionalOnWebApplication:是web应用

@ConditionalOnNotWebApplication:不是web应用

@ConditionalOnJndi:JNDI指定存在项

@ConditionalOnProperty: 配置Configuration的加载规则

  • prefix :配置属性名称的前缀
  • value :数组,获取对应property名称的值,与name不可同时使用
  • name :数组,可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
  • havingValue  :比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
  • matchIfMissing :缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效

Full全模式和Lite轻量级模式

@Configuration参数proxyBeanMethods:

Full 全模式(默认):@Configuration(proxyBeanMethods = true)

同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法会被代理,从ioc容器中取bean实列,所以实列是一样的。即单实例对象,在该模式下SpringBoot每次启动都会判断检查容器中是否存在该组件

Lite 轻量级模式:@Configuration(proxyBeanMethods = false)

同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法不会被代理,相当于直接调用一个普通方法,会有构造方法,但是没有bean的生命周期,返回的是不同的实例。

注:proxyBeanMethods 是为了让使用@Bean注解的方法被代理。而不是@Bean的单例多例的设置参数。

测试例子这里不展示,可以下载我的代码查看

@Configuration(proxyBeanMethods = false)
public class AppConfig {

    //放一份myBean到ioc容器
    @Bean
    public Mybean myBean() {
        return new Mybean();
    }
    //放一份yourBean到ioc容器
    @Bean
    public YourBean yourBean() {
        System.out.println("==========");
        //注意:@Configuration(proxyBeanMethods = false):myBean()方法不代理,直接调用
        //注意:@Configuration(proxyBeanMethods = true):myBean()方法代理,从ioc容器拿
        return new YourBean(myBean());
    }
}

什么时候用Full全模式,什么时候用Lite轻量级模式?

  • 当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系时,建议使用Full全模式
  • 当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高springboot的启动速度和性能

Starter命名规范

Spring官方Starter通常命名为spring-boot-starter-{name}如:spring-boot-starter-web

Spring官方建议非官方Starter命名应遵循{name}-spring-boot-starter的格式:如mybatis-spring-boot-starter。

开发Starter

1. 创建Starter项目

新建项目后,要删除main启动类

2. 添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ljw</groupId>
    <artifactId>ljw-spring-boot-starter</artifactId>
    <version>1.0</version>

   <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!--        包含自动配置的代码-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <!--        配置文件点击可以跳转实体-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

</project>

我们没有main入口,需要去除pom文件中maven打包插件spring-boot-maven-plugin

spring-boot-configuration-processor作用:

  • spring-boot-configuration-processor其实是一个注解处理器,在编译阶段干活的,一般在maven的声明都是optional 为true
  • 你在idea里面可以点击port,进到这个字段里面,还可以看到配置的提示信息
  • 这是因为在你的资源文件里面有一个spring-configuration-metadata.json文件,这是spring配置的元数据,是json形式

3. 编写属性类

@ConfigurationProperties可以定义一个配置信息类,和配置文件进行映射

@ConfigurationProperties(prefix = "ljw.config")
public class HelloProperties {

    private String name = "hello 默认值!";

    private int age = 8;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4. 自定义业务类

这里可以模拟一些获取了配置文件信息的进行业务操作的业务类

public class HelloService {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String hello() {
        return "HelloService{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

5. 编写自动配置类

命名规范:XxxAutoConfiguration

@Configuration(proxyBeanMethods = false)
// 当存在某个类时,此自动配置类才会生效
@ConditionalOnClass(value = {HelloService.class})
// 导入我们自定义的配置类,供当前类使用
@EnableConfigurationProperties(value = HelloProperties.class)
// 只有非web应用程序时此自动配置类才会生效
@ConditionalOnWebApplication
//判断ljw.config.flag的值是否为“true”, matchIfMissing = true:没有该配置属性时也会正常加载
@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
public class HelloAutoConfiguration {

    /**
     * @param helloProperties 直接方法签名入参注入HelloProperties,也可以使用属性注入
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(HelloService.class)
    //@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
    public HelloService helloService(HelloProperties helloProperties) {
        HelloService helloService = new HelloService();
        //把获取的信息注入
        helloService.setName(helloProperties.getName());
        helloService.setAge(helloProperties.getAge());
        return helloService;
    }

}

注:这里配置一个web应用才能注入,并且ljw.config.flag的值是否为“true”或者不配置该key才能注入HelloService服务

6. 编写spring.factories

把自动配置类HelloAutoConfiguration配置到org.springframework.boot.autoconfigure.EnableAutoConfiguration的key下,springboot会自动加载该文件并根据条件装配

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ljw.starter.config.HelloAutoConfiguration

7. 编写配置提示文件(非必须)

additional-spring-configuration-metadata.json

配置additional-spring-configuration-metadata.json文件后,在开发人员的IDE工具使用个人编写的配置读取很有效的在application.properties或application.yml文件下完成提示。

配置详细格式参数可查看文档

我的配置:

{"properties": [
    {
      "name": "ljw.config.name",
      "type": "java.lang.String",
      "defaultValue": "hello 默认值!这里配置的是提示,真正默认值在Properties里面",
      "description": "这是字符串名称啊."
    },
    {
      "name": "ljw.config.age",
      "defaultValue": 8,
      "description": "这是int类型的年龄啊.",
      "deprecation": {
              "reason": "过时原因.",
              "replacement": "替代key是:ljw.config.age22",
              "level": "warning"
            }
    }
]}

大家参考下面properties表格进行配置上的理解。

deprecation每个properties元素的属性中包含的JSON对象可以包含以下属性:

spring-configuration-metadata.json

spring-configuration-metadata.json代码量挺大的,为了方便我们可以通过IDE来生成,这里使用的是idea。

在idea设置中搜索Annotation Processors,接下来勾住Enable annonation processing就完成了。在编译打包后的文件中看到自动生成的spring-configuration-metadata.json。这个文件不用我们编写

下面是自动生成的:

{
  "groups": [
    {
      "name": "ljw.config",
      "type": "com.ljw.starter.properties.HelloProperties",
      "sourceType": "com.ljw.starter.properties.HelloProperties"
    }
  ],
  "properties": [
    {
      "name": "ljw.config.name",
      "type": "java.lang.String",
      "description": "这是字符串名称啊.",
      "sourceType": "com.ljw.starter.properties.HelloProperties",
      "defaultValue": "hello 默认值!这里配置的是提示,真正默认值在Properties里面"
    },
    {
      "name": "ljw.config.age",
      "type": "java.lang.Integer",
      "description": "这是int类型的年龄啊.",
      "sourceType": "com.ljw.starter.properties.HelloProperties",
      "defaultValue": 8,
      "deprecated": true,
      "deprecation": {
        "level": "warning",
        "reason": "过时原因.",
        "replacement": "替代key是:ljw.config.age22"
      }
    }
  ],
  "hints": []
}

测试Starter

1. 前置环境

install打包自定义starter项目:ljw-spring-boot-starter

新建项目:ljw-test-spring-boot-starter

2. 添加依赖

引入打好包的自定义starter

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!--        测试web应用-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--自定义satrter-->
    <dependency>
        <groupId>com.ljw</groupId>
        <artifactId>ljw-spring-boot-starter</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

3. 测试类

@Service
public class TestController implements CommandLineRunner {

    /**
     * 注入自定义starter服务
     */
    @Resource
    private HelloService helloService;

    @Override
    public void run(String... args) throws Exception {
        System.out.println(helloService.hello());
    }
}

4. 修改配置文件

输入前缀可以看出已经有提示了

ljw.config.name=ljw hello!
ljw.config.age=99
ljw.config.flag=true
#不会注入
#ljw.config.flag=true1
# 可以看到哪些自动配置了
debug=true

5. 运行程序打印

HelloService{name='ljw hello!', age=99}

条件注入

  • 如果没有spring-boot-starter-web依赖,不能注入服务HelloService
  • 如果配置了ljw.config.flag,值不是true,不能注入服务HelloService;如果不配置ljw.config.flag,可以注入

6. 查看自动配置类生效的方法

通过启用 debug=true 属性,让控制台打印自动配置报告,这样就可以很方便地知道哪些自动配置类生效。

   HelloAutoConfiguration matched:
      - @ConditionalOnClass found required class 'com.ljw.starter.service.HelloService' (OnClassCondition)
      - @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition)
      - @ConditionalOnProperty (ljw.config.flag=true) matched (OnPropertyCondition)

   HelloAutoConfiguration#helloService matched:
      - @ConditionalOnMissingBean (types: com.ljw.starter.service.HelloService; SearchStrategy: all) did not find any beans (OnBeanCondition)

以上就是详解SpringBoot如何自定义Starter的详细内容,更多关于SpringBoot自定义Starter的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot自动配置之自定义starter的实现代码

    前言:前面已经介绍了自动配置的很多原理,现在我们着手自己定义一个starter. 需求:自定义redis-starter,要求当导入redis坐标后,SpringBoot自动创建Jedis的Bean.正式开始之前,我们可以查看Mybatis的起步依赖是如果实现自动配置的.我这里就省略了,大家根据之前的分析文章,自己看源码即可. 一.先创建一个SpringBoot工程redis-spring-boot-autoconfigure,该工程中添加jedis依赖,并且创建一个自动配置类RedisAuto

  • 浅谈SpringBoot如何自定义Starters

    一.Starters原理 1.1 Starters场景启动器 1.场景需要用到的依赖是什么? 比如依赖的jar 2.如何编写自动配置? 以WebMvcAutoConfiguration自动配置为例: @Configuration @ConditionalOnWebApplication @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class }) @Conditiona

  • 使用SpringBoot自定义starter详解

    一.新建一个工程 工程由xxx-sprig-boot-starter和xxx-sprig-boot-starter-configure两个模块组成: xxx-sprig-boot-starter模块 只用来做依赖导入 依赖于 xxx-sprig-boot-starter-configure模块,没有实际代码 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave

  • springboot自定义starter启动器的具体使用实践

    目录 第一步.创建 xxx-spring-boot-starter 的spring Initializr模块 第二步.删除不需要的内容(启动类.除下面spring-boot-starter的其它依赖,maven编译插件) 第三步.写代码,对外提供一些自己写的类 第四步.在resources资源文件夹下创建一个META-INF文件夹,并创建一个spring.factories文件 第五步.将该项目发布的maven仓库,或者安装到本地仓库中让其它项目能使用的到 第六步.测试自己定义的启动器使用有效

  • 使用SpringBoot自定义starter的完整步骤

    前言 使用过SpringBoot的都应该知道,一个SpringBoot 项目就是由一个一个 Starter 组成的,一个 Starter 代表该项目的 SpringBoot 启动依赖,除了官方已有的 Starter,我们可以根据自己的需要自定义新的Starter. 一.自定义SpringBoot Starter 自定义Starter,首选需要实现自动化配置,而要实现自动化配置需要满足以下两个条件: (1)能够自动配置项目所需要的配置信息,也就是自动加载依赖环境: (2)能够根据项目提供的信息自动

  • 详解SpringBoot如何自定义Starter

    目录 阅读收获 本章源码下载 什么是Starter 为什么使用Starter Springboot自动配置 spring.factories Starter开发常用注解 Full全模式和Lite轻量级模式 Starter命名规范 开发Starter 1. 创建Starter项目 2. 添加依赖 3. 编写属性类 4. 自定义业务类 5. 编写自动配置类 6. 编写spring.factories 7. 编写配置提示文件(非必须) 测试Starter 1. 前置环境 2. 添加依赖 3. 测试类

  • 详解SpringBoot中自定义和配置拦截器的方法

    目录 1.SpringBoot版本 2.什么是拦截器 3.工作原理 4.拦截器的工作流程 4.1正常流程 4.2中断流程 5.应用场景 6.如何自定义一个拦截器 7.如何使其在Spring Boot中生效 8.实际使用 8.1场景模拟 8.2思路 8.3实现过程 8.4效果体验 9.总结 1.SpringBoot版本 本文基于的Spring Boot的版本是2.6.7 . 2.什么是拦截器 Spring MVC中的拦截器(Interceptor)类似于ServLet中的过滤器(Filter),它

  • 详解SpringBoot简化配置分析总结

    在SpringBoot启动类中,该主类被@SpringBootApplication所修饰,跟踪该注解类,除元注解外,该注解类被如下自定注解修饰. @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 让我们简单叙述下它们各自的功能: @ComponentScan:扫描需要被IoC容器管理下需要管理的Bean,默认当前根目录下的 @EnableAutoConfiguration:装载所有第三方的Bean @SpringB

  • 详解springboot整合ehcache实现缓存机制

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. ehcache提供了多种缓存策略,主要分为内存和磁盘两级,所以无需担心容量问题. spring-boot是一个快速的集成框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 由于spring-boot无需任何样板化的配置文件,所以spring-boot集成一些其他框架时会有略微的

  • 详解springboot+aop+Lua分布式限流的最佳实践

    一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,

  • 详解springboot整合ueditor踩过的坑

    有一天老板突然找我让我改富文本(一脸懵逼,不过也不能推啊默默地接下了),大家都知道现在的富文本视频功能都是只有上传链接的没有从本地上传这一说(就连现在的csdn的也是)于是我找了好多个,最终发现百度的ueditor可以. 经过几天的日夜,甚至牺牲了周末休息时间开始翻阅资料... 废话不多说,开始教程: 第一步: 去ue官网下载他的源码 第二步: 解压下载的源码(下载可能会慢,好像需要翻墙下载) 然后打开项目把源码拖进项目的resources/static中去 第三步 就是重点了 由于spring

  • 详解springboot+mybatis-plue实现内置的CRUD使用详情

    mybatis-plus的特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD操作:内置通用 Mapper.通用Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 支持 Lambda形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错 支持主键自动生成:支持多达 4种主键策略(内含分布式唯一 ID 生成器

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • 详解SpringBoot中的tomcat优化和修改

    项目背景 在做项目的时候,把SpringBoot的项目打包成安装包了,在客户上面安装运行,一切都是那么的完美,可是发生了意外,对方突然说导出导入的文件都不行了.我急急忙忙的查看日志,发现报了一个错误 java.io.IOException: The temporary upload location [C:\Windows\Temp\tomcat.1351070438015228346.8884\work\Tomcat\localhost\ROOT] is not valid at org.ap

  • 详解SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)

    引言 ​前两天在写一个实时数据处理的项目,项目要求是 1s 要处理掉 1k 的数据,这时候显然光靠查数据库是不行的,技术选型的时候老大跟我提了一下使用 Layering-Cache 这个开源项目来做缓存框架. ​之间问了一下身边的小伙伴,似乎对这块了解不多.一般也就用用 Redis 来缓存,应该是很少用多级缓存框架来专门性的管理缓存吧. ​趁着这个机会,我多了解了一些关于 SpringBoot 中缓存的相关技术,于是有了这篇文章! 在项目性能需求比较高时,就不能单单依赖数据库访问来获取数据了,必

随机推荐