笔记-HTTPS握手流程

HTTPS(超文本传输安全协议)的连接流程结合了传输层的TCP三次握手和TLS(传输层安全性协议)的握手过程来提供一个安全的连接。这个过程可以大致分为以下几个步骤:

1. TCP三次握手

这是任何HTTP或HTTPS连接开始的地方。客户端和服务器之间建立一个TCP连接,这是一个可靠的连接,确保了数据包正确无误地到达。

2. TLS握手

在TCP连接成功建立之后,TLS握手过程开始,目的是安全地交换密钥,验证服务器,并且建立加密通道。

a) 客户端Hello(Client Hello)

客户端发送一个“Client Hello”消息到服务器,包含:

  • 客户端支持的TLS版本。
  • 可接受的加密算法(密码套件)列表。
  • 一个客户端随机数(Client Random),之后用于生成加密密钥。

b) 服务器Hello(Server Hello)

服务器响应一个“Server Hello”消息,包含:

  • 选择的TLS版本和密码套件。
  • 一个服务器随机数(Server Random)。
  • 服务器证书(通常是X.509格式)。

c) 服务器证书验证

客户端验证服务器证书的有效性,它可能包括:

  • 确认证书由可信的证书颁发机构(CA)签名。
  • 检查证书的有效期。
  • 确认证书中的域名与正在连接的服务器域名一致。

d) 密钥交换

客户端使用从服务器证书中提取的公钥,发送预主密钥(Pre-Master Secret),这通常是加密的。

e) 会话密钥生成

客户端和服务器都使用客户端随机数、服务器随机数和预主密钥生成相同的会话密钥,这将用于此后的对话加密。

f) 客户端完成(Client Finished)

客户端发送一个“Finished”消息,这通常是之前所有消息的加密和散列值,用于服务器验证。

g) 服务器完成(Server Finished)

服务器同样发送一个“Finished”消息,客户端验证这个消息以确保没有中间人攻击。

3. 加密通信

HTTPS使用的是一种称为公钥基础设施(PKI)的体系结构,在这种体系结构中,确实使用了公钥和私钥机制,但它结合了对称加密和非对称加密的优点来提供安全通信。

  1. 非对称加密
    • 在初始的握手阶段,服务器会向客户端发送其公钥作为数字证书的一部分。
    • 客户端将使用此公钥加密信息(如预主密钥),然后发送回服务器。
    • 只有持有对应私钥的服务器才能解密这些信息,这确保了即使这些信息被截获,没有私钥也无法解密。
    • 这个过程是非对称的,因为加密(公钥)和解密(私钥)使用不同的密钥。
  2. 对称加密
    • 一旦非对称步骤完成,客户端和服务器就会使用交换的信息来生成对称的会话密钥。
    • 后续的通信将使用这个对称密钥进行加密和解密,这意味着客户端和服务器都使用同一个密钥。
    • 对称加密比非对称加密更快,适合大量数据的加密通信。

笔记-TCP的三次握手四次挥手

TCP三次握手例子

假设你的电脑(客户端A)尝试通过TCP连接到一个网站的服务器(服务器B)。服务器的IP地址是192.168.1.1,客户端的IP地址是192.168.1.2,而你想要访问的服务(比如一个网页)是通过标准的HTTP服务端口80提供的。

  1. SYN步骤:
    • 客户端A(192.168.1.2:12345)生成一个随机的初始序列号,比如1000,并向服务器B的80端口发送一个TCP段,其SYN位设置为1。
    • 此时的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.2:12345 目的地址:192.168.1.1:80 Seq=1000 ACK=0 Flags=[SYN]
    • 服务器B接收到这个请求。
  2. SYN-ACK步骤:
    • 服务器B(192.168.1.1:80)收到请求后,会从它自己的端口回应客户端A。它也会生成一个随机的初始序列号,比如5000,并且把确认号设置为客户端A的序列号加1(也就是1001)。
    • 服务器B发送的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.1:80 目的地址:192.168.1.2:12345 Seq=5000 ACK=1001 Flags=[SYN, ACK]
    • 客户端A接收到这个响应。
  3. ACK步骤:
    • 客户端A(192.168.1.2:12345)再次发送一个TCP段给服务器B,ACK位设置为1,序列号为1001(表示客户端A准备好接收服务器B的下一个字节),确认号为5001(表示服务器B发送的数据已被确认,服务器可以发送下一个字节了)。
    • 此时客户端A发送的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.2:12345 目的地址:192.168.1.1:80 Seq=1001 ACK=5001 Flags=[ACK]
    • 服务器B接收到这个确认。

