commit 9c7abe3c2375eb6411feba86a3c434fc4ca58ca0 Author: Rikako Wu <496063163@qq.com> Date: Fri Apr 8 13:15:24 2022 +0800 daily commit diff --git a/Git/git.md b/Git/git.md new file mode 100644 index 0000000..7a5f208 --- /dev/null +++ b/Git/git.md @@ -0,0 +1,89 @@ +# Git +* ## Git基本操作 + * ### 获得一个git仓库,可通过如下方式: + * 将并未使用版本控制的本地目录转化为git仓库 + * 在本地目录下,可以通过git init来将当前目录置于git的版本控制管理之下 + * 从远程(比如GitHub)克隆一个git仓库 + ```shell + > git init + ``` + * git clone [dirname] 可以将git项目从远程拷贝到本地 + ```shell + > git clone $url + ``` + * ### 记录git仓库的变化 + * git仓库中文件的状态: + * tracked:处于tracked状态的文件是在最后一次提交的快照中存在的文件,git仓库知道这些文件的存在。这些文件可以是modified、unmodified、staged + * untracked:处于untracked状态的文件,在最后一次提交的快照中并不存在该文件,而且该文件也并不属于unstaged状态, + * git文件状态变化: + * 当一个文件处于untracked状态时,要将对该文件调用git add状态将其变为tracked状态,并且要通过git commit命令将变化提交,此时文件会变为unmodified状态 + * 可以通过git status来查看当前git仓库中各个文件的状态 + ```shell + # 查看git仓库的信息 + > git status + ``` + * 跟踪git仓库中文件状态: + * git add命令可以将文件从untracked状态变为tracked状态,并且能将对文件的修改从unstaged状态变为staged状态 + ```shell + # 如果参数后跟的是目录名称,那么会递归的将目录中所有的文件都标记为staged状态 + > git add [文件名/目录名] + ``` + * 在对文件进行修改之后,必须调用git add将修改变为staged状态,否则,在修改后直接调用git commit,提交的仍然是上次调用git add时的文件状态。 + * 可以通过为git status添加更多选项来指定输出: + ```shell + # 为git status指定 -s或者 --short选项,会用字母来表示各个文件的状态 + # ?? 表示文件为untracked状态 + # A 表示被添加到staging area的新文件 + # M 表示文件被修改过但是没有被staged + # AM 表示该文件在add之后又被修改过 + > git status -s + ``` + * 如果想要git仓库不跟踪一些文件的文件状态(例如日志文件、由系统产生的文件等),在调用git status时也不希望该类文件被列出,可以通过在目录下指定一个.gitignore文件,并且在文件中指明被忽略的文件的模式 + ```shell + # .gitignore文件语法 + # 空行或者以'#'开始的行将会被忽略 + # 模式会递归的应用到文件树中的所有文件 + # 可以在模式开始之前加上/符号来避免递归 + # 可以将模式以/结尾来指定一个目录 + # 可以通过!表示明确追踪某个模式的文件(例如,如果确定忽略*.a模式 + # 的文件,但是只想追踪liba.a文件,可以指定模式为 + # *.a + # !liba.a + # 可以通过**来匹配嵌套的目录,如a/**/b,既可以匹配a/b,也可以 + # 匹配a/x/y/b和a/x/b + $ cat .gitignore + *.[oa] + *~ + ``` + * 查看git仓库中文件变化的具体细节 + * 查看仓库中文件变化的具体细节,有如下两类查看: + * 查看已经修改但是并没有被staged的修改: + ```shell + # 该命令会比较work directory和staging area的内容,并且输出具体 + # 哪些内容没有被staged + > git diff + ``` + * 查看仓库中文件已经被staged但是没有被commit的变化: + ```shell + # 该命令会比较staging area中的内容和上次提交的内容,并且输出 + # 具体的差异内容 + $ git diff --staged + ``` + * git提交: + * 可以通过git commit命令来提交staging area中的内容 + ```shell + # 通过git commit可以提交已经被staged的修改,但是,已经修改但是 + # 没有被staged的修改并不会被commit + # 可以通过git commit -a来省略git add的过程,其会自动对已经traked + # 的文件调用git add操作 + $ git commit [ -a ] + ``` + * 可以通过git rm来将文件从tracked files中移除,并且,工作目录中的该文件也会被移除 + * 如果该文件已经被修改或是已经将修改提交到staging area中,则git rm方法必须要指定-f选项强制删除(避免错误调用git rm而导致处于staged状态但是并未被提交的修改或者还未被staged的修改丢失,需要强制指定-f) + * 如果想要将文件从staging area中删除,但是不将其从文件系统中删除,可以为git rm命令指定--cached选项 + ```shell + # --cached选项通常用于.gitignore文件中遗漏文件被git add命令提 + # 交到staging area的情况,此时可以通过git rm --cached命令 + # 将提交的文件修改从staging area中移除 + ``` + * git mv:可以通过git mv来对文件进行重命名 diff --git a/dart/dart.md b/dart/dart.md new file mode 100644 index 0000000..b32bc67 --- /dev/null +++ b/dart/dart.md @@ -0,0 +1,79 @@ +# dart语法 +* ## dart语法中需要注意的事项 + * print方法传入对象时默认会将对象转化为字符串,会通过toString方法来转化。默认情况下toString方法未被重写,此时会返回”Instance of 'ClassName'“ + * 字符串可以通过 " ${varname}" 来将变量值填充到字符串中 + * @override会告知编译器检查是否方法被覆盖 + * 若想把变量声明为私有的,需要在变量名之前加上单下划线_,加上下划线前缀后该变量只能在当前文件的可见,在其他文件中是不可见的(dart中不存在类范围可见) + * dart中的变量是不能为空的,变量必须要经过初始化。如果变量可以为空,要在变量类型后面加上? + * 默认情况下,dart为公共的实例变量提供了隐式的getter和setter,不需要手动为变量指定getter和setter,除非想要将变量标明为只读或者只写的 + * final和const都表示一个变量在赋值之后不能被修改,但是const代表这个变量是编译时常量,而final修饰的变量可以用变量为其赋值,但是赋值之后final修饰的变量不可变 + * const除了可以用来修饰变量以外,还能够用来修饰变量值。可以为list等类型的值指定一个const,如const [],此时该值是无法修改的,无法向list中添加元素、 + * 可以通过get和set方法来为字段指定getter和setter,以此将字段表示为只读或者只写的 + * dart并不支持重载,但是dart可以设置可选参数,可选的命名参数通过{}来包围,并且命名参数可以通过=来赋默认值 + * 可以通过factory关键字来为类型创建工厂构造方法 + * dart中可以通过implements关键字来实现类之间的继承关系。每个类都定义了一个接口,如果一个类被其他类implements,那么子类会继承父类的接口,但是子类并不会继承父类的实现,需要手动实现父类中的方法 + * dart支持函数式编程,函数能作为参数传递给方法,并且函数能赋值给变量 +* ## dart中的异步操作 + * dart中可以通过Future来保存异步操作的结果,Future有两个状态,已完成和未完成 + * 当调用一个异步方法时,该方法会返回一个Future对象,并且该Future对象的状态为未完成 + * 可以通过async关键字和await关键字来定义异步方法并且使用它们的返回值,await只能够在async方法中使用 +* ## dart中的类型 + * dart中所有的变量引用的都是对象,对象都是一个真实类的实例。除了null以外,所有的类都继承自object。 + * dynamic、Object、var区别: + * Object:可以将任何对象赋值给Object引用,所有类都继承自Object + * var:编译器会动态推断var变量所引用的对象类型,当给var变量赋一种类型的值后,var变量就被自动推断为这种类型,之后无法将其他类型的值赋值给var变量 + * dynamic:将类型检查延迟到运行时,但是这样可能会造成语法检查失效的情况,如果对dynamic变量引用的对象调用其中不存在的方法,这样编辑代码时也不会报错,而是直到运行时才抛出异常 + * late修饰符: + * 对于局部变量,dart通常可以判断不可为空的变量何时被初始化,故而其可以在声明时不初始化,而是后续初始化 + * 但是对于全局变量和实例变量,dart无法判断其何时被初始化,故而dart不会对其进行判断,在声明时必须被初始化 + * 如果确定全局变量或实例变量后续会被初始化,那么可以为其指明late修饰符,表明其会序会被初始化,编译器会取消报错 + * 如果被late修饰的变量在声明时初始化,那么其初始化过程只会在变量第一次被使用时调用,如果一个声明为late的变量没有被调用,那么其初始化过程也不会被调用 + * dart中类型: + * Numbers: + * int:整数类型 + * double:浮点数类型 + * num:如果变量被声明为num类型,那么其既可以为int,也可以为double + * 字符串和数字之间的转化可以通过如下安徽念书来实现: + * int.parse + * double.parse + * intObject.toString + * doubleObject.toString + * Strings: + * dart中String类似于java中的String,如果想要对字符串进行修改,可以使用StringBuffer + * 对于字符串,如果在字符串之前加上r,表示该字符串是raw的,该字符串中的内容将不会被转义,如\n和${}等字符串中内容都不会被转义 + * 布尔 + * Lists: + * 对List类型的对象,可以采用扩展操作符...或者...?来对list对象中的元素进行拓展 + * 可以通过for和if语法来对List集合中插入元素 + * Sets + * Maps + * 函数: + * dart中函数可以可以通过{}来设置可选参数,如果可选参数列表中某参数是必须的,可以在该参数之前加上required关键字 + * dart中可以通过[]来标明可选的位置参数,通常将[]放置到最后 + * 词法闭包: + * 定义在方法内部的方法可以访问其外部的变量,并且持有其状态 + * 通过闭包,可以隐藏变量,防止其变量被其他方法直接访问,而是通过调用返回的方法来访问和修改对象 + * 异常: + * dart中异常可以通过on或者catch来捕获: + * on XXXException会捕获特定类型的异常,比如on FormatException {} + * catch捕获非特定类型的异常 + * 通过rethrow关键字可以将捕获的异常重新抛出 + * ## dart中的类机制 + * 对于dart中的类型,除了null之外,所有的类都继承自Object类 + * 如果子类要调用父类的构造方法,需要在初始化列表中进行调用,类似于c++语法 + * 可以在初始化列表中指定重定向构造函数,此时可以通过this(params...)方法来调用其他的构造函数 + * 常量构造函数: + * 如果生成类的对象都是不可变的,可以在构造函数之前加上const来标识其为常量构造函数 + * 工厂构造函数: + * 通过factory关键字可以指定工厂构造函数 + * 运算符重写: + * 可以通过operator关键字来对类型的操作符进行重写 + * getter/setter方法: + * 可以通过get、set关键字来添加getter和setter方法 + * 类接口: + * 每个类都隐式定义了一个接口并且实现了该接口,该隐式接口包含了类所有的实例成员并且包含这个类所实现的其他接口。如果一个想让类B调用类A的API并且不想继承类A的实现,则可以让类B实现类A的接口 + * 扩展类: + * 可以通过extends关键字来继承父类并且可以覆盖其实现 + * ## dart中的泛型机制 + * 通过<>可以指定泛型 + * 可以通过extends关键字限定泛型的范围,例如List将类型T限定为Object类的子类 \ No newline at end of file diff --git a/flutter/flutter.md b/flutter/flutter.md new file mode 100644 index 0000000..b8e5342 --- /dev/null +++ b/flutter/flutter.md @@ -0,0 +1,42 @@ +# Flutter +* ## Flutter结构: + * Flutter的UI界面由Widget来构成,每当构成界面的Widget状态发生改变,那么其将会对Widget构成的UI进行重新渲染 + * 为了创建一个Flutter应用,仅需调用runApp方法并且向其中传入一个Widget即可,传入的Widget将会成为Widget树中的根节点 +* ## Flutter中的基础Widget + * ### Text:Text Widget用来表示一组具有同一样式的文本 + * ### Row:用来构造水平布局 + * 要想使用水平布局,可以向Row的children属性中传入一个Widget的List,如果想要子Widget扩展去尽可能的填充可以获取的垂直控件,可以将子Widget用Expanded包裹 + * ### Column: 类似于Row,用来构建垂直布局 + * ### Stack布局:Stack布局可以将children属性中的Widget集合重叠起来,默认左上角对齐。Stack中的Wdiget按先后顺序进行绘制,位于第一的Wiget会被绘制到最底层,后面的元素依次覆盖前一个元素。 + * Stack中可以由Positioned元素和non-Positioned元素,Positioned元素由Positioned Widget包裹,可以指定其在Stack中相对Stack的位置,而不是默认左上角对齐 + * ### Container:创建一个可见的矩形区域,可以通过BoxDecoration来指定Container的边框样式、背景颜色等 + * ### 可以通过MaterialApp和Scaffold等组件来构建Material Design的APP +* ## 处理用户的手势 + * 通过用GestureDetector包裹Widget可以检测用户的手势,并为onTap等手势创建回调函数 +* ## StatefulWidget + * 相比较于StatelessWidget,StatefulWidget可以维护状态信息 + * StatefulWidget的使用: + * StatefulWidget子类通过覆盖createState方法可以维护一个State对象来存储状态信息 + * 在State对象中, + * 通过覆盖State对象的build方法,返回一个Widget对象来显示StatefulWidget的UI + * 每次对State中维护的状态进行修改时,都要调用setState方法,并且传入一个函数 + * 每次调用setState方法时,都会重新调用build方法来对StatefulWidget的UI进行重新渲染 +* ## Flutter中的布局 + * Row和Column:通常用于创建行和列的布局 + * mainAxisAlignment/crossAxisAlignment:用于控制主轴和交叉轴如何对其元素 + * 当Widget太大而超出布局时,可以通过为Widget外层套一个Expand来让其适应行或者列布局 + * 如果在行或者列布局中,想要一个widget的弹性布局空间是其他兄弟widget的2倍,可以设置Expand的flex属性为2。flex属性默认值是1. + * 默认情况下,行或列布局会沿着主轴尽量的占据空间。如果要将子项目尽量贴合在一起,可以使用mainAxisSize属性,将其设置为mainAxisSize.min + * 标准widget + * Container:类似于html中的div,可以为widget增加padding、margin、borders、background color或者其他的装饰 + * GridView:将widget作为二位列表显示,有两种方式来创建GridView + * GridView.extent:通过指定单元格的最大宽度 + * GridView.count:指定列的数量 + * ListView:当内容过多时会自动的支持滚动 + * Stack:Stack布局会将children中位于后面的Widget覆盖到位于前面的Widget之上 + * Material Widget + * Card:通过将Widget设置为Card的child,可以添加圆角和阴影的效果 + * ListTile:用于显示行信息,具有如下属性: + * title:为行指定title信息 + * subtilte:为行指定附加信息,如果isThreeLine为false,subtitle不能换行,如果为true,subtitle可以为两行 + * leading:为行定义icon \ No newline at end of file diff --git a/jwt/jwt.md b/jwt/jwt.md new file mode 100644 index 0000000..96eeb8f --- /dev/null +++ b/jwt/jwt.md @@ -0,0 +1,4 @@ +# JWT +* ## jwt简介 + > jwt是一个开源标准。jwt定义了一个紧凑且自包含的方式安全的在多方之间通过json字符串传递信息。由于传递的信息会被签名,故而信息是可验证和信任的。 +* \ No newline at end of file diff --git a/maven/maven.md b/maven/maven.md new file mode 100644 index 0000000..ad15377 --- /dev/null +++ b/maven/maven.md @@ -0,0 +1,153 @@ +# maven +* ## maven的pom文件结构 + * project:project元素是pom文件的顶层元素 + * modelVersion:pom文件使用的model版本 + * groupId + * artifactId + * version + * name:maven项目的名称 + * url:项目的url资源路径 + * propertities:包含可以在pom文件中任何位置访问的变量 + * dependencies:定义该项目使用的依赖项 + * build:定义项目的目录结构和插件的管理 + * packaging:指明项目打jar包还是war包 +* ## maven命令 + * mvn compile:会对项目进行编译,并且将编译后的字节码放置到basedir/target/classes目录下 + * mvn test:对项目的main源码进行编译,并且再对test源码进行编译,并执行单元测试 + * mvn package:编译项目并且创建jar包,创建的jar包将会被放置到basedir/target目录下 + * mvn install:生成项目对应的jar包并且将生成的jar包安装到本地仓库中(homedir/.m2/repository) + * mvn clean:再开始前移除target目录中所有的文件 +* ## SNAPSHOT + * < verssion >标签值中含有snapshot代表该artifact版本并不是稳定或者不变的,而是一个快照版本,是可能被修改的 +* ## plugin使用 + * 当想对项目的构建过程进行自定义时,需要再pom文件的build标签中添加plugin: + ```pom + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + ``` +* ## 向maven项目中添加资源 + * 如果想要向maven项目中添加资源,那么将资源复制到basedir/src/main/resources或者basedir/test/main/resources目录下,在项目生成jar包时,会将资源复制到jar包中的classpath目录下 +* ## maven项目添加依赖 + * 通过向pom文件中添加dependency可以为项目添加依赖项 + * 依赖至少需要有如下元素: + * groupId + * artifactId + * version + * scope可选:指定依赖的作用范围 + * compile + * test + * runtime +* ## maven项目中parent + * 可以为父maven项目指定modules来指定子项目 + * 可以为子项目指定parent元素来指定父项目 + * 可以在父项目的路径下调用mvn package同时打包多个项目 +* ## maven filtering + * 通过filter,可以在资源文件中使用${}来引用环境变量、定义在pom文件中的变量、定义在外部文件中的变量(需要在filters中指定)、在命令行中通过-D指定的变量 + * 在对资源文件实施filering时,需要指定资源文件路径,并且在resource标签中将filtering属性指定为true +* ## maven生命周期 + * maven项目的生命周期分为如下三种 + * default lifecycle + * clean lifecycle + * site lifecycle:负责web项目的建立和发布 + * maven项目的default生命周期: + * validate:验证项目是否正确 + * compile:编译源代码 + * test:通过单元测试来对编译好的源代码进行测试 + * package:将编译好的源码进行打包,打包成jar包或者是war包 + * verify:对集成测试的结果进行检查 + * install:将打包后的结构安装到本地仓库中 + * deploy:在构建环境中完成,将软件包部署到远程仓库中 + * 当调用上述生命周期的命令时,只需要调用最后一个阶段名称,之前的阶段会被默认执行(eg:如果调用mvn package,那么之前的阶段例如validate、compile、test、package也会被默认执行) + * 如果在调用生命周期命令之前想要清理先前的残留,可以在生命周期之前加入clean命令清理先前留下的target目录,eg:mvn clean package + * maven的build阶段由plugin goals构成 + * 每个plugin goal都会绑定到0或者多个build phase + * 每个build phase都绑定了0或者多个plugin goals,如果一个build phase没有绑定plugin goals,那么它将不会被执行,如果绑定了多个plugin goals,多个plugin goals都会被执行 +* ## maven的打包方式 + * 已知build phase由多个plugin goal组成,那么,在构建项目时,为了将plugin goal和build phase相绑定,可以通过packaging标签指定打包的方式(如jar,war,pom,ear(除了包含war、jar包外,还包含EJB组件) + * 如果指定packaging标签的打包方式为jar,那么maven项目将会为每个build phase都指定一个plugin goals列表;而对于打包成pom的项目,只会为install和deploy的阶段绑定plugin goals +* ## maven插件 + * maven通过plugin来提供plugin goals,plugin类似于dependency,也具有groupId,artifactId,version。plugin可包含多个plugin goals,并且可以为插件指定executions来指定执行的goal,并且可以指定phase将goal与build phase相关联。 +* ## pom文件的继承关系 + * 可以通过为子module中的pom文件指定一个parent元素来指定它的父pom文件。此时通常要求父parent已经安装在本地仓库后者父pom文件位于子module pom文件的上一级目录。 + * 如果不满足上述条件,可以通过为parent元素指定relativePath来显式指定父pom文件的位置 + * 可以为父pom指定modules元素,并将父pom文件中的packaging元素指定为pom,此时对父module调用mvn命令时,相同的命令会被传递给子module执行 + * 子项目pom文件在继承父项目pom文件时,可以省略groupId和version,省略的内容会被自动填充为与父项目中pom文件定义的相同 +* ## maven profile的激活方式 + * 通过命令行激活:可以在mvn命令中指定-P选项,并且在之后跟上profile的id名称来指明激活的profile,-P后跟随的profile将会和activeProfiles中指明的pofile一起被激活 + * 通过activition配置激活:在配置文件中,可以通过在profile标签中指定activition属性来指定激活条件 + * 可以通过jdk版本前缀选择是否激活: + ```pom + + + + 1.8 + + + ``` + * 可以通过os标签指定特定操作系统环境来选择是否激活 + * 可以通过property来通过系统属性判断是否激活,可以通过命令行-D来添加参数控制是否激活(系统环境变量可以通过env.XXX来获取) + * 可以通过文件是否存在(file标签的exists和missing)来判断是否激活 + * activationByDefault:可以通过该标签来决定profile是否被激活 + * 但是如果在同一pom文件中,有其他profile被上述的激活方法激活,那么默认被激活的profile将不会被激活 + * 如果同一个pom文件中的其他profile通过命令行或者上述提到的其他方式被激活,那么所有默认激活的profile都不会被激活 + * deactive一个profile:可以通过-P !profilename来不激活一个profile + * profile定义的位置: + * 外部文件(位于pom文件之外的文件,例如settings.xml或者profiles.xml)只能定义repositories、pluginRepositories、properties属性 + * pom文件内 + * maven依赖管理 + * maven会自动包含可传递的依赖项,避免了需要手动指定项目依赖项所需要依赖的库。 + * 所有从父项目继承的依赖项,或者依赖项所需的依赖,都会在maven的项目中 + * 依赖中介: + * 当项目中存在一个依赖的多个版本时,会选择在依赖树中距离当前项目最近的依赖所选择的版本。如果两个不同版本的依赖项距离当前的项目深度相同,那么第一个被声明的依赖项版本获胜。 + * 可以在项目中明确声明依赖项的版本来保证依赖项版本 + * 依赖管理: + * 可以在dependencyManagement中添加依赖,添加后当发生依赖版本冲突或者没有指定依赖版本时,会使用dependencyManagement中定义的依赖版本 + * springboot在spring-boot-dependencies的pom文件中指定了一些依赖的默认版本,故而在构建springboot项目时一些依赖项并不用指定版本信息 + * 在dependencyManagement中,子项目声明的依赖会优于父项目声明的依赖,如果父项目和子项目同时在依赖管理中声明一个依赖的不同版本,那么以子项目声明的为准 + * 当发生版本冲突时,依赖管理优先于依赖中介,并不是看依赖距离当前项目的深度来决定版本,而是看依赖管理中定义的版本来决定 + * dependency scope: + * 该机制可以允许maven只包含适用于当前阶段的依赖 + * dependency scope: + * compile:默认的scope,在所有阶段的classpath中都可用 + * provided:声明为provided表明其只在编译和测试的时候被需要,而在运行时环境中classpath中并不包含该依赖,该依赖会在runtime中被提供 + * runtime:表明该依赖只在运行时环境中被需要,在runtime classpath中存在该依赖,但是并无法在compile时被访问 + * test:该依赖只会在测试时被使用,而在正式情况下不需要该依赖 + * system:类似于provided,但是必须要提供明确包含该依赖的jar包。该依赖必须一直可见并且不会再仓库中去查找。 + * system依赖要通过配置systemPath来指定该依赖的路径 + * import:仅用于dependencyManagement中的依赖,表示将当前依赖替换为目标pom文件中dependencyManagement中的内容 + * 当导入的依赖版本于当前依赖管理中自定义的依赖版本冲突时,以自定义的依赖版本为准 + * import通常用于解决只能继承一个父pom中依赖管理的问题,可以通过import来导入多个pom文件的依赖继承 + * 当import多个pom文件中的依赖管理时,如果发生了冲突, + * 如果自定义的依赖管理项中包含冲突依赖,以自定义的版本为准 + * 如果自定义的依赖管理项中不包含冲突依赖,那么以最先被导入的版本为准 + * exclude依赖: + * 可以在依赖中指定exclusion来排除某依赖 + * 使用场景: + * 当项目的某个依赖项依赖了一个存在安全问题或者于jdk版本不兼容的依赖项,那么可以通过exclusion来排除该依赖项 + * 当对一个依赖项中的某个依赖进行排除时,不仅可以排除依赖项的直接依赖项,还可以排除该依赖项的传递依赖项 + * 可选依赖 + * 可以通过为依赖添加optional标签来将依赖声明为可选的 + * 如果依赖被声明为可选的,如项目B声明依赖A时可选的,那么当C依赖项目B时,C将不会包含依赖A,如果C要包含依赖A,需要显式的声明依赖A + * 可选依赖的使用场景: + * 如果库X2提供多种数据库的访问API,故而X2需要各种数据库的driver依赖,但是如果项目C依赖了X2,那么项目C仅需要一种数据库的driver。此时X2可以将各种driver声明为可选的,当项目C依赖X2时,仅需显式依赖于其选择的一种driver依赖即可 \ No newline at end of file