• 20201108 小马哥讲Spring核心编程思想


    第十九章:Spring Environment 抽象

    理解 Spring Environment 抽象

    • 统一的 Spring 配置属性管理

      • PropertySource
      • Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)
      • PropertyPlaceholderConfigurer 在 Spring 5.2 已过期
    • 条件化 Spring Bean 装配管理

      • Profile
      • 通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean
    • Environment 接口功能分析

      • 继承了 org.springframework.core.env.PropertyResolver 接口,此接口提供属性管理和解析占位符的能力
      • org.springframework.core.env.Environment 接口提供管理 Profiles 的能力
    • Spring 5.2 以后,PropertySourcesPlaceholderConfigurer 取代 PropertyPlaceholderConfigurer ,两者功能类似,都继承自 PlaceholderConfigurerSupport 优势在于 EnvironmentPropertySource

    Spring Environment 接口使用场景

    • 用于属性占位符处理
    • 用于转换 Spring 配置属性类型
    • 用于存储 Spring 配置属性源(PropertySource)
    • 用于 Profiles 状态的维护

    Environment 占位符处理

    • Spring 3.1 前占位符处理

      • 组件:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
      • 接口:org.springframework.util.StringValueResolver
    • Spring 3.1+ 占位符处理

      • 组件:org.springframework.context.support.PropertySourcesPlaceholderConfigurer
      • 实现类:org.springframework.beans.factory.config.EmbeddedValueResolver
        • 实现了 StringValueResolver 接口
    • PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer 都实现了BeanFactoryPostProcessor,两者对占位符的处理都位于 BeanFactoryPostProcessor#postProcessBeanFactory 方法中

    理解条件配置 Spring Profiles

    • Spring 3.1 条件配置

      • API: org.springframework.core.env.ConfigurableEnvironment
        • 修改:addActiveProfile(String)、setActiveProfiles(String...)setDefaultProfiles(String...)
        • 获取:getActiveProfiles()getDefaultProfiles()
        • 匹配:acceptsProfiles(String...)acceptsProfiles(Profiles)
    • 注解:@org.springframework.context.annotation.Profile

    • 属性值常量:org.springframework.core.env.AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME

      • spring.profiles.active,可以通过设置此属性,设置 active Profiles

    Spring 4 重构 @Profile

    • 基于 Spring 4 接口实现
      • @org.springframework.context.annotation.Conditional
      • org.springframework.context.annotation.Condition
        • org.springframework.context.annotation.ProfileCondition

    依赖注入 Environment

    • 直接依赖注入

      • 通过 EnvironmentAware 接口回调
      • 通过 @Autowired 注入 Environment
    • 间接依赖注入

      • 通过 ApplicationContextAware 接口回调
      • 通过 @Autowired 注入 ApplicationContext
    • ApplicationContextEnvironment 是一一对应的

    • AbstractApplicationContext#prepareBeanFactory 中,以单例形式,将 Environment 注入了 BeanFactory

      if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
      	beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
      }
      

    依赖查找 Environment

    • 直接依赖查找

      • 通过 org.springframework.context.ConfigurableApplicationContext#ENVIRONMENT_BEAN_NAME
    • 间接依赖查找

      • 通过 org.springframework.context.ConfigurableApplicationContext#getEnvironment

    依赖注入 @Value

    • 通过注入 @Value
      • 实现 - org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

      • @Value 注解的解析在 populateBean 阶段

      • 解析的生命周期在 InstantiationAwareBeanPostProcessor#postProcessProperties

      • AutowiredAnnotationBeanPostProcessor 实现 InstantiationAwareBeanPostProcessor

      • 解析方法位于 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

        • org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency

    Spring 类型转换在 Environment 中的运用

    • Environment 底层实现

      • 底层实现 - org.springframework.core.env.PropertySourcesPropertyResolver
        • 核心方法 - convertValueIfNecessary(Object, Class)
      • 底层服务 - org.springframework.core.convert.ConversionService
        • 默认实现 - org.springframework.core.convert.support.DefaultConversionService
    • Environment#getProperty ,从这个方法开始分析

    • AbstractEnvironment#getProperty 使用 PropertySourcesPropertyResolverPropertySourcesPropertyResolver 使用 ConversionService

    Spring 类型转换在 @Value 中的运用

    • @Value 底层实现
      • 底层实现 - org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
        • org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
      • 底层服务 - org.springframework.beans.TypeConverter
        • 默认实现 - org.springframework.beans.TypeConverterDelegate
          • java.beans.PropertyEditor
          • org.springframework.core.convert.ConversionService

    Spring 配置属性源 PropertySource

    • API
      • 单配置属性源 - org.springframework.core.env.PropertySource
      • 多配置属性源 - org.springframework.core.env.PropertySources
    • 注解
      • 单配置属性源 - @org.springframework.context.annotation.PropertySource
      • 多配置属性源 - @org.springframework.context.annotation.PropertySources
    • 关联
      • 存储对象 - org.springframework.core.env.MutablePropertySources
      • 关联方法 - org.springframework.core.env.ConfigurableEnvironment#getPropertySources

    Spring 内建的配置属性源

    • 內建 PropertySource
    PropertySource 类型 说明
    org.springframework.core.env.CommandLinePropertySource 命令行配置属性源
    org.springframework.jndi.JndiPropertySource JDNI 配置属性源
    org.springframework.core.env.PropertiesPropertySource Properties 配置属性源
    org.springframework.web.context.support.ServletConfigPropertySource Servlet 配置属性源
    org.springframework.web.context.support.ServletContextPropertySource ServletContext 配置属性源
    org.springframework.core.env.SystemEnvironmentPropertySource 环境变量配置属性源

    基于注解扩展 Spring 配置属性源

    • @org.springframework.context.annotation.PropertySource 实现原理

      • 入口 - org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
        • org.springframework.context.annotation.ConfigurationClassParser#processPropertySource
      • 4.3 新增语义
        • 配置属性字符编码 - encoding
        • org.springframework.core.io.support.PropertySourceFactory
      • 适配对象 - org.springframework.core.env.CompositePropertySource
    • PropertySource 之前只支持 properties 文件,从 Spring 4.3 开始支持其他格式文件

      • org.springframework.context.annotation.PropertySource#factory
        • org.springframework.core.io.support.EncodedResource

    基于 API 扩展 Spring 配置属性源

    • Spring 应用上下文启动前装配 PropertySource

    • Spring 应用上下文启动后装配 PropertySource

    • 修改 PropertySource 里的属性值,是否影响 Bean 里的注入属性值,需要考虑到 Bean 的初始化时机

    • PropertySource 存在顺序,总是取先匹配到的

    课外资料

    • Spring 4.1 测试配置属性源 - @TestPropertySource

    面试题

    简单介绍 Spring Environment 接口?

    • 核心接口 - org.springframework.core.env.Environment

    • 父接口 - org.springframework.core.env.PropertyResolver

    • 可配置接口 - org.springframework.core.env.ConfigurableEnvironment

    • 职责:

      • 管理 Spring 配置属性源
      • 管理 Profiles

    如何控制 PropertySource 的优先级?

    • org.springframework.core.env.ConfigurableEnvironment#getPropertySources
      • org.springframework.core.env.MutablePropertySources
        • MutablePropertySources 中存在控制 PropertySource 顺序的方法

    Environment 完整的生命周期是怎样的?

    第二十章:Spring 应用上下文生命周期

    ApplicationContext 接口继承关系

    img

    Spring 应用上下文生命周期

    • AbstractApplicationContext#refresh

      @Override
      public void refresh() throws BeansException, IllegalStateException {
          synchronized (this.startupShutdownMonitor) {
              // Prepare this context for refreshing.
              prepareRefresh();
      
              // Tell the subclass to refresh the internal bean factory.
              ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      
              // Prepare the bean factory for use in this context.
              prepareBeanFactory(beanFactory);
      
              try {
                  // Allows post-processing of the bean factory in context subclasses.
                  postProcessBeanFactory(beanFactory);
      
                  // Invoke factory processors registered as beans in the context.
                  invokeBeanFactoryPostProcessors(beanFactory);
      
                  // Register bean processors that intercept bean creation.
                  registerBeanPostProcessors(beanFactory);
      
                  // Initialize message source for this context.
                  initMessageSource();
      
                  // Initialize event multicaster for this context.
                  initApplicationEventMulticaster();
      
                  // Initialize other special beans in specific context subclasses.
                  onRefresh();
      
                  // Check for listener beans and register them.
                  registerListeners();
      
                  // Instantiate all remaining (non-lazy-init) singletons.
                  finishBeanFactoryInitialization(beanFactory);
      
                  // Last step: publish corresponding event.
                  finishRefresh();
              }
      
              catch (BeansException ex) {
                  if (logger.isWarnEnabled()) {
                      logger.warn("Exception encountered during context initialization - " +
                              "cancelling refresh attempt: " + ex);
                  }
      
                  // Destroy already created singletons to avoid dangling resources.
                  destroyBeans();
      
                  // Reset 'active' flag.
                  cancelRefresh(ex);
      
                  // Propagate exception to caller.
                  throw ex;
              }
      
              finally {
                  // Reset common introspection caches in Spring's core, since we
                  // might not ever need metadata for singleton beans anymore...
                  resetCommonCaches();
              }
          }
      }
      

    Spring 应用上下文启动准备阶段

    • AbstractApplicationContext#prepareRefresh() 方法
      • 启动时间 - startupDate
      • 状态标识 - closed(false)active(true)
      • 初始化 PropertySources - initPropertySources()
        • 默认为空方法,子类可继承
        • Web 应用上下文继承这个方法,并将 Web 参数初始化为 PropertySource
      • 检验 Environment 中必须属性
        • AbstractPropertyResolver#requiredProperties,默认为空,可设置
      • 初始化事件监听器集合
        • earlyApplicationListeners
        • applicationListeners
      • 初始化早期 Spring 事件集合
        • earlyApplicationEvents

    BeanFactory 创建阶段

    • AbstractApplicationContext#obtainFreshBeanFactory 方法
      • 抽象方法
      • 子类实现,AbstractRefreshableApplicationContext#refreshBeanFactory
        • 刷新 Spring 应用上下文底层 BeanFactory - refreshBeanFactory()
          • 销毁或关闭 BeanFactory ,如果已存在的话
          • 创建 BeanFactory - createBeanFactory()
            • DefaultListableBeanFactory
          • 设置 BeanFactory Id
          • 自定义 BeanFactory 属性 - customizeBeanFactory(beanFactory)
          • 设置 “是否允许 BeanDefinition 重复定义” - customizeBeanFactory(DefaultListableBeanFactory)
            • AbstractRefreshableApplicationContext#allowBeanDefinitionOverriding
            • 默认为 true
            • 设置 “是否允许循环引用(依赖)” - customizeBeanFactory(DefaultListableBeanFactory)
              • AbstractRefreshableApplicationContext#allowCircularReferences
              • 默认为 true
          • 加载 BeanDefinition - loadBeanDefinitions(DefaultListableBeanFactory) 方法
            • 抽象方法
          • 关联新建 BeanFactory 到 Spring 应用上下文
    • 返回 Spring 应用上下文底层 BeanFactory - getBeanFactory()
      • 抽象方法

    BeanFactory 准备阶段

    • AbstractApplicationContext#prepareBeanFactory(ConfigurableListableBeanFactory) 方法
      • 关联 ClassLoader
      • 设置 Bean 表达式处理器
        • 与 SpEL 表达式相关
        • org.springframework.context.expression.StandardBeanExpressionResolver
      • 添加 PropertyEditorRegistrar 实现 - ResourceEditorRegistrar
        • org.springframework.beans.support.ResourceEditorRegistrar
      • 添加 Aware 回调接口 BeanPostProcessor 实现 - ApplicationContextAwareProcessor
        • EnvironmentAware
        • EmbeddedValueResolverAware
        • ResourceLoaderAware
        • ApplicationEventPublisherAware
        • MessageSourceAware
        • ApplicationContextAware
      • 忽略 Aware 回调接口作为依赖注入接口
      • 注册 ResolvableDependency 对象 - BeanFactoryResourceLoaderApplicationEventPublisher 以及 Applicationcontext
        • BeanFactoryApplicationContext 关联的 BeanFactory
        • ResourceLoaderApplicationEventPublisher 以及 Applicationcontext 都是 ApplicationContext
      • 添加 BeanPostProcessor - ApplicationListenerDetector
        • BeanPostProcessor#postProcessAfterInitialization 阶段,将单例的 ApplicationListener 加入 ApplicationContext
      • 如果包含 beanName 是 loadTimeWeaver 的 bean,注册 BeanPostProcessor - LoadTimeWeaverAwareProcessor 对象,并设置容器的临时 ClassLoaderAbstractBeanFactory#tempClassLoader
        • 与 AOP 相关
      • 注册单例对象 - Environment、Java System Properties 以及 OS 环境变量
        • environment - ApplicationContext#environment
        • systemProperties - (Map) System.getProperties()
        • systemEnvironment - (Map) System.getenv()

    BeanFactory后置处理阶段

    • AbstractApplicationContext#postProcessBeanFactory(ConfigurableListableBeanFactory) 方法
      • 由子类覆盖该方法
    • org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) 方法
      • 判断 BeanFactory 是不是 BeanDefinitionRegistry 的实例
        • DefaultListableBeanFactory 实现 BeanDefinitionRegistry
        • 如果是,调用 BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor 后置处理方法
          • BeanDefinitionRegistryPostProcessor 继承 BeanFactoryPostProcessor
          • BeanFactoryPostProcessor#postProcessBeanFactory
          • BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
        • 如果不是,只调用 BeanFactoryPostProcessor 后置处理方法 BeanFactoryPostProcessor#postProcessBeanFactory
      • 如果包含 beanName 是 loadTimeWeaver 的 bean,注册 BeanPostProcessor - LoadTimeWeaverAwareProcessor 对象,并设置容器的临时 ClassLoaderAbstractBeanFactory#tempClassLoader
        • 与 AOP 相关

    执行顺序:

    1. BeanDefinitionRegistryPostProcessor 进行处理
      1. 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
        1. 按照添加顺序执行, AbstractApplicationContext#beanFactoryPostProcessors 中的 BeanDefinitionRegistryPostProcessor
        2. 按照 Order 顺序执行,BeanFactory 中实现了 PriorityOrderedBeanDefinitionRegistryPostProcessor Bean
        3. 按照 Order 顺序执行,BeanFactory 中实现了 OrderedBeanDefinitionRegistryPostProcessor Bean
        4. 按照 Order 顺序执行,其他 BeanFactory 中的 BeanDefinitionRegistryPostProcessor Bean
      2. 执行 BeanFactoryPostProcessor#postProcessBeanFactory
        1. AbstractApplicationContext#beanFactoryPostProcessors 中的普通 BeanFactoryPostProcessor
        2. BeanFactoryBeanDefinitionRegistryPostProcessor
    2. BeanFactoryPostProcessor 继续处理,BeanFactoryPostProcessor#postProcessBeanFactory
      1. 按照 Order 顺序执行,实现 PriorityOrdered 接口的 BeanFactoryPostProcessor
      2. 按照 Order 顺序执行,实现 Ordered 接口的 BeanFactoryPostProcessor
      3. 其他常规 BeanFactoryPostProcessor

    BeanFactory 注册 BeanPostProcessor 阶段

    • AbstractApplicationContext#registerBeanPostProcessors(ConfigurableListableBeanFactory) 方 法
      • 注册 PriorityOrdered 类型的 BeanPostProcessor Beans
      • 注册 Ordered 类型的 BeanPostProcessor Beans
      • 注册普通 BeanPostProcessor Beans
      • 注册 MergedBeanDefinitionPostProcessor Beans
        • MergedBeanDefinitionPostProcessor 继承 BeanPostProcessor,生命周期在 MergedBeanDefinition 后
      • 重新注册 ApplicationListenerDetector 对象
        • 为了将 ApplicationListenerDetector 的顺序放到最后

    初始化內建 Bean: MessageSource

    • AbstractApplicationContext#initMessageSource 方法
      • 如果 BeanFactory 中存在 beanName 为 messageSourceMessageSource ,则使用,否则注册 DelegatingMessageSource
      • 回顾章节 - 第十二章 Spring 国际化 - MessageSource 内建依赖

    初始化內建 Bean: Spring 事件广播器

    • AbstractApplicationContext#initApplicationEventMulticaster 方法
      • 如果 BeanFactory 中存在 beanName 为 applicationEventMulticasterApplicationEventMulticaster ,则使用,否则注册 SimpleApplicationEventMulticaster
      • 回顾章节 - 第十七章 Spring 事件 - ApplicationEventPublisher 底层实现

    Spring 应用上下文刷新阶段

    • AbstractApplicationContext#onRefresh 方法
      • 空方法,由子类覆盖该方法
        • org.springframework.web.context.support.AbstractRefreshableWebApplicationContext#onRefresh
        • org.springframework.web.context.support.GenericWebApplicationContext#onRefresh
        • org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#onRefresh
        • org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
        • org.springframework.web.context.support.StaticWebApplicationContext#onRefresh

    Spring 事件监听器注册阶段

    • AbstractApplicationContext#registerListeners 方法
      • ApplicationListener 添加到 AbstractApplicationContext#applicationEventMulticaster
        • 添加当前应用上下文所关联的 ApplicationListener 对象(集合)
        • 添加 BeanFactory 所注册 ApplicationListener Beans
      • 广播早期 Spring 事件
        • AbstractApplicationContext#earlyApplicationEvents

    BeanFactory 初始化完成阶段

    • AbstractApplicationContext#finishBeanFactoryInitialization(ConfigurableListableBeanFactory) 方法
      • BeanFactory 关联 ConversionService Bean,如果存在

        • beanName 为 conversionServiceConversionService
      • 添加 StringValueResolver 对象

        • 如果 AbstractBeanFactory#embeddedValueResolvers 为空,添加一个

          if (!beanFactory.hasEmbeddedValueResolver()) {
          	beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
          }
          
      • 依赖查找 LoadTimeWeaverAware Bean

      • BeanFactory 临时 ClassLoader 置为 null

      • BeanFactory 冻结配置

        • DefaultListableBeanFactory#configurationFrozen
        • DefaultListableBeanFactory#frozenBeanDefinitionNames
      • BeanFactory 初始化非延迟单例 Beans

        1. 初始化非延迟单例 Bean
        2. 触发单例 Bean 中的 SmartInitializingSingleton 的生命周期, SmartInitializingSingleton#afterSingletonsInstantiated

    Spring 应用上下文刷新完成阶段

    • AbstractApplicationContext#finishRefresh 方法
      • 清除 ResourceLoader 缓存 - clearResourceCaches() @since 5.0
      • 初始化 LifecycleProcessor 对象 - initLifecycleProcessor()
        • 如果不存在 beanName 为 lifecycleProcessorLifecycleProcessor,则使用 DefaultLifecycleProcessor
      • 调用 LifecycleProcessor#onRefresh() 方法
      • 发布 Spring 应用上下文已刷新事件 - ContextRefreshedEvent
      • MBeanServer 托管 Live Beans

    Spring 应用上下文启动阶段

    • AbstractApplicationContext#start() 方法
      • 启动 LifecycleProcessor
        • 依赖查找 Lifecycle Beans
        • 启动 Lifecycle Beans
    • 发布Spring应用上下文已启动事件 - ContextStartedEvent

    Spring 应用上下文停止阶段

    • AbstractApplicationContext#stop() 方法
      • 停止 LifecycleProcessor
      • 依赖查找 Lifecycle Beans
      • 停止 Lifecycle Beans
    • 发布 Spring 应用上下文已停止事件 - ContextStoppedEvent

    Spring 应用上下文关闭阶段

    • AbstractApplicationContext#close() 方法
    • 状态标识:active(false)closed(true)
    • Live Beans JMX 撤销托管
      • LiveBeansView#unregisterApplicationContext(ConfigurableApplicationContext)
    • 发布 Spring 应用上下文已关闭事件 - ContextCLosedEvent
    • 关闭 LifecycleProcessor
      • 依赖查找 Lifecycle Beans
      • 停止 Lifecycle Beans
    • 销毁 Spring Beans
    • 关闭 BeanFactory
    • 回调 onClose()
    • 注册 Shutdown Hook 线程(如果曾注册)

    面试题

    Spring 应用上下文生命周期有哪些阶段?

    • 刷新阶段 - ConfigurableApplicationContext#refresh()
    • 启动阶段 - ConfigurableApplicationContext#start()
    • 停止阶段 - ConfigurableApplicationContext#stop()
    • 关闭阶段 - ConfigurableApplicationContext#close()

    Environment 完整的生命周期是怎样的?

    Spring 应用上下文生命周期执行动作?

    课程加餐内容讨论

    为什么说 ObjectFactory 提供的是延迟依赖查找?

    • 原因
      • ObjectFactory (或 ObjectProvider )可关联某一类型 Bean
      • ObjectFactoryObjectProvider 对象在被依赖注入和依赖查询时并未实时查找关联类型的 Bean
      • ObjectFactory (或 ObjectProvider )调用 getObject() 方法时,目标 Bean 才被依赖查找
    • 总结
      • ObjectFactory (或 ObjectProvider )相当于某一类型 Bean 依赖查找代理对象

    依赖查找(注入)的Bean会被缓存吗?

    • 单例 Bean (Singleton) - 会
      • 缓存位置:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects 属性
    • 原型 Bean (Prototype) - 不会
      • 当依赖查询或依赖注入时,根据 BeanDefinition 每次创建
    • 其他 Scope Bean
      • request : 每个 ServletRequest 内部缓存,生命周期维持在每次HTTP请求
      • session : 每个 HttpSession 内部缓存,生命周期维持在每个用户HTTP会话
      • application : 当前 Servlet 应用内部缓存

    @Bean 的处理流程是怎样的?

    • 解析范围 - Configuration Class 中的 @Bean 方法
    • 方法类型 - 静态 @Bean 方法和实例 @Bean 方法

    BeanFactory是如何处理循环依赖的?

    • 循环依赖开关(方法)- AbstractAutowireCapableBeanFactory#setAllowCircularReferences
    • 单例工程(属性)- DefaultSingletonBeanRegistry#singletonFactories
    • 获取早期未处理 Bean (方法)- AbstractAutowireCapableBeanFactory#getEarlyBeanReference
    • 早期未处理 Bean (属性)- DefaultSingletonBeanRegistry#earlySingletonObjects

    MyBatis 与 Spring Framework 是如何集成的?

  • 相关阅读:
    Game的基本元素.[小糊涂的灵感]
    J2ME图书介绍 [小糊涂的灵感]
    j2me 这个论坛好一点.[小糊涂的灵感]
    Frame rate test for tilebased games 测试结果.[小糊涂的灵感]
    源码方式在ubuntu系统上安装ruby1.9.2
    模块全解======>>ruby的类是单继承生物、所以出现了module、实现了多继承
    在ubuntu下安装rails3.0
    在ubuntu下编写运行shell脚本
    在linux下开远程桌面访问windows的解决方法
    在命令行中打开sqlite的数据库
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/13946011.html
Copyright © 2020-2023  润新知