TCP三次握手作用包括:

  1. 同步序列号:由于TCP是基于字节流的,各个数据包的顺序非常重要。三次握手可以同步连接双方的初始序列号,从而保证数据传输的顺序和完整性。
  2. 确认双方的接收和发送能力:三次握手确保了双方都能够接收和发送数据。首次握手确认了服务器的接收能力,第二次握手确认了服务器的发送能力以及客户端的接收能力,最后一次握手则确认了客户端的发送能力。
  3. 防止旧的连接请求造成混乱:网络中可能存在旧的连接请求数据包(因为延迟或重发),这些过时的数据包如果被接收,可能会造成错误的连接建立。三次握手通过确认和序列号机制,使得旧的连接请求不会被错误地接受。
  4. 资源分配:在三次握手过程中,TCP堆栈会分配缓冲区和变量以跟踪连接状态。通过这样的过程,只有在双方都确认连接请求之后,才会完成资源分配,避免了因为未完成连接而造成的资源浪费。

三次握手是对建立TCP连接过程的最小化要求,它可以有效地启动一个双向通信会话,同时避免了一些常见的网络通信问题。如果减少到两次握手,那么就无法解决上述的第三个问题,也就是说,如果网络中存在延迟的连接请求,那么这样的请求可能会导致不必要的连接建立,从而造成混乱。三次握手是在效率和可靠性之间的一个妥协。

四次挥手断开TCP连接的例子:

假设客户端A与服务器B之间建立了一个TCP连接,并且在此期间它们已经交换了一些数据。现在,客户端A决定关闭这个连接。以下是关闭过程的详细步骤和假设的序列号及确认号。

  1. 第一次挥手
    • 客户端A发送一个FIN标志的段给服务器B,指示A已经完成了数据的发送。
    • 假设客户端A之前发送的最后一个数据段的序列号是10000。
    • 客户端A的FIN段的序列号将会是10001(因为TCP协议规定FIN标志的段也消耗一个序列号)。
    • 如果服务器B之前发送给客户端A的最后一个数据段的序列号是5000,那么客户端A发送的ACK确认号将是5001,确认它已经收到了直到序列号5000的所有数据。
    • 段信息如下:cssCopy code源地址:客户端A 目的地址:服务器B Seq=10001 Ack=5001 Flags=[FIN, ACK]
  2. 第二次挥手
    • 服务器B收到客户端A的FIN段,发送一个ACK段回应,确认收到A的结束请求。
    • 服务器B的ACK段的确认号将是客户端A的序列号加1,也就是10002,因为它需要确认收到了包含FIN标志的那个序列号。
    • 此时,服务器B还没有准备好关闭连接或者可能仍有数据需要发送给客户端A。
    • 段信息如下:cssCopy code源地址:服务器B 目的地址:客户端A Seq=服务器B当前的序列号 Ack=10002 Flags=[ACK]
  3. 第三次挥手
    • 服务器B已完成数据的发送,并且准备关闭连接,发送一个FIN标志的段给客户端A。
    • 假设服务器B之前发送的最后一个数据段的序列号是8000。
    • 服务器B发送的FIN段的序列号将会是8001。
    • 服务器B在发送FIN之前也已经收到了客户端A之前的所有数据,所以ACK确认号将是客户端A之前的确认号,也就是10002。
    • 段信息如下:cssCopy code源地址:服务器B 目的地址:客户端A Seq=8001 Ack=10002 Flags=[FIN, ACK]
  4. 第四次挥手
    • 客户端A收到服务器B的FIN段,发送一个ACK段回应,确认收到B的结束请求。
    • 客户端A的ACK段的确认号将是服务器B的序列号加1,也就是8002。
    • 此时客户端A会等待足够的时间确保服务器B收到了这个ACK段,这个时间通常是该连接的往返时延的两倍。
    • 殅信息如下:cssCopy code源地址:客户端A 目的地址:服务器B Seq=10002 Ack=8002 Flags=[ACK]

