ASN1语法与BC库的ASN1 API

  • ASN1和BC库
    • 基础语法
      • 注释
      • 对象标识符
      • 模块结构
    • 数据类型
      • 简单类型
      • 字符串类型
        • 位串类型
        • 字符串类型
      • 容器类型(结构化类型)
      • CHOICE类型
    • 编码规则
      • BER
      • Tagging
      • DER
    • 使用导引
      • 定义你自己的对像

ASN1和BC库

本文为BC库文档《Java Cryptography Tools and Techniques.pdf》一书的附录A的学习笔记

涉及密码学的绝大多数标准都使用ASN.1(抽象语法表示法1),这主要是一种用于描述对象如何编码以进行传输的语言。算法参数,密钥,签名和加密的消息。任何你能想到的,很有可能在某个地方会有ASN.1结构对其进行编码。

基础语法

注释

有行注释和块注释,语法分别如下

行注释

--行内容--

块注释,块注释可以嵌套

/*块内容
*/

对象标识符

ASN1对象标识符(Object Identifiers (OID))是为了允许构造全局唯一的命名空间。一个OID是由一个由点(’ . ')分隔的数字列表组成的,一个OID的结构沿着一条路径,通常被称为弧线,通过一个由3个主要分支扩展的树。这三个主要的分支对应三个机构:ITU-TISOISO/ITU-T。OID弧线的第一个数字代表这三个组织中的一个,其中数字0分配给ITU-T、数字1分配给ISO,数字2分配给联合ISO/ITU-T组织。

​ 三个主要分支机构的分配是根据一个组织最初在ISO/ITU-IT领域的位置进行的,数字0分配给ITU-T,数字1分配给ISO,数字2分配给联合ISO/ITU-T组织。在这之后,空间的划分变得相当随意,因为主弧的所有者会根据需要在下一层分配号码给其他组织。也就是说,尽管创建OID的方式具有明显的任意性质,但这些数字是全局惟一的,并用于为ASN.1模块、数据类型、算法以及您能想到的任何其他东西提供标识。唯一的复杂性是,因为oid基于组织而不是主体,在某些情况下,您会注意到相同的加密算法将被不同的oid引用。

例如,当您使用SHA256withRSA调用Signature.getInstance()时使用的RSA安全算法有一个与之相关联的对象标识符,如下所示:

iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 11

模块结构

ASN.1模块的基本结构如下:

ModuleName { ObjectIdentifier }
DEFINITIONS Tagging TAGS ::=
BEGIN
EXPORTS export_list ;
IMPORTS import_list ;
body
END

ModuleNameObjectIdentifier具有用于标识所描述的模块的值。

例如:

PKIX1Explicit88 { iso(1) identified-organization(3) dod(6) internet(1)security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-explicit(18) }
DEFINITIONS EXPLICIT TAGS ::=
BEGIN
-- EXPORTS ALL --
-- IMPORTS NONE --

因此模块名为PKIX1Explict88,唯一标识为1.3.6.1.5.5.7.0.18

DEFINITIONS EXPLICIT TAGS::=告诉我们标签字段是EXPLICIT,另一种选择是IMPLICIT,我们稍后会看到两者之间的区别。

EXPORTS可以将本模块的内容导出供其他模块使用,IMPORTS可以导入其他模块的内容在本例中,没有EXPORTS关键字(它在注释中),这意味着模块中的任何内容都可以导出到另一个模块。IMPORTS关键字也不见了,而且正如注释所暗示的那样,这意味着不需要导入。

数据类型

ASN.1类型都属于三类:简单类型、字符串类型和结构化类型。字符串类型进一步细分为两个类别,原始位类型和代表特定字符编码的类型。结构化类型表示两种容器类型:对象的有序序列和对象的无序集。这些类型都在包org.bouncycastle.asn1包中定义。

简单类型

  1. BOOLEAN

    表示一个真值或假值。在BC库中由ASN1Boolean类表示,使用ASN1Boolean.TRUEASN1Boolean.FALSE

  2. ENUMERATED

    是INTEGER的一种特殊情况,通常来自受限集。在BC库由ASN1Enumerated类表示。

  3. INTEGER

    一个INTEGER可以用来表示任意大小的整数。注意:这意味着它们是有符号的,并且只有一种编码形式(二补码 two’s-complement)。BC库中整数表示为ASN1Integer

  4. NULL

    显式空值。重要的是不要将NULL与Java null混淆,Java null实际上更接近ASN.1的absent概念,即不存在编码。NULL用真实的编码来表示它。BC库中它由ASN1Null和遗留的DERNull表示。DERNull.INSTANCE提供一个常量值。

  5. OBJECT IDENTIFIER

    我们在上面的对象标识符一节中介绍了这种类型,这是它们的实际类型定义。BC库中它们由ASN1ObjectIdentifier类表示。

  6. UTCTime

    UTCTime用于定义具有两位数年份的协调世界时。一般来说,两位数年份被解释为代表从1950年到2049年的年份,但显然有一些系统使用不同的窗口,所以总是值得检查如何转换这些窗口。UTCTime的最低分辨率是秒。UTCTime由BC库中的ASN1UTCTimeDERUTCTime类表示。

  7. GeneralizedTime

    GeneralizedTime有一个4位数的年,可以用来表示任意精度的秒。UTCTime由BC库中ASN1GeneralizedTimeDERGeneralizedTime类表示。

大多数基本类型只有一种编码,所以无论您使用DER还是更宽松的BER,它们总是以相同的方式编码。

两个例外是 UTCTimeGeneralizedTime。 这两个原语都将时间编码为 ASCII 字符串。 对于 CERDER,在 GeneralizedTime 的情况下,这意味着秒元素将始终存在,并且小数秒(如果存在)将省略尾随零并且字符串将以 Z终止,因此以 UTC/GMT 作为时区。 UTCTime遵循类似的模式。对于CERDER, UTCTime值被编码为总是存在的秒,并以Z结束。因为在一段数据中编码原语是非常容易的,这些原语可能最终会被用于签名中,如果签名验证者可能强制DER编码,那么在验证时就会出现意想不到的失败。

字符串类型

字符串类型又进一步分为两类。位串(Bit string)类型和字符串(character string)类型。

位串类型

有两种位串类型:

  1. BIT STRING

    BIT STRING允许存储任意长的位串。它们被编码为两部分,第一部分是填充位的数量,后面是组成实际位串的字节串。在DER编码中,填充位都应该为零。BIT STRING由BC库中的ASN1BitStringDERBitString表示。

  2. OCTET STRING

    OCTET STRING允许存储任意的字节串(对于更现代的人来说,是字节数组)。BC库中OCTET STRINGASN1OctetStringBEROctetStringDEROctetString表示。还有用于BER OCTET字符串处理的流类——BEROctetStringGeneratorBEROctetStringParser

字符串类型

ASN.1支持多种字符串类型。其中一些字符串类型表示已经退役的字符集,所以您很少会看到它们(VideotexStringTeletex是这方面的两个最好的例子),其他如PrintableStringIA5StringBMPStringUTF8String可能是最常见的。与位字符串类型一样,所有这些都允许存储任意长的字符串,但在Bouncy Castle的实践中,我们只发现了需要DER和直接长度(direct-length)编码的情况。

  1. BMPString、

    BMPString由BC中DERBMPString类表示。它的名字来自于Basic Multilingual Plane——一个包含所有与现存语言相关的字符的结构。该字符集由iso10646表示,与Unicode表示的字符集相同。

  2. GeneralString

    GeneralStringDERGeneralString类表示。它可以包含iso2375所述的国际编码字符集登记册所述的任何字符,包括控制字符。

  3. GraphicString

    GraphicStringDERGraphicString类表示。此字符串类型派生自与GeneralString相同的字符集,但不包含控制字符。只允许打印字符。

  4. IA5String

    IA5String是由来自国际字母表5(International Alphabet 5)的字符组成的,这是一个老的ITU-T提案。现在,它们被认为涵盖了整个ASCII字符集。该类型由DERIA5String类表示。

  5. NumericString

    数字字符串只能包含数字0 ~ 9和空格字符。它由DERNumericString类表示。

  6. PrintableString

    PrintableStringDERPrintableString类表示。PrintableStringASCII子集中提取它的字符集:字母a~Z, A~Z, 0到9,以及附加的字符: 空格、单引号、圆括号、+、-、点号、冒号、=、?、/、逗号。

  7. TeletexString

    最初称为T61String,由DERT61String类表示。它可能很难正确解释,因为虽然它是一个8位字符类型,但它支持使用以ASCII ESC(转义字符)开头的字符序列来更改字符集。

  8. UniversalString

    UniversalStringDERUniversalString类表示。这个字符串类型是为了国际化而添加的。默认情况下,Bouncy Castle会将这些字符串转换为显示字符编码的十六进制字符串。

  9. UTF8String

    UTF8String表示Universal Transformation Format, 8 bit,是完全国际化的推荐字符串类型。它由DERUTF8String类表示,现在使用非常广泛。

  10. VideotexString

    顾名思义,VideotexString是为与视频文本系统一起使用而设计的,并适应可以使用控制代码构建简单图像的8位字符。

  11. VisibleString

    VisibleStringDERVisibleString类表示。最初,这种类型只包含来自ISO 646的字符,但自1994年以来,它被解释为包含没有控制字符的纯ASCII字符。

容器类型(结构化类型)

ASN.1中有两种容器类型—SETSEQUENCE。这些在BC ASN.1 API中使用ASN1SetASN1Sequence类 在最顶层进行支持。

ASN1SetASN1Sequence类都是抽象类,它们都有提示特定编码风格的实现类。·ASN.1 SEQUENCE·结构是有序的,因此编码风格的选择不会影响编码中元素的顺序。另一方面,ASN.1 SET 结构不是有序的,而是为了满足 DER 的要求,将元素按照其编码的数值排序(通过对元素进行编码然后将其转换为一个 添加足够的尾随零以使每个编码具有相同长度后的无符号整数)。

CHOICE类型

​ CHOICE是一种特殊类型,它表示ASN.1字段或变量将是一组可能的ASN.1类型或结构之一。在Java中并没有类似的东西,但与C中联合相识。

​ Bouncy Castle提供了一个标记接口ASN1Choice,可以在表示CHOICE类型的ASN.1对象上实现。这个标记接口应该在可以使用的地方使用,因为它有助于提醒Bouncy Castle编码器对CHOICE强制执行正确的编码规则(下面有更多介绍)。

编码规则

ASN.1支持一系列不同的编码方式,从精心设计的无歧义且大小最小的二进制格式到使用XML的通用编码方式。从目前在Bouncy Castle所做的事情来看,有三组编码规则是最相关的:基本编码规则(Basic Encoding Rules BER),区别编码规则(Distinguished Encoding Rules DER)和规范编码规则(Canonical Encoding Rules CER)。

BER

ASN.1编码系统的核心是围绕基本编码规则(Basic encoding Rules, BER)建立的。BER编码遵循TLV(tag-length-value 标签-值长度-值内容)约定。该类型使用tag来标识,接下来是给出内容长度的值,然后是描述内容的字节串。DER和CER都是BER的子集。

BER编码提供了三种编码ASN.1结构或原语的方法:

  1. Primitive definite-length

    在这种情况下,整个原语用单个块描述—类型标记tag 后面的长度和数据告诉你关于编码结构中的大小和数据所需的一切。

  2. Constructed definite-length

    construct definet -length既用于表示包含多个原语(可能自己构造)的 ASN.1 结构,也用于表示被定义为原语定长编码列表的集合的原语。 在其中之一的情况下,只能通过读取构成构造编码的所有原始编码来确定数据的实际大小以及构成它的字节。

  3. Constructed indefinite-length

    Constructed indefinite-length也由其他编码组成——可能是不定长本身。 它由长度为 0x80 的八位字节表示,并以由两个值为 0x00 的八位字节组成的内容结束标记终止。 这种编码出现很多 - 它是唯一可以处理可能大于内存资源数据的方法,或者其他考虑因素使得无法计算出编码数据最初可能有多长。

Tagging

​ 作为 TLV格式,BER 具有一组标准的预定义标签,用于常见原语和结构化类型。 BER还允许你为容器类型定义自己的标签值。不过,在定义自己的标签值时,有几个不同之处。

​ 第一个变化是ASN.1对象可以标记为 EXPLICIT,这意味着您的标记值和对象的原始标记值将包含在编码中,或者ASN.1 对象可以标记为 IMPLICIT,这意味着您的标记值替换了对象在编码中的原始标记值。 当然,随着原始标签值丢失,如果您不知道原始标签值是什么并且手头没有任何文档告诉您它应该是什么,您可能会问自己如何正确解释编码。 可悲的是,这是无法判断。 这对于结构化类型变得尤为重要,因为显式标记的原语将生成与包含单个原语的隐式标记的 SET 或 SEQUENCE 相同的编码。

Bouncy Castle ASN.1 库中的所有标准 API 类型都支持静态 getInstance() 方法,该方法采用 ASN1TaggedObject指示隐式或显式标记的布尔值。 由于 IMPLICIT 和结构化类型的含糊不清,因此在解释 ASN1TaggedObject 类型的对象时使用这些方法(带指示标记)非常重要,并确保使用与使用的标记类型相关的布尔值的正确值。

​ 第二个变化是 ASN.1 对象是 CHOICE。 对于 CHOICE 类型,不能丢失与 CHOICE 值相关的任何标签,因为这是了解 CHOICE 值真正代表什么的唯一线索。

​ Bouncy Castle 提供了一个标记接口 ASN1Choice,它可以在 ASN.1 对象上实现,以提醒 Bouncy Castle 编码器强制执行正确的编码规则。 我们建议您在定义代表 ASN.1 CHOICE 的对象时使用它。

DER

​ 由于提供了结构化的基本类型、无序集等,可能有几种方法可以对给定的ASN.1结构进行编码。DER旨在确保相同的ASN.1数据总是产生相同的编码。

​ 这对于签名数据或MAC数据尤其重要,因为验证签名ASN.1数据的能力依赖于验证方能够准确地重新创建已签名数据的编码的能力。

​ 为了确保这种能力,DER对BER增加了以下限制:

  1. 只允许固定长度(definite-length)编码。
  2. 只有结构化类型 SEQUENCESETIMPLICIT 标记的 SEQUENCESETEXPLICIT标记的对象可以使用构造的定长对象。
  3. 对数据长度的编码必须始终以最小字节数进行编码(不允许前导零)。
  4. 被设置为DEFAULT值的字段不包含在编码中。
  5. SET中包含的对象在编码前按调整后的编码值排序(在必要的地方附加0以使所有编码长度相同)。

注意:最后两个限制也适用于CER。

DER SET是有序的,ASN.1 SET本身是无序的。除DER外,编码不依赖于SET中的特定顺序。

使用导引

​ 在面向对象的语言中使用像 ASN.1 这样的协议层的困难之一是有时不清楚在何处处理 ASN.1 结构是安全的。例如是该将AlgorithmIdentifier作为对象org.bouncycastle.asn1.x509.AlgorithmIdentifier,还是 作为其原语组成SEQUENCE的结构(Java 中的 org.bouncycastle.asn1.ASN1Sequence)。 在这种情况下,转换实际上并不能很好地工作 - 脱离网络的对象通常是原始组合,但从应用程序中的高级函数传递的对象往往是引用特定标准模块中类型的实际对象。 如果以意外方式使用提供的方法,则强制转换可能很容易导致 ClassCastException。 可以使用简单的构造函数,但可能会导致不必要地创建许多对象并增加复杂性。

​ 在 Bouncy Castle 中,这个问题通过使用静态 getInstance() 方法来解决,这使得强制转换变得不必要。 例如,AlgorithmIdentifier.getInstance() 可以传递一个 AlgorithmIdentifier对象或一个 ASN1Sequence 对象,该对象表示一个 AlgorithmIdentifier ,并且如果传入一个 null 值将始终返回一个 AlgorithmIdentifiernull。同样 ASN1Sequence.getInstance()可以传递一个 AlgorithmIdentifier 对象 或 ASN1Sequence 对象,如果传入 null 值,将始终返回 ASN1Sequence或 null。

定义你自己的对像

​ 通常,如果您正在定义基于 ASN.1 的对象,您应该定义一个继承 org.bouncycastle.asn1.ASN1Object的类。 这将提供equals()hashCode() 定义,并且需要在扩展类中定义 toASN1Primitive()方法。 之后,如果您希望遵循现有模式(我们强烈推荐),您应该定义一个静态 getInstance(Object) 方法,该方法能够接受任意类类型(toASN1Primitive() 返回的任何内容)或 null

下面的示例代码提供了ASN.1结构的类定义:

SimpleStructure ::= SEQUENCE {version INTEGER DEFAULT 0,created GeneralizedTime,data OCTET STRING,comment [0] UTF8String OPTIONAL
}

可以使用一下Java代码来定义:

/*** SimpleStructure的实现,一个示例ASN.1对象 (tagging IMPLICIT).* * SimpleStructure ::= SEQUENCE {*       version INTEGER DEFAULT 0,*         created GeneralizedTime,*       data OCTET STRING,*         comment [0] UTF8String OPTIONAL* }*/
public class SimpleStructure extends ASN1Object {//主要的属性都为final,表示这是一个不可变的类,创建了就不可修改private final BigInteger version;private final Date created;private final byte[] data;private String comment;/*** 将传入的对象转换或强制转换为SimpleStructure。** @param obj 待转换的对象* @return  SimpleStructure对象*/public static SimpleStructure getInstance(Object obj) {//如果其本身就是一个SimpleStructure,直接返回这个对象if (obj instanceof SimpleStructure) {return (SimpleStructure) obj;} //如果是其他类型且不为空,则转为ASN1Sequence(它可以表示任何结构类型)//后再从ASN1Sequence中取出需要的数据项else if (obj != null) {return new SimpleStructure(ASN1Sequence.getInstance(obj));}return null;}/*** 创建一个具有默认版本的结构。** @param created 创建时间* @param data  需要包含的编码过的数据*/public SimpleStructure(Date created, byte[] data) {this(0, created, data, null);}/*** 创建一个具有默认版本和可选注释的结构。** @param created 创建时间* @param data   编码过的数据* @param comment 注释*/public SimpleStructure(Date created, byte[] data, String comment) {this(0, created, data, comment);}/*** 创建具有特定版本和可选注释的结构。** @param version 版本* @param created 创建时间* @param data    编码过的数据* @param comment 注释*/public SimpleStructure(int version, Date created,byte[] data, String comment) {this.version = BigInteger.valueOf(version);this.created = new Date(created.getTime());this.data = Arrays.clone(data);if (comment != null) {this.comment = comment;} else {this.comment = null;}}// BC希望用户养成使用getInstance()的习惯。它是安全的private SimpleStructure(ASN1Sequence seq) {int index = 0;//逐个从ASN1Sequence 中取出里面的各项数据,看是否是指定类型if (seq.getObjectAt(0) instanceof ASN1Integer) {this.version = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();index++;} //如果不是则赋值默认值else {this.version = BigInteger.ZERO;}//解析时间值try {this.created = ASN1GeneralizedTime.getInstance(seq.getObjectAt(index++)).getDate();} catch (ParseException e) {throw new IllegalArgumentException("exception parsing created: " + e.getMessage(), e);}//获取字节数组数据this.data = Arrays.clone(ASN1OctetString.getInstance(seq.getObjectAt(index++)).getOctets());//comment是可选的,从ASN1Sequence剩下的数据项中尝试拿到commentfor (int i = index; i != seq.size(); i++) {ASN1TaggedObject t = ASN1TaggedObject.getInstance(seq.getObjectAt(i));//拿到tag number为0的(根据ASN1定义的信息)if (t.getTagNo() == 0) {comment = DERUTF8String.getInstance(t, false).getString();}}}//不可变的对线可以直接返回,可变的要拷贝返回新的对象public BigInteger getVersion() {return version;}public Date getCreated()throws ParseException {return new Date(created.getTime());}public byte[] getData() {return Arrays.clone(data);}public String getComment() {return comment;}/*** 生成对象的DER表示。** @return 由DER原语组成的ASN1Primitive.*/@Overridepublic ASN1Primitive toASN1Primitive() {//存放各属性值的容器ASN1EncodableVector v = new ASN1EncodableVector();//按照ASN1类型将Java字段值封装为对应类型的Java对象// DER编码规则规定,具有指定的DEFAULT值的字段不包含在编码中if (!version.equals(BigInteger.ZERO)) {v.add(new ASN1Integer(version));}v.add(new DERGeneralizedTime(created));v.add(new DEROctetString(data));if (comment != null) {v.add(new DERTaggedObject(false, 0, new DERUTF8String(comment)));}return new DERSequence(v);}
}

​ 该类遵循标准模式。它扩展了ASN1Object,提供了一个getInstance()方法,还考虑了DER编码规则(version字段只有在它不是默认值时才进行编码)。getInstance()方法能够将其大部分工作外包给

ASN1Sequence. getInstance()和接受ASN1Sequence的构造函数被标记为private,因此任何使用它的尝试都必须通过SimpleStructure.getInstance()方法。

​ 需要注意的另一件事是类是不可变的。这里需要注意的主要事情是,与几乎所有ASN.1对象不同,ASN1OctetString不是不可变的——主要是出于性能原因。这就是为什么在getOctets()方法返回的byte[]上调用Arrays.clone()方法的原因。理想情况下ASN1Object同时提供了equals()hashCode()ASN1Object应该是不可变的!

这个例子我们自定义了一个SEQUENCE类型的ASN1对象。如前在容器类型中所述,BC提供了ASN1Sequence类做顶层支持,该类是ASN1结构在Java中的一个标准的映射(其中的各个属性只能使用getObjectAt方法访问)。我们在自定义一个Java的POJO类来映射我们定义的ASN1结构的对象时,可以使用ASN1Sequence的对象作为桥梁来将数据对象在Java标准类型与ASN1标准类型之间进行转换----先用ASN1Sequence的getInstance方法将一个传进来的obj(通常为一个ASN1Primitive的子类,或ASN1Encodable的子类,ASN1Encodable可以调用toASN1Primitive获取ASN1Primitive对象)转为ASN1Sequence对象。

当然,DER支持的SET类型也能类似地使用ASN1Set类做支持。

ASN1语法与BC库的ASN1 API 使用BC库自定义ASN1类型相关推荐

  1. 百度地图API详解之自定义地图类型

    个人博客原文地址:http://www.jiazhengblog.com/blog/2011/10/08/422/ 今天的文章主要介绍如何利用地图API实现自定义地图. 百度地图API目前默认支持两种 ...

  2. 百度地图api设置html5,百度地图API详解之自定义地图类型

    今天的文章主要介绍如何利用地图API实现自定义地图. 百度地图API目前默认支持两种地图类型(map type):普通图和三维图,它们分别通过常量BMAP_NORMAL_MAP和BMAP_PERSPE ...

  3. [GO语言基础] 二.编译运行、语法规范、注释转义及API标准库知识普及

    作为网络安全初学者,会遇到采用Go语言开发的恶意样本.因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识:另一方面是分享与读者,希望大家一起进步.前文介绍了什么是GO语言 ...

  4. c运行库、c标准库、windows API的区别和联系

    c运行库.c标准库.windows API的区别和联系 C运行时库函数 C运行时库函数是指C语言本身支持的一些基本函数,通常是汇编直接实现的.    API函数 API函数是操作系统为方便用户设计应用 ...

  5. c运行库、c标准库、windows API都是什么玩意

    c运行库.c标准库.windows API都是什么玩意 2012-11-28 14:37 768人阅读 评论(2) 收藏 举报 C运行库和C标准库的关系 C标准库,顾名思义既然是标准,就是由标准组织制 ...

  6. python的api库_python 利用toapi库自动生成api

    在学习做接口测试自动化的时候,我们往往会自己动手写一些简单的API,比如写一个简单的TODO API之类. 不过自己写API的时候经常需要造一些假数据,以及处理分页逻辑,开始的时候还觉得比较有意思,但 ...

  7. [sharepoint]rest api文档库文件上传,下载,拷贝,剪切,删除文件,创建文件夹,修改文件夹属性,删除文件夹,获取文档列表...

    写在前面 最近对文档库的知识点进行了整理,也就有了这篇文章,当时查找这些接口,并用在实践中,确实废了一些功夫,也为了让更多的人走更少的弯路. 系列文章 sharepoint环境安装过程中几点需要注意的 ...

  8. CondenserDotNet - 使用 Kestrel 和 Consul 的 API 反向代理库!

    简介 CondenserDotNet - 使用 Kestrel 和 Consul 的 API 反向代理库! 特点 •Consul 客户端库,包括服务注册.发现和配置•反向代理•交互式 UI,用于查看有 ...

  9. eclipse 中文_谁说API必须用英文?中文API的Java库可以有!

    是不是看惯了文档里的英文接口(API),也在 IDE 里看惯了自动补全里的英文接口? 现今的绝大多数 API 的确是英文命名没错,但绝非不能或者不应该实现和发布中文 API 的库. 这里用一个简单的汉 ...

最新文章

  1. Spring Boot中使用LDAP来统一管理用户信息
  2. 小米知识图谱团队斩获CCKS 2020实体链指比赛冠军
  3. 2010年06月12日
  4. php动态添加查询,php动态添加url查询参数的方法,php动态url参数_PHP教程
  5. PRD文档编写与规范
  6. 详解iPhone开发之Objective-C和 C 混编
  7. 「luogu2414」[NOI2011]阿狸的打字机
  8. 尼尔机械纪元机器人驱动_中国的工业机器人发展到了哪一步?
  9. python学习笔记10-匿名函数lambda
  10. 微软发布面向企业区块链网络的Coco Framework
  11. 自然语言处理中的Attention Model原理介绍
  12. Gym 101246G Revolutionary Roads
  13. Helix QAC软件下载安装使用试用
  14. int类型和String类型相互转换
  15. Vue 组件化通信 provide inject ,dispatch ,boardcast
  16. 真无线蓝牙耳机排名前十的品牌,公认佩戴舒适性好的蓝牙耳机分享
  17. cuda、Nvidia driver、GCC版本对应关系
  18. win10 安装kali子系统
  19. 2012网页服务器搭建教程,服务器2012搭建vps教程
  20. 物联网无线通讯wifi模块AP和STA模式分别是什么意思?

热门文章

  1. Spring Security 5.x+OAuth2.0+OIDC1.0
  2. java实现保存合同模板_Java中常用到的文件操作那些事(一)——替换doc文档模板,生成真实合同案例...
  3. 【抽样理论】有偏抽样和生存者佯谬
  4. 985计算机报录比,985211院校报录比大汇总!
  5. 单机触摸屏翻书系统网络广告机
  6. 日语教程 早安日语 名古屋日语 日语mp3 日语教程word版
  7. 根据TXT文件的内容重命名图片——以百度街景为例
  8. Windows命令提示符大全
  9. 用JS向HTML文本框输入,淘汰赛JS - 双向绑定多个输入(硬编码的HTML输入文本框),并得到JSON阵列...
  10. “风神”扫荡,暴雨成灾