TCP的四次挥手(Four-way Handshake)断开连接的过程比建立连接的三次握手(Three-way Handshake)要复杂,这主要是因为TCP连接是全双工的,意味着每个方向的通信是独立的。这里是为什么需要四次挥手的一些原因:

  1. 全双工通信:在TCP连接中,数据传输是全双工的,即数据可以在两个方向上同时进行。因此,每个方向都必须单独关闭,这就需要两个FIN标志和两个ACK标志。
  2. 确保数据完全传输:TCP连接的一方(比如客户端)发送FIN标志仅表示它没有更多的数据要发送,但是它仍然可以接收数据。服务器在收到这个FIN之后,可能仍然有数据要发送给客户端。服务器必须确保所有传出的数据都被客户端接收到,然后才能安全地发送它自己的FIN标志来关闭连接的另一个方向。
  3. 防止数据丢失:四次挥手允许每一方确认它们已经接收到对方发送的最后一个包。这意味着如果任何FIN或ACK在传输过程中丢失,连接不会立即关闭,从而允许重传丢失的包,确保连接的可靠终止。
  4. TIME_WAIT状态:客户端发送最后一个ACK后,它还需要等待足够的时间(称为2MSL,Maximum Segment Lifetime)以确保服务器收到了这个ACK。如果服务器没有收到ACK,它将重新发送FIN包。TIME_WAIT状态确保了旧的连接残留的数据包不会在新的连接中出现,从而避免可能的混淆。

总结来说,四次挥手的过程确保了连接的双方都能够完全完成数据传输,并且优雅地关闭连接,没有任何一方突然中断连接导致数据丢失。这也是为什么不能简化为三次或更少次数的原因,因为TCP设计的优先考虑是确保数据传输的可靠性和完整性。

笔记-Java针对字节码的优化

编译器生成字节码时的优化

编译器在生成字节码时可以进行一些优化,以提高代码的执行效率和性能。以下是一些常见的优化技术:

  1. 常量折叠:编译器可以在编译时将常量表达式计算出来,将其结果作为字节码中的常量。这样可以避免在运行时重复计算相同的表达式。
  2. 循环展开:编译器可以将循环展开,将循环体中的代码复制多次以减少循环的迭代次数。这样可以减少循环控制的开销,提高程序的执行速度。
  3. 变量内联:编译器可以将某些函数调用替换为直接使用函数体中的代码。这样可以避免函数调用的开销,减少程序的运行时间。
  4. 公共子表达式消除:编译器可以识别出相同的子表达式,并将其计算结果存储在临时变量中。这样可以避免重复计算相同的表达式,提高程序的执行效率。
  5. 死代码消除:编译器可以识别出不会被执行的代码,将其从生成的字节码中删除。这样可以减少字节码的大小,提高程序的执行效率。
  6. 消除冗余检查:消除不必要的安全检查、空指针检查等。

这些优化技术可以帮助编译器生成更高效的字节码,从而提高代码的执行效率和性能。编译器根据代码的特性和优化策略的选择,可以进行不同程度的优化。

JVM在运行时执行的一些关键优化:

即时编译(JIT)和适应性优化

  • 方法热点探测:JVM监控方法和循环的执行频率,将经常执行的代码(热点代码)识别出来进行优化。
  • 适应性优化:JVM能够根据程序的实际运行情况动态调整优化策略。
  • 多级优化:JVM可能会采用多级别的优化,对不同的代码应用不同程度的优化。

编译器优化

  • 方法内联:将方法调用替换为方法体本身,减少方法调用的开销。
  • 循环优化:包括循环展开、循环不变式外提等技术来减少循环开销。
  • 逃逸分析:分析对象是否在方法或线程外部被引用,如果没有“逃逸”,可能在栈上分配内存,避免堆分配。
  • 消除冗余检查:消除不必要的安全检查、空指针检查等。
  • 公共子表达式消除:如果同一个表达式被多次计算,并且每次计算的结果都相同,JIT编译器会尝试只计算一次。

垃圾回收(GC)优化

  • 分代收集:根据对象的生命周期将堆内存分成几块,如年轻代、老年代等,以不同方式进行垃圾回收。
  • 垃圾回收算法:采用多种算法,如标记-清除、标记-整理、复制算法等,优化GC性能和响应时间。
  • 并发和并行垃圾回收:在多核处理器上并行进行垃圾回收,以及在应用线程运行的同时并发执行垃圾回收。

锁和同步优化

  • 轻量级锁:当锁未竞争时,使用更高效的算法来减少线程阻塞。
  • 锁消除:编译器识别并去除不需要的同步。
  • 锁粗化:如果在一系列操作中多次获取和释放同一个锁,则可能将锁的范围扩展到整个操作序列,减少锁操作的开销。
  • 偏向锁:在单线程访问的情况下降低锁的竞争开销。

运行时性能监控与反馈

  • 动态编译器反馈:JIT编译器可以根据代码的执行情况收集信息,并利用这些信息进行更好的优化。
  • 反优化:在一些情况下,之前做出的优化可能不再有效,JVM可以将已经编译的代码恢复到未优化的状态。

JVM的优化是一个持续的过程,随着程序的运行,JVM会逐渐学习并应用越来越多的优化策略,以期达到最优性能。

笔记-JAVA 基本数据类型和对应封装类思想


Java中的基本数据类型和它们的封装类(有时被称作包装类)之间存在一些关键的不同点,这些设计主要是为了解决基本数据类型在面向对象编程中的局限性。

基本数据类型:

  1. 性能:基本数据类型(如int, double, char等)通常会有更好的性能,因为它们存储的是值,且存储在栈上。
  2. 存储:基本类型直接存储数据,它们不是对象,不具备对象的特性。
  3. 默认值:基本类型有默认值,例如int的默认值为0,boolean的默认值为false,无需显式初始化。
  4. 栈内存:基本类型的变量通常存储在栈内存上,这使得访问速度非常快。

封装类:

  1. 对象特性:封装类(如Integer, Double, Character等)是类,它们包装了基本类型的值并提供了对象的特性。
  2. 堆内存:封装类的实例存储在堆内存中,它们是引用类型。
  3. null值:封装类可以赋值为null,表示这个引用不指向任何对象,而基本数据类型不能赋值为null
  4. 工具方法:封装类提供了一些有用的工具方法,例如转换、比较、解析字符串为相应的类型等。
  5. 集合框架:Java的集合框架(如List, Set, Map等)只能存储对象,不能存储基本类型。这就是封装类存在的主要原因之一。
  6. 泛型:Java的泛型只支持引用类型,不支持基本数据类型。因此,需要使用封装类来使用泛型。
  7. 类型系统的一致性:将基本类型封装为类,让Java的类型系统更加统一,可以将基本类型当做对象处理。

设计原因:

  1. 性能与对象抽象:基本数据类型提供了性能优势,封装类提供了面向对象的抽象。这样的设计允许Java在保持较高性能的同时,又能利用面向对象的特性。
  2. 兼容集合框架:为了能在集合框架中使用基本类型的值,需要一个可以作为Object子类的类型,这就是封装类的作用。
  3. 泛型支持:泛型的引入使得类型安全的集合成为可能,封装类使得基本类型可以在这样的集合中使用。
  4. 自动装箱与拆箱:Java 5引入了自动装箱和拆箱,这使得基本类型和封装类之间的转换自动化,简化了编程。

封装类和基本数据类型之间的转换是自动的,这个特性被称为自动装箱和拆箱。例如,当你把一个int赋值给一个Integer对象时,自动装箱就发生了;反过来,当你把一个Integer赋值给一个int时,就会发生自动拆箱。这种设计让开发者能够在面向对象编程和性能之间找到平衡点。

使用基本数据类型场景:

  1. 性能敏感:如果你正在编写性能敏感的代码,比如在大规模数据处理、科学计算或者实时系统中,基本类型更快,因为它们避免了对象封装的开销。
  2. 默认值:当你需要确保类型总是有一个默认值,比如int默认是0,而不是null时。
  3. 数组处理:处理大量的数值数据时使用基本类型数组(如int[]),这样做可以减少内存消耗并提高处理速度。

使用封装类场景:

  1. 需要对象:当你需要将数字作为对象传递,例如在使用集合框架类ArrayListHashMap时,只能存储对象。
  2. 允许null:如果你需要表示一个变量可能没有值(即null),或者需要在某些业务逻辑中区分0null
  3. 泛型代码:Java的泛型不支持基本数据类型,所以在泛型代码中,例如ArrayList<Integer>,必须使用封装类。
  4. 使用对象方法:封装类提供了多种方法,比如将数字转换为字符串,或者与其他数字类型进行转换等。
  5. 系统编程或API调用:有些系统编程接口或API方法需要对象类型的参数,这时必须使用封装类。
  6. 序列化:只有对象才能被序列化,这样才能通过网络发送或写入文件等。

笔记-UTF-8、UTF-16编码

UTF-8编码方式:

UTF-8使用一至四个字节为每个字符编码,编码规则如下:

  1. 单字节的字符:对于标准的ASCII字符(U+0000至U+007F),UTF-8和ASCII编码是相同的,使用单个字节,其最高位始终为0。
  2. 多字节的字符:对于其他范围内的字符,则使用两个到四个字节表示:
    • 两字节字符:第一个字节的前三位为110,后面五位用于数据;第二个字节的前两位为10,后面六位用于数据。
    • 三字节字符:第一个字节的前四位为1110,后面四位用于数据;随后的字节的前两位为10,后面六位用于数据。
    • 四字节字符:第一个字节的前五位为11110,后面三位用于数据;随后的字节的前两位为10,后面六位用于数据。

每个字节中,以10开头的字节为后续字节,它们不会独立出现,只会跟随一个高位字节。这种编码方法的一个优势是通过查看任意字节,都能够知道它是单独的字符还是某个字符的一部分,因为UTF-8字节序列是顺序无关的,所以不像UTF-16和UTF-32,UTF-8不需要字节顺序标记(BOM)来指示字节的顺序

UTF-16编码方式:

UTF-16(16-bit Unicode Transformation Format)使用单个16位的代码单元来编码U+0000至U+FFFF之间的字符,这部分包括了基本的多语言平面(BMP)。对于更高位的字符,则使用一对16位的代码单元,即32位来编码,这种对称称为代理对(Surrogate Pair)。

  • BMP字符:使用一个16位的代码单元,直接对应于字符的Unicode码点。
  • 代理对:对于超出BMP的字符,使用两个16位的代码单元。第一个代码单元(高代理项)的范围是U+D800至U+DBFF,第二个代码单元(低代理项)的范围是U+DC00至U+DFFF。

UTF-8与UTF-16的区别:

  1. 字节顺序:UTF-8是字节顺序无关的,而UTF-16需要区分字节顺序,因此有UTF-16LE和UTF-16BE两种格式,分别代表小端序和大端序,有时还有带字节顺序标记(BOM)的格式。
  2. 空间效率:对于ASCII字符,UTF-8更为高效,只需要一个字节。而UTF-16不论什么字符至少需要两个字节。但对于很多亚洲语言,UTF-16可能更加空间高效,因为这些语言中的字符在UTF-8中可能需要三个或四个字节。
  3. 编码复杂性:UTF-8是可变长度的,从1到4个字节不等;UTF-16也是可变长度的,但主要是两个长度,要么是2个字节,要么是4个字节(代理对)。
  4. 兼容性:UTF-8编码兼容ASCII,因此被广泛用于网络和文件存储,特别是在主要交流用的是ASCII字符的系统中。UTF-16常用于需要处理大量非BMP字符的应用中,比如某些文本处理软件。

Linux AMD APU 省电降温指南

安装软件

RyzenAdj 命令行控制AMD APU工具

Ryzen Controller 封装RyzenAdj的GUI工具(简单好用,而且支持中文)

同类型的封装RyzenAdj的GUI工具 还有RyzenAdjCtl它可以做到更加细粒度的控制这类工具主要是针对移动端的AMDAPU,对于桌面端的支持不是太好。

博主使用的是桌面端的5600G,测试时发现RyzenAdj只能控制CPU功耗,设置GPU频率不生效。

针对iGPU,可以使用这个工具Radeon Profile,它是Linux下AMD显卡的专用超频工具,虽然它没法对我们的核显超频,但是可以在首页控制核显运行频率来大幅度减少功耗。

RyzenAdj效果

在默认设置时,随便播放一个1080P视频,功耗在50W上下

手动设置功耗墙到10W

播放同样的1080P视频,功耗和温度显著降低,这时候打开任务管理器可以发现CPU占用提高很多,但丝毫不影响视频播放的性能。

Radeon Profile效果

功耗计实测

在没置功耗墙之前,整机待机功耗73W左右(7块硬盘+pcie网卡+pcie转sata卡+4根16G 3200内存),突然负载时功耗能飙到140W,满载功耗也在130W。设置功耗墙为10W后,整机功耗最多也就84W,这时候性能确实拉了,但是日常使用并用作NAS已经是绰绰有余了,非常满意!

JackSon 注解笔记

超全的JackSon注解大全包含demo https://www.tutorialspoint.com/jackson_annotations/index.htm

  • @JsonProperty 属性注解 可以修改序列化别名、默认值、展示效果
  • @JsonView 属性和方法注解 可以在不同视图中显示不同的序列化
  • @JsonCreator 方法注解 控制序列化使用的构造函数
  • @JsonValue 属性注解 控制某属性作为序列化的唯一结果输出
  • @JsonUnwrapped 对象扁平化把子属性对象解构提取到根对象
  • @JsonTypeName 配合@JsonTypeInfo指定序列化时属性对应名称
  • @JsonTypeInfo 解决多态反序列化问题 这里的大佬总结的很好
  • @JsonSubTypes 解决多态序列化时,无法确认子类类型的问题
  • @JsonTypeIdResolver 多态序列化相关 这部分可以看这个大佬的总结
  • @JsonTypeId 指定序列化时的key值
  • @JsonRawValue 是否按照字面量序列化,就是序列化结果是字符”key”:”xxx”还是”key”:xxx
  • @JsonManagedReference, @JsonBackReference 解决循环引用 具体的案例看这里
  • @JsonInclude 可以实现根据条件过滤key或value值 比如隐藏所有null值不序列化
  • @JsonIgnoreType 标记在类上,所有以该类为类型的值都会被忽略
  • @JsonIgnoreProperties 标记在类上,控制该类哪些属性被隐藏,还可以控制遇到json字符串未知参数是否抛出异常 ignoreUnknown
  • @JsonIdentityReference @JsonIdentityInfo 解决循环引用问题 具体demo看这里 @JsonIdentityReference可以让第二次序列化的对象(存在循环引用的对象)序列化为它的id值,@JsonIdentityReference 设alwaysAsId=true会让对象直接序列化为它的id值
  • @JsonGetter 指定序列化时使用的方法,可用于定义非静态、无参数返回值(非 void)方法,用作逻辑属性的“getter”。它可以用作更通用 JsonProperty注释的替代方法(这是一般情况下的推荐选择)。Getter 是指在序列化具有该方法的类的 Object 实例时(可能继承自超类),通过该方法进行调用,并将返回值序列化为该属性的值。
  • @JsonFormat 序列化时指定格式,常用于各种时间格式
  • @JsonFilter 定义一个过滤器的名称 在序列化时根据该名称指定序列化器 具体demo看这里
  • @JsonAnySetter 该注解允许我们把一个可变的map属性作为标准属性, 在反序列过程中, 从Json字符串得到的属性值会加入到map属性中,可以用它来存不存在于类中的属性。
  • @JsonAnyGetter 该注解用于把可变的Map类型属性当做标准属性,和@JsonAnySetter一起使用
  • @JacksonInject 指定属性是注入的而不是从Json字符串中反序列化获取
  • @JsonAutoDetect 可以覆盖可见性,可以指定属性的可见性
  • @JacksonAnnotation,@JacksonAnnotationsInside 属于JackSon的元注解

@JsonProperty

注意:虽然官方文档说 该注解如果和@JsonIgnore一同使用,@JsonIgnore 则优先于此属性。但实际测试用发现如果两个注解同时存在,效果等同 READ_ONLY,序列化都能展示,反序化不能写入。


String value: 指定序列化key
String defaultValue: 指定key的默认值
boolean	required:是否必须,但请注意,从 2.6 开始,此属性仅用于 Creator 属性,以确保 JSON 中存在属性值:对于其他属性(使用 setter 或可变字段注入的属性),不执行验证,也就是说只和@JsonCreator一起使用时生效。
int index: 指定该字段排序
JsonProperty.Access access: 序列化权限控制 有下列四种

  • AUTO 自动确定此属性的读取和/或写入访问权限。
  • READ_ONLY 只能在序列化时读取,而在反序列化期间不能写入(设置)。
  • READ_WRITE 无论可见性规则如何,都将访问该属性以进行序列化(将值写为外部表示)和反序列化(从外部表示中读取值)。
  • WRITE_ONLY 只能被写(set)用于反序列化,而不会被读(get)在序列化时,即该属性的值不包含在序列化中。

序列化权限控制测试代码:MyTest.java

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class MyTest {

    @JsonProperty(access = AUTO)
    public String myA;

    @JsonProperty(access = READ_ONLY)
    public String myB;

    @JsonProperty(access = WRITE_ONLY)
    public String myC;

    @JsonProperty(access = READ_WRITE)
    public String myD;

    public static void main(String[] args) throws JsonProcessingException {
        MyTest myTest = MyTest.builder().myA("A").myB("B").myC("C").myD("D").build();
        ObjectMapper mapper = new ObjectMapper();
        String toJson = mapper.writeValueAsString(myTest);
        System.out.println("=======预期打印======");
        System.out.println("{\"myA\":\"A\",\"myB\":\"B\",\"myC\":\"C\",\"myD\":\"D\"}");
        System.out.println("=======序列化打印=====");
        System.out.println(toJson);
        System.out.println("=======反列化打印=====");
        String fromJson = "{\"myA\":\"A\",\"myB\":\"B\",\"myC\":\"C\",\"myD\":\"D\"}";
        MyTest test = mapper.readValue(fromJson,MyTest.class);
        System.out.println(test);
    }
}
输出结果:
=======预期打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
MyTest(myA=A, myB=B, myC=C, myD=D)
=======序列化打印=====
{"myA":"A","myB":"B","myD":"D"}
=======反列化打印=====
MyTest(myA=A, myB=null, myC=C, myD=D)

@JsonCreator

JackSon默认使用无参构造方法和set方法反序列对象,使用该注解可以指定JaskSon使用指定方法进行反序列化构造对象,可以标注在构造方法和静态工厂方法上。

@JsonView

@JsonView使用方法:

  1.使用接口来声明多个视图 例如下面这个 代码来自baeldung.com

public class Views {
    public static class Public {
    }

    public static class Internal extends Public {
    }
}
public class Item {
 
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

  2.在pojo的属性上指定视图

public class Item {
 
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

  3.在Controller方法上指定视图

@JsonView(Views.Internal.class)
@RequestMapping("/items/internal/{id}")
public Item getItemInternal(@PathVariable int id) {
    return ItemManager.getById(id);
}

4.直接进行序列化或反序列化时可以指定视图

@Test
public void whenUseJsonViewToDeserialize_thenCorrect() 
  throws IOException {
    String json = "{"id":1,"name":"John"}";

    ObjectMapper mapper = new ObjectMapper();
    User user = mapper
      .readerWithView(Views.Public.class)
      .forType(User.class)
      .readValue(json);

    assertEquals(1, user.getId());
    assertEquals("John", user.getName());
}

@JsonValue

@JsonView是jackson json中的一个注解,指定属性后序列化将只使用该属性值作为序列化接口(这个注解只能作用于一个属性上)。

@JsonUnwrapped

@JsonUnwrapped 对象扁平化 如果属性序列化后是一个对象 会将该属性的对象解构提取到根对象中。

@JsonTypeName

用于绑定被注释类所具有的逻辑名称的注释。与JsonTypeInfo(特别是JsonTypeInfo.use()属性)一起使用以建立对象名称和属性之间的关系,反序列化为父类时,用于确定该对象的具体类型,与@JsonSubTypes 直接标注在父类等效。

@JsonTypeInfo

用于多态类型处理

Class<?> defaultImpl 指定默认的反序列化类型,当反序列对象无法映射到现在有的指定类型时会使用它进行反序列化。

JsonTypeInfo.As include 指定类型标识信息的展示方式。
有下列5种可选值 下面的测试结果使用的注解是@JsonTypeInfo(use= JsonTypeInfo.Id.NAME)

PROPERTY 使用单个可配置属性的包含机制,与实际数据(POJO 属性)一起作为单独的元属性包含在内。,这个属性的值由 @JsonTypeInfo注解的property确定,否则就使用不同use情况下的默认值(@class、@c、@type)。

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@type":"MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

EXISTING_PROPERTY 与PROPERTY区别在于,该注解在序列化时不会输出标识符,反序列流程根PROPERTY相同。

EXTERNAL_PROPERTY 只作用于属性上,把子属性的标识符提升到根对象里,具体使用场景没搞明白。

WRAPPER_OBJECT 包裹在一个对象中,相当于在外层创建一个父对象 {标识符:{原本的对象}} 用一个大对象包住

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"MyTest":{"myA":"A","myB":"B","myC":"C","myD":"D"}}

WRAPPER_ARRAY 包裹在一个数组中。

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.WRAPPER_ARRAY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
["MyTest",{"myA":"A","myB":"B","myC":"C","myD":"D"}]

JsonTypeInfo.Id use 指定在序列化时类型标识信息展示的值。
有下列5种可选值

CLASS 意味着使用完全限定的 Java 类名作为类型标识符。

测试结果:可以看到序列化对象多了一个 @class key,而且其值为全限定类名。(若不指定property则默认为@class

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印========{"@class":"com.example.demo.bean.MyTest","myA":"A","myB":B,"myC":"C","myD":"D"}

CUSTOM 意味着键入机制使用自定义处理,可能具有自定义配置。这个注解需结合property属性和@JsonTypeIdResolver一起使用,指定类标识符的值。

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)
实现TypeIdResolver接口 自定义序列化流程

MINIMAL_CLASS 表示使用具有最小路径的 Java 类名作为类型标识符,多了一个”@c“字段,其值为最小路径类名。(若不指定property则默认为@c

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@c":".MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

NAME 表示使用逻辑类型名作为类型信息;然后需要将名称单独解析为实际的具体类型(类),多了一个”@type”字段,其值为类名。(若不指定property则默认为@type

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@type":"MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

NONE 这意味着不包含显式类型元数据,并且键入纯粹是使用上下文信息完成的,可能会增加其他注释。

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"myA":"A","myB":"B","myC":"C","myD":"D"}

String property 指定类标识名称,在include=JsonTypeInfo.As.PROPERTY或use=JsonTypeInfo.Id.CUSTOM生效,其他情况使用默认的识别码名称。
注意:include=JsonTypeInfo.As.PROPERTY和property同时存在有个问题,如果POJO具有相同名称的属性,会出现两个!

@JsonSubTypes

用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,也可以在子类上直接使用@JsonTypeName 实现同等效果。

Sentinel 面板数据空白解决方案

博主最近不小心买了很多吃灰服务器,正好可以拿去Java微服务相关,基于选新不选旧原则,直接开始学习SpringCloud。学习的过程中,遇到过两次sentinel面板数据空白的问题,先说解决方案

1.部署sentinel服务器和部署微服务服务器必须要开放对应端口!!sentinel的实时监控是通过调用机器Adapter获取实时监控的。

2.时间和时区要一致!!!(博主的吃灰服务器主要是欧洲和美国的,但是部署的时候发现,sentinel只有微服务启动的时候才会显示实时监控,一段时间后就不显示了,这就是时间和时区不一致导致的)