开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

这才是你需要的C语言、C++学习路线!

发表于 2020-02-29

小伙伴们,大家好。

关于C语言和C++的学习路线终于梳理完了。当然我也只能从我曾经近3年的通信公司后台开发经历和目之所及的世界,跟大家聊聊这个话题。

这块的东西很多很杂,不过梳理和总结之后,回过头来看,其实学习思路、学习路线应该还是比较清晰的。由于个人认知有限,不足的地方也欢迎大家评论里补充。


先聊几个有趣的问题

这几个问题都是私信里常被问到的,也是我当时学习过程中的一些疑惑。

问: 为啥我学完了C语言或者C++,却还是啥东西也做不出来?

答: 编程语言学完了就能做出东西那也真是天才哇!应该说语言学得就算再精通,它其实也只代表完成了“最小的”那一部分,和实际上手干活之间还是有一个非常大的鸿沟,这个鸿沟就表现为下文即将详述的 编程基础四大件 + 应用实践编程。

问: 为什么C/C++写出来的东西都是运行于黑乎乎的命令行?这玩意真有用吗?

答: 嘿嘿,谁说黑乎乎的命令行里运行的程序就没有用!咱大名鼎鼎的Linux系统都以命令行跟用户交互呢,而且Linux里面很多强大的工具都是运行于黑乎乎的命令行!

问: 从技术学习和实际运用的角度来看,C/C++和Java到底区别在哪?

答: C/C++,它和Java确实不太一样。C语言和C++,尤其C++,语言粒度细、机制多,性能虽然高,但语言本身的包袱也确实重,我们更愿意称它“造轮子”的语言!也正是因为C语言和C++性能好、粒度细,所以什么都能做。而Java本身就是一种服务于互联网软件开发(后端开发+客户端开发)的语言,它有一个明显的“生态圈”的概念,所以应用领域非常清晰。我个人觉得C语言和C++编程比Java还是要难一些,Java毕竟是纯应用层的,C语言和C++则对程序员能力的要求要更高一些。


岗位分析

了解一下岗位,知道以后能做什么,这个也有利于自己树立学习目标。

C语言和C++属于“造轮子”语言,几乎什么都能做。不过一般来说,C语言和C++主要还是做后台(服务端)开发比较多,包括:

  • 通信公司后台开发
  • 互联网公司后台开发
  • 游戏公司后台开发
  • ……

当然这个后台开发具体职责又有很多细分,比如:

  • 有做数据处理和分析的
  • 有做基础协议和通信的
  • 有做服务端底层应用优化的
  • 甚至还有做后台系统驱动和内核的
  • ……

不管怎样,下面即将要介绍的这些学习路线和内容适用于以上所有情况。所以呢,下面就来讲讲具体的学习路线。


语言本身的学习

C语言:

  • 除了最最基础的语法:变量、条件、循环、字符串、数组、函数、结构体等之外
  • C语言最最最最最重要的那就是:指针、内存管理,以后企业里开发就靠它俩吃饭,这也是检验C语言掌握情况的两大标准

推荐书籍:《C Primer Plus》、《C和指针》、《C专家编程》

C++:

  • C++和C语言确实是不同的语言,但是C++确实是对C语言的延伸,可以理解为在C语言里加入了面向对象的特性。因为只有面向对象特性的加持,代码才能更好的复用、扩展和工程化,这是大型项目的必备要素
  • 除了C语言所有的底子之外,还需要学习C++的面向对象(封装、继承与多态)特性、泛型、模板、STL等等

推荐书籍(有先后顺序):《C++ Primer》、《Effective C++》、《C++ 标准程序库》、《STL源码剖析》《深度探索C++对象模型》

最后一个小建议是:语言部分的学习建议不要拖太久,一定要规划好时间,一鼓作气,高强度给它压下来,否则容易把自己搞泄气。


编程基础四大件

基础四大件包括:数据结构和算法、计算机网络、操作系统、设计模式

这跟学什么编程语言、后续从事什么编程方向均无关,只要做编程开发,这四个计算机基础就无法避开。可以这么说,这基础四大件真的比编程语言重要!!!

1、数据结构和算法

可以说这个直接决定了面试的成败!几种基础数据结构类型得烂熟于心,比如:字符串、链表、二叉树、堆、栈、队列、哈希等;基本的几大算法也要了如指掌,比如查找、排序、动态规划、分治等等。

建议LeetCode多刷题。

参考资料: 《大话数据结构》、《算法》、《剑指offer》《LeetCode刷题》

2、计算机网络

此处的计算机网络指的就是TCP/IP协议栈,可以说它是当下互联网通信的基石,无论如何一定要对TCP/IP的协议栈了如指掌,主要就是学习和掌握原理,包括:ARP协议、IP协议、ICMP协议、TCP和UDP协议、DNS协议、HTTP协议、HTTPS协议。

推荐书籍: 《TCP/IP详解》

3、操作系统

该部分重点包括:进程和线程的相关原理(原子性、并发、锁)、内存相关原理(内存分布、内存调度)

推荐书籍:《深入理解计算机系统》

4、设计模式

倒不需要23种设计模式全部记住,常见的几个如:单例模式、工厂模式、代理模式、策略模式、模板方法模式建议熟练于心。

推荐书籍:《大话设计模式》、《设计模式之禅》


应用实践编程

这部分会涉及到一些工具、编程环境、和具体编程实践,应该说这一部分学完,自己应该能做点东西出来、或者说能看懂一些实际项目的代码。

实践这部分,我也是结合我之前在通信公司的实际工作经历和所听所见,来聊一聊。

1、Linux操作系统的使用

很多人初学C语言、C++(包括我)都是在Windows环境上进行的,而实际企业级开发几乎不可能,所以Linux系统必须要会,我们别无选择。先谈使用。

包括:常见的Linux操作命令、基本的Shell编程。

推荐书籍:《鸟哥的Linux私房菜》

2、编译/调试工具

首先是跟编译相关的:编译工具!

我们知道很多人学C语言、C++都在类似Visual Studio这种集成IDE里进行代码编译,这个其实也用了编译器,只不过是微软自家的MS编译器,而且所有操作均可视化。而企业里开发很少会基于Windows系统,所以Linux平台上的编译器更为重要,最典型的当属GCC,甚至有些公司有自己定制过的交叉编译工具,但没关系,只要GCC熟悉,其他问题都不大。

其次,大家自学C语言、C++,都借助类似VS这种IDE,点按钮即可对源文件编译。而企业里实际项目的编译动作叫make,编译的实际动作和过程都是写在makefile文件里,所以makefile的书写规则建议学习!

最后说到调试,Linux平台上的GDB调试工具要熟练使用,会借助于它进行调试。

推荐资料: 这部分没有书可推荐,英文好的同学可以直接看GNU官网关于GCC和GDB的文档,中文材料可以看:《debuging with gdb》(中文版)和陈皓先生的《跟我一起写makefile》

3、Linux环境编程

还是那句话,企业里C语言和C++几乎都是基于Linux平台的,这一部分我们没办法避开

(1)Linux系统编程

众所周知,Windows系统编程里有各种各样的Windows API,同理Linux系统API的使用就更加重要。

(2)多线程编程

此处指的是多线程编程实践相关的东西,一般包括:线程、资源、信号、同步、互斥、锁等等一些具体的编程方法。

(3)网络编程

此处的网络编程主要指的是具体Linux系统上的网络编程API和IO函数的编程实践。

推荐书籍:这三部分综合在一起,推荐必看书籍包括《Unix环境高级编程》、《Linux高性能服务器编程》、《POSIX多线程程序设计》


每天进步一点点,Peace!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

写给大忙人看的操作系统

发表于 2020-02-28

文章主要结构图如下

操作系统

现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入/输出设备构成。

然而,程序员不会直接和这些硬件打交道,而且每位程序员不可能会掌握所有计算机系统的细节,这样我们就不用再编写代码了,所以在硬件的基础之上,计算机安装了一层软件,这层软件能够通过响应用户输入的指令达到控制硬件的效果,从而满足用户需求,这种软件称之为 操作系统,它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型。

我们一般常见的操作系统主要有 Windows、Linux、FreeBSD 或 OS X ,这种带有图形界面的操作系统被称为 图形用户界面(Graphical User Interface, GUI),而基于文本、命令行的通常称为 Shell。下面是我们所要探讨的操作系统的部件

这是一个操作系统的简化图,最下面的是硬件,硬件包括芯片、电路板、磁盘、键盘、显示器等我们上面提到的设备,在硬件之上是软件。大部分计算机有两种运行模式:内核态 和 用户态,软件中最基础的部分是操作系统,它运行在 内核态 中,内核态也称为 管态 和 核心态,它们都是操作系统的运行状态,只不过是不同的叫法而已。操作系统具有硬件的访问权,可以执行机器能够运行的任何指令。软件的其余部分运行在 用户态 下。

用户接口程序(shell 或者 GUI)处于用户态中,并且它们位于用户态的最低层,允许用户运行其他程序,例如 Web 浏览器、电子邮件阅读器、音乐播放器等。而且,越靠近用户态的应用程序越容易编写,如果你不喜欢某个电子邮件阅读器你可以重新写一个或者换一个,但你不能自行写一个操作系统或者是中断处理程序。这个程序由硬件保护,防止外部对其进行修改。

计算机硬件简介

操作系统与运行操作系统的内核硬件关系密切。操作系统扩展了计算机指令集并管理计算机的资源。因此,操作系统因此必须足够了解硬件的运行,这里我们先简要介绍一下现代计算机中的计算机硬件。

从概念上来看,一台简单的个人电脑可以被抽象为上面这种相似的模型,CPU、内存、I/O 设备都和总线串联起来并通过总线与其他设备进行通信。现代操作系统有着更为复杂的结构,会设计很多条总线,我们稍后会看到。暂时来讲,这个模型能够满足我们的讨论。

CPU

CPU 是计算机的大脑,它主要和内存进行交互,从内存中提取指令并执行它。一个 CPU 的执行周期是从内存中提取第一条指令、解码并决定它的类型和操作数,执行,然后再提取、解码执行后续的指令。重复该循环直到程序运行完毕。

每个 CPU 都有一组可以执行的特定指令集。因此,x86 的 CPU 不能执行 ARM 的程序并且 ARM 的 CPU 也不能执行 x86 的程序。由于访问内存获取执行或数据要比执行指令花费的时间长,因此所有的 CPU 内部都会包含一些寄存器来保存关键变量和临时结果。因此,在指令集中通常会有一些指令用于把关键字从内存中加载到寄存器中,以及把关键字从寄存器存入到内存中。还有一些其他的指令会把来自寄存器和内存的操作数进行组合,例如 add 操作就会把两个操作数相加并把结果保存到内存中。

除了用于保存变量和临时结果的通用寄存器外,大多数计算机还具有几个特殊的寄存器,这些寄存器对于程序员是可见的。其中之一就是 程序计数器(program counter),程序计数器会指示下一条需要从内存提取指令的地址。提取指令后,程序计数器将更新为下一条需要提取的地址。

另一个寄存器是 堆栈指针(stack pointer),它指向内存中当前栈的顶端。堆栈指针会包含输入过程中的有关参数、局部变量以及没有保存在寄存器中的临时变量。

还有一个寄存器是 PSW(Program Status Word) 程序状态字寄存器,这个寄存器是由操作系统维护的8个字节(64位) long 类型的数据集合。它会跟踪当前系统的状态。除非发生系统结束,否则我们可以忽略 PSW 。用户程序通常可以读取整个PSW,但通常只能写入其某些字段。PSW 在系统调用和 I / O 中起着重要作用。

操作系统必须了解所有的寄存器。在时间多路复用(time multiplexing) 的 CPU 中,操作系统往往停止运行一个程序转而运行另外一个。每次当操作系统停止运行一个程序时,操作系统会保存所有寄存器的值,以便于后续重新运行该程序。

为了提升性能, CPU 设计人员早就放弃了同时去读取、解码和执行一条简单的指令。许多现代的 CPU 都具有同时读取多条指令的机制。例如,一个 CPU 可能会有单独访问、解码和执行单元,所以,当 CPU 执行第 N 条指令时,还可以对 N + 1 条指令解码,还可以读取 N + 2 条指令。像这样的组织形式被称为 流水线(pipeline),

比流水线更先进的设计是 超标量(superscalar)CPU,下面是超标量 CPU 的设计

在上面这个设计中,存在多个执行单元,例如,一个用来进行整数运算、一个用来浮点数运算、一个用来布尔运算。两个或者更多的指令被一次性取出、解码并放入缓冲区中,直至它们执行完毕。只要一个执行单元空闲,就会去检查缓冲区是否有可以执行的指令。如果有,就把指令从缓冲区中取出并执行。这种设计的含义是应用程序通常是无序执行的。在大多数情况下,硬件负责保证这种运算的结果与顺序执行指令时的结果相同。

除了用在嵌入式系统中非常简单的 CPU 之外,多数 CPU 都有两种模式,即前面已经提到的内核态和用户态。通常情况下,PSW 寄存器中的一个二进制位会控制当前状态是内核态还是用户态。当运行在内核态时,CPU 能够执行任何指令集中的指令并且能够使用硬件的功能。在台式机和服务器上,操作系统通常以内核模式运行,从而可以访问完整的硬件。在大多数嵌入式系统中,一部分运行在内核态下,剩下的一部分运行在用户态下。

用户应用程序通常运行在用户态下,在用户态下,CPU 只能执行指令集中的一部分并且只能访问硬件的一部分功能。一般情况下,在用户态下,有关 I/O 和内存保护的所有指令是禁止执行的。当然,设置 PSW 模式的二进制位为内核态也是禁止的。

为了获取操作系统的服务,用户程序必须使用 系统调用(system call),系统调用会转换为内核态并且调用操作系统。TRAP 指令用于把用户态切换为内核态并启用操作系统。当有关工作完成之后,在系统调用后面的指令会把控制权交给用户程序。我们会在后面探讨操作系统的调用细节。

需要注意的是操作系统在进行系统调用时会存在陷阱。大部分的陷阱会导致硬件发出警告,比如说试图被零除或浮点下溢等你。在所有的情况下,操作系统都能得到控制权并决定如何处理异常情况。有时,由于出错的原因,程序不得不停止。

多线程和多核芯片

Intel Pentinum 4也就是奔腾处理器引入了被称为多线程(multithreading) 或 超线程(hyperthreading, Intel 公司的命名) 的特性,x86 处理器和其他一些 CPU 芯片就是这样做的。包括 SSPARC、Power5、Intel Xeon 和 Intel Core 系列 。近似地说,多线程允许 CPU 保持两个不同的线程状态并且在纳秒级(nanosecond) 的时间完成切换。线程是一种轻量级的进程,我们会在后面说到。例如,如果一个进程想要从内存中读取指令(这通常会经历几个时钟周期),多线程 CPU 则可以切换至另一个线程。多线程不会提供真正的并行处理。在一个时刻只有一个进程在运行。

对于操作系统来讲,多线程是有意义的,因为每个线程对操作系统来说都像是一个单个的 CPU。比如一个有两个 CPU 的操作系统,并且每个 CPU 运行两个线程,那么这对于操作系统来说就可能是 4 个 CPU。

除了多线程之外,现在许多 CPU 芯片上都具有四个、八个或更多完整的处理器或内核。多核芯片在其上有效地承载了四个微型芯片,每个微型芯片都有自己的独立CPU。

如果要说在绝对核心数量方面,没有什么能赢过现代 GPU(Graphics Processing Unit),GPU 是指由成千上万个微核组成的处理器。它们擅长处理大量并行的简单计算。

内存

计算机中第二个主要的组件就是内存。理想情况下,内存应该非常快速(比执行一条指令要快,从而不会拖慢 CPU 执行效率),而且足够大且便宜,但是目前的技术手段无法满足三者的需求。于是采用了不同的处理方式,存储器系统采用一种分层次的结构

顶层的存储器速度最高,但是容量最小,成本非常高,层级结构越向下,其访问效率越慢,容量越大,但是造价也就越便宜。

寄存器

存储器的顶层是 CPU 中的寄存器,它们用和 CPU 一样的材料制成,所以和 CPU 一样快。程序必须在软件中自行管理这些寄存器(即决定如何使用它们)

高速缓存

位于寄存器下面的是高速缓存,它多数由硬件控制。主存被分割成高速缓存行(cache lines) 为 64 字节,内存地址的 0 - 63 对应高速缓存行 0 ,地址 64 - 127 对应高速缓存行的 1,等等。使用最频繁的高速缓存行保存在位于 CPU 内部或非常靠近 CPU 的高速缓存中。当应用程序需要从内存中读取关键词的时候,高速缓存的硬件会检查所需要的高速缓存行是否在高速缓存中。如果在的话,那么这就是高速缓存命中(cache hit)。高速缓存满足了该请求,并且没有通过总线将内存请求发送到主内存。高速缓存命中通常需要花费两个时钟周期。缓存未命中需要从内存中提取,这会消耗大量的时间。高速缓存行会限制容量的大小因为它的造价非常昂贵。有一些机器会有两个或者三个高速缓存级别,每一级高速缓存比前一级慢且容量更大。

缓存在计算机很多领域都扮演了非常重要的角色,不仅仅是 RAM 缓存行。

随机存储器(RAM): 内存中最重要的一种,表示既可以从中读取数据,也可以写入数据。当机器关闭时,内存中的信息会 丢失。

大量的可用资源被划分为小的部分,这些可用资源的一部分会获得比其他资源更频繁的使用权,缓存经常用来提升性能。操作系统无时无刻的不在使用缓存。例如,大多数操作系统在主机内存中保留(部分)频繁使用的文件,以避免重复从磁盘重复获取。举个例子,类似于 /home/ast/projects/minix3/src/kernel/clock.c 这样的场路径名转换成的文件所在磁盘地址的结果也可以保存缓存中,以避免重复寻址。另外,当一个 Web 页面(URL) 的地址转换为网络地址(IP地址)后,这个转换结果也可以缓存起来供将来使用。

在任何缓存系统中,都会有下面这几个噬需解决的问题

  • 何时把新的内容放进缓存
  • 把新的内容应该放在缓存的哪一行
  • 在需要空闲空间时,应该把哪块内容从缓存中移除
  • 应该把移除的内容放在某个较大存储器的何处

并不是每个问题都与每种缓存情况有关。对于 CPU 缓存中的主存缓存行,当有缓存未命中时,就会调入新的内容。通常通过所引用内存地址的高位计算应该使用的缓存行。

缓存是解决问题的一种好的方式,所以现代 CPU 设计了两种缓存。第一级缓存或者说是 L1 cache 总是位于 CPU 内部,用来将已解码的指令调入 CPU 的执行引擎。对于那些频繁使用的关键字,多数芯片有第二个 L1 cache 。典型的 L1 cache 的大小为 16 KB。另外,往往还设有二级缓存,也就是 L2 cache,用来存放最近使用过的关键字,一般是兆字节为单位。L1 cache 和 L2 cache 最大的不同在于是否存在延迟。访问 L1 cache 没有任何的延迟,然而访问 L2 cache 会有 1 - 2 个时钟周期的延后。

什么是时钟周期?计算机处理器或 CPU 的速度由时钟周期来确定,该时钟周期是振荡器两个脉冲之间的时间量。一般而言,每秒脉冲数越高,计算机处理器处理信息的速度就越快。 时钟速度以 Hz 为单位测量,通常为兆赫(MHz)或千兆赫(GHz)。 例如,一个4 GHz处理器每秒执行4,000,000,000个时钟周期。

计算机处理器可以在每个时钟周期执行一条或多条指令,这具体取决于处理器的类型。 早期的计算机处理器和较慢的 CPU 在每个时钟周期只能执行一条指令,而现代处理器在每个时钟周期可以执行多条指令。

主存

在上面的层次结构中再下一层是主存,这是内存系统的主力军,主存通常叫做 RAM(Random Access Memory),由于 1950 年代和 1960 年代的计算机使用微小的可磁化铁氧体磁芯作为主存储器,因此旧时有时将其称为核心存储器。所有不能再高速缓存中得到满足的内存访问请求都会转往主存中。

除了主存之外,许多计算机还具有少量的非易失性随机存取存储器。它们与 RAM 不同,在电源断电后,非易失性随机访问存储器并不会丢失内容。ROM(Read Only Memory) 中的内容一旦存储后就不会再被修改。它非常快而且便宜。(如果有人问你,有没有什么又快又便宜的内存设备,那就是 ROM 了)在计算机中,用于启动计算机的引导加载模块(也就是 bootstrap )就存放在 ROM 中。另外,一些 I/O 卡也采用 ROM 处理底层设备控制。

EEPROM(Electrically Erasable PROM,) 和 闪存(flash memory) 也是非易失性的,但是与 ROM 相反,它们可以擦除和重写。不过重写它们需要比写入 RAM 更多的时间,所以它们的使用方式与 ROM 相同,但是与 ROM 不同的是他们可以通过重写字段来纠正程序中出现的错误。

闪存也通常用来作为便携性的存储媒介。闪存是数码相机中的胶卷,是便携式音乐播放器的磁盘。闪存的速度介于 RAM 和磁盘之间。另外,与磁盘存储器不同的是,如果闪存擦除的次数太多,会出现磨损。

还有一类是 CMOS,它是易失性的。许多计算机都会使用 CMOS 存储器保持当前时间和日期。

磁盘

下一个层次是磁盘(硬盘),磁盘同 RAM 相比,每个二进制位的成本低了两个数量级,而且经常也有两个数量级大的容量。磁盘唯一的问题是随机访问数据时间大约慢了三个数量级。磁盘访问慢的原因是因为磁盘的构造不同

磁盘是一种机械装置,在一个磁盘中有一个或多个金属盘片,它们以 5400rpm、7200rpm、10800rpm 或更高的速度旋转。从边缘开始有一个机械臂悬横在盘面上,这类似于老式播放塑料唱片 33 转唱机上的拾音臂。信息会写在磁盘一系列的同心圆上。在任意一个给定臂的位置,每个磁头可以读取一段环形区域,称为磁道(track)。把一个给定臂的位置上的所有磁道合并起来,组成了一个柱面(cylinder)。

每个磁道划分若干扇区,扇区的值是 512 字节。在现代磁盘中,较外部的柱面比较内部的柱面有更多的扇区。机械臂从一个柱面移动到相邻的柱面大约需要 1ms。而随机移到一个柱面的典型时间为 5ms 至 10ms,具体情况以驱动器为准。一旦磁臂到达正确的磁道上,驱动器必须等待所需的扇区旋转到磁头之下,就开始读写,低端硬盘的速率是50MB/s,而高速磁盘的速率是 160MB/s。

需要注意,固态硬盘(Solid State Disk, SSD)不是磁盘,固态硬盘并没有可以移动的部分,外形也不像唱片,并且数据是存储在存储器(闪存)中,与磁盘唯一的相似之处就是它也存储了大量即使在电源关闭也不会丢失的数据。

许多计算机支持一种著名的虚拟内存机制,这种机制使得期望运行的存储空间大于实际的物理存储空间。其方法是将程序放在磁盘上,而将主存作为一部分缓存,用来保存最频繁使用的部分程序,这种机制需要快速映像内存地址,用来把程序生成的地址转换为有关字节在 RAM 中的物理地址。这种映像由 CPU 中的一个称为 存储器管理单元(Memory Management Unit, MMU) 的部件来完成。

缓存和 MMU 的出现是对系统的性能有很重要的影响,在多道程序系统中,从一个程序切换到另一个程序的机制称为 上下文切换(context switch),对来自缓存中的资源进行修改并把其写回磁盘是很有必要的。

I/O 设备

CPU 和存储器不是操作系统需要管理的全部,I/O 设备也与操作系统关系密切。可以参考上面这个图片,I/O 设备一般包括两个部分:设备控制器和设备本身。控制器本身是一块芯片或者一组芯片,它能够控制物理设备。它能够接收操作系统的指令,例如,从设备中读取数据并完成数据的处理。

在许多情况下,实际控制设备的过程是非常复杂而且存在诸多细节。因此控制器的工作就是为操作系统提供一个更简单(但仍然非常复杂)的接口。也就是屏蔽物理细节。任何复杂的东西都可以加一层代理来解决,这是计算机或者人类社会很普世的一个解决方案

I/O 设备另一部分是设备本身,设备本身有一个相对简单的接口,这是因为接口既不能做很多工作,而且也已经被标准化了。例如,标准化后任何一个 SATA 磁盘控制器就可以适配任意一种 SATA 磁盘,所以标准化是必要的。ATA 代表 高级技术附件(AT Attachment),而 SATA 表示串行高级技术附件(Serial ATA)。

AT 是啥?它是 IBM 公司的第二代个人计算机的高级技术成果,使用 1984 年推出的 6MHz 80286 处理器,这个处理器是当时最强大的。

像是高级这种词汇应该慎用,否则 20 年后再回首很可能会被无情打脸。

现在 SATA 是很多计算机的标准硬盘接口。由于实际的设备接口隐藏在控制器中,所以操作系统看到的是对控制器的接口,这个接口和设备接口有很大区别。

每种类型的设备控制器都是不同的,所以需要不同的软件进行控制。专门与控制器进行信息交流,发出命令处理指令接收响应的软件,称为 设备驱动程序(device driver)。 每个控制器厂家都应该针对不同的操作系统提供不同的设备驱动程序。

为了使设备驱动程序能够工作,必须把它安装在操作系统中,这样能够使它在内核态中运行。要将设备驱动程序装入操作系统,一般有三个途径

  • 第一个途径是将内核与设备启动程序重新连接,然后重启系统。这是 UNIX 系统采用的工作方式
  • 第二个途径是在一个操作系统文件中设置一个入口,通知该文件需要一个设备驱动程序,然后重新启动系统。在重新系统时,操作系统回寻找有关的设备启动程序并把它装载,这是 Windows 采用的工作方式
  • 第三个途径是操作系统能够在运行时接收新的设备驱动程序并立刻安装,无需重启操作系统,这种方式采用的少,但是正变得普及起来。热插拔设备,比如 USB 和 IEEE 1394 都需要动态可装载的设备驱动程序。

每个设备控制器都有少量用于通信的寄存器,例如,一个最小的磁盘控制器也会有用于指定磁盘地址、内存地址、扇区计数的寄存器。要激活控制器,设备驱动程序回从操作系统获取一条指令,然后翻译成对应的值,并写入设备寄存器中,所有设备寄存器的结合构成了 I/O 端口空间 。

在一些计算机中,设备寄存器会被映射到操作系统的可用地址空间,使他们能够向内存一样完成读写操作。在这种计算机中,不需要专门的 I/O 指令,用户程序可以被硬件阻挡在外,防止其接触这些存储器地址(例如,采用基址寄存器和变址寄存器)。在另一些计算机中,设备寄存器被放入一个专门的 I/O 端口空间,每个寄存器都有一个端口地址。在这些计算机中,特殊的 IN 和 OUT 指令会在内核态下启用,它能够允许设备驱动程序和寄存器进行读写。前面第一种方式会限制特殊的 I/O 指令但是允许一些地址空间;后者不需要地址空间但是需要特殊的指令,这两种应用都很广泛。

实现输入和输出的方式有三种。

  • 在最简单的方式中,用户程序会发起系统调用,内核会将其转换为相应驱动程序的程序调用,然后设备驱动程序启动 I/O 并循环检查该设备,看该设备是否完成了工作(一般会有一些二进制位用来指示设备仍在忙碌中)。当 I/O 调用完成后,设备驱动程序把数据送到指定的地方并返回。然后操作系统会将控制权交给调用者。这种方式称为 忙等待(busy waiting),这种方式的缺点是要一直占据 CPU,CPU 会一直轮询 I/O 设备直到 I/O 操作完成。
  • 第二种方式是设备驱动程序启动设备并且让该设备在操作完成时发生中断。设备驱动程序在这个时刻返回。操作系统接着在需要时阻塞调用者并安排其他工作进行。当设备驱动程序检测到该设备操作完成时,它发出一个 中断 通知操作完成。

在操作系统中,中断是非常重要的,所以这需要更加细致的讨论一下。

如上图所示,这是一个三步的 I/O 过程,第一步,设备驱动程序会通过写入设备寄存器告诉控制器应该做什么。然后,控制器启动设备。当控制器完成读取或写入被告知需要传输的字节后,它会在步骤 2 中使用某些总线向中断控制器发送信号。如果中断控制器准备好了接收中断信号(如果正忙于一个优先级较高的中断,则可能不会接收),那么它就会在 CPU 的一个引脚上面声明。这就是步骤3

在第四步中,中断控制器把该设备的编号放在总线上,这样 CPU 可以读取总线,并且知道哪个设备完成了操作(可能同时有多个设备同时运行)。

一旦 CPU 决定去实施中断后,程序计数器和 PSW 就会被压入到当前堆栈中并且 CPU 会切换到内核态。设备编号可以作为内存的一个引用,用来寻找该设备中断处理程序的地址。这部分内存称作中断向量(interrupt vector)。一旦中断处理程序(中断设备的设备驱动程序的一部分)开始后,它会移除栈中的程序计数器和 PSW 寄存器,并把它们进行保存,然后查询设备的状态。在中断处理程序全部完成后,它会返回到先前用户程序尚未执行的第一条指令,这个过程如下

  • 实现 I/O 的第三种方式是使用特殊的硬件:直接存储器访问(Direct Memory Access, DMA) 芯片。它可以控制内存和某些控制器之间的位流,而无需 CPU 的干预。CPU 会对 DMA 芯片进行设置,说明需要传送的字节数,有关的设备和内存地址以及操作方向。当 DMA 芯片完成后,会造成中断,中断过程就像上面描述的那样。我们会在后面具体讨论中断过程

当另一个中断处理程序正在运行时,中断可能(并且经常)发生在不合宜的时间。 因此,CPU 可以禁用中断,并且可以在之后重启中断。在 CPU 关闭中断后,任何已经发出中断的设备,可以继续保持其中断信号处理,但是 CPU 不会中断,直至中断再次启用为止。如果在关闭中断时,已经有多个设备发出了中断信号,中断控制器将决定优先处理哪个中断,通常这取决于事先赋予每个设备的优先级,最高优先级的设备优先赢得中断权,其他设备则必须等待。

总线

上面的结构(简单个人计算机的组件图)在小型计算机已经使用了多年,并用在早期的 IBM PC 中。然而,随着处理器核内存变得越来越快,单个总线处理所有请求的能力也达到了上线,其中也包括 IBM PC 总线。必须放弃使用这种模式。其结果导致了其他总线的出现,它们处理 I/O 设备以及 CPU 到存储器的速度都更快。这种演变的结果导致了下面这种结构的出现。

上图中的 x86 系统包含很多总线,高速缓存、内存、PCIe、PCI、USB、SATA 和 DMI,每条总线都有不同的传输速率和功能。操作系统必须了解所有的总线配置和管理。其中最主要的总线是 PCIe(Peripheral Component Interconnect Express) 总线。

Intel 发明的 PCIe 总线也是作为之前古老的 PCI 总线的继承者,而古老的 PCI 总线也是为了取代古董级别的 ISA(Industry Standard Architecture) 总线而设立的。数十 Gb/s 的传输能力使得 PCIe 比它的前身快很多,而且它们本质上也十分不同。直到发明 PCIe 的 2004 年,大多数总线都是并行且共享的。共享总线架构(shared bus architeture) 表示多个设备使用一些相同的电线传输数据。因此,当多个设备同时发送数据时,此时你需要一个决策者来决定谁能够使用总线。而 PCIe 则不一样,它使用专门的端到端链路。传统 PCI 中使用的并行总线架构(parallel bus architecture) 表示通过多条电线发送相同的数据字。例如,在传统的 PCI 总线上,一个 32 位数据通过 32 条并行的电线发送。而 PCIe 则不同,它选用了串行总线架构(serial bus architecture) ,并通过单个连接(称为通道)发送消息中的所有比特数据,就像网络数据包一样。这样做会简化很多,因为不再确保所有 32 位数据在同一时刻准确到达相同的目的地。通过将多个数据通路并行起来,并行性仍可以有效利用。例如,可以使用 32 条数据通道并行传输 32 条消息。

在上图结构中,CPU 通过 DDR3 总线与内存对话,通过 PCIe 总线与外围图形设备 (GPU)对话,通过 DMI(Direct Media Interface)总线经集成中心与所有其他设备对话。而集成控制中心通过串行总线与 USB 设备对话,通过 SATA 总线与硬盘和 DVD 驱动器对话,通过 PCIe 传输以太网络帧。

不仅如此,每一个核

USB(Univversal Serial Bus) 是用来将所有慢速 I/O 设备(比如键盘和鼠标)与计算机相连的设备。USB 1.0 可以处理总计 12 Mb/s 的负载,而 USB 2.0 将总线速度提高到 480Mb/s ,而 USB 3.0 能达到不小于 5Gb/s 的速率。所有的 USB 设备都可以直接连接到计算机并能够立刻开始工作,而不像之前那样要求重启计算机。

SCSI(Small Computer System Interface) 总线是一种高速总线,用在高速硬盘,扫描仪和其他需要较大带宽的设备上。现在,它们主要用在服务器和工作站中,速度可以达到 640MB/s 。

计算机启动过程

那么有了上面一些硬件再加上操作系统的支持,我们的计算机就可以开始工作了,那么计算机的启动过程是怎样的呢?下面只是一个简要版的启动过程

在每台计算机上有一块双亲板,也就是母板,母板也就是主板,它是计算机最基本也就是最重要的部件之一。主板一般为矩形电路板,上面安装了组成计算机的主要电路系统,一般有 BIOS 芯片、I/O 控制芯片、键盘和面板控制开关接口、指示灯插接件、扩充插槽、主板及插卡的直流电源供电接插件等元件。

在母板上有一个称为 基本输入输出系统(Basic Input Output System, BIOS)的程序。在 BIOS 内有底层 I/O 软件,包括读键盘、写屏幕、磁盘I/O 以及其他过程。如今,它被保存在闪存中,它是非易失性的,但是当BIOS 中发现错误时,可以由操作系统进行更新。

在计算机启动(booted)时,BIOS 开启,它会首先检查所安装的 RAM 的数量,键盘和其他基础设备是否已安装并且正常响应。接着,它开始扫描 PCIe 和 PCI 总线并找出连在上面的所有设备。即插即用的设备也会被记录下来。如果现有的设备和系统上一次启动时的设备不同,则新的设备将被重新配置。

蓝后,BIOS 通过尝试存储在 CMOS 存储器中的设备清单尝试启动设备

CMOS是 Complementary Metal Oxide Semiconductor(互补金属氧化物半导体)的缩写。它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的 RAM 芯片。因为可读写的特性,所以在电脑主板上用来保存 BIOS 设置完电脑硬件参数后的数据,这个芯片仅仅是用来存放数据的。

而对 BIOS 中各项参数的设定要通过专门的程序。BIOS 设置程序一般都被厂商整合在芯片中,在开机时通过特定的按键就可进入 BIOS 设置程序,方便地对系统进行设置。因此 BIOS 设置有时也被叫做 CMOS 设置。

用户可以在系统启动后进入一个 BIOS 配置程序,对设备清单进行修改。然后,判断是否能够从外部 CD-ROM 和 USB 驱动程序启动,如果启动失败的话(也就是没有),系统将从硬盘启动,boots 设备中的第一个扇区被读入内存并执行。该扇区包含一个程序,该程序通常在引导扇区末尾检查分区表以确定哪个分区处于活动状态。然后从该分区读入第二个启动加载程序,该加载器从活动分区中读取操作系统并启动它。

然后操作系统会询问 BIOS 获取配置信息。对于每个设备来说,会检查是否有设备驱动程序。如果没有,则会向用户询问是否需要插入 CD-ROM 驱动(由设备制造商提供)或者从 Internet 上下载。一旦有了设备驱动程序,操作系统会把它们加载到内核中,然后初始化表,创建所需的后台进程,并启动登录程序或GUI。

操作系统博物馆

操作系统已经存在了大半个世纪,在这段时期内,出现了各种类型的操作系统,但并不是所有的操作系统都很出名,下面就罗列一些比较出名的操作系统

大型机操作系统

高端一些的操作系统是大型机操作系统,这些大型操作系统可在大型公司的数据中心找到。这些计算机的 I/O 容量与个人计算机不同。一个大型计算机有 1000 个磁盘和数百万 G 字节的容量是很正常,如果有这样一台个人计算机朋友会很羡慕。大型机也在高端 Web 服务器、大型电子商务服务站点上。

服务器操作系统

下一个层次是服务器操作系统。它们运行在服务器上,服务器可以是大型个人计算机、工作站甚至是大型机。它们通过网络为若干用户服务,并且允许用户共享硬件和软件资源。服务器可提供打印服务、文件服务或 Web 服务。Internet 服务商运行着许多台服务器机器,为用户提供支持,使 Web 站点保存 Web 页面并处理进来的请求。典型的服务器操作系统有 Solaris、FreeBSD、Linux 和 Windows Server 201x

多处理器操作系统

获得大型计算能力的一种越来越普遍的方式是将多个 CPU 连接到一个系统中。依据它们连接方式和共享方式的不同,这些系统称为并行计算机,多计算机或多处理器。他们需要专门的操作系统,不过通常采用的操作系统是配有通信、连接和一致性等专门功能的服务器操作系统的变体。

个人计算机中近来出现了多核芯片,所以常规的台式机和笔记本电脑操作系统也开始与小规模多处理器打交道,而核的数量正在与时俱进。许多主流操作系统比如 Windows 和 Linux 都可以运行在多核处理器上。

个人计算机系统

接下来一类是个人计算机操作系统。现代个人计算机操作系统支持多道处理程序。在启动时,通常有几十个程序开始运行,它们的功能是为单个用户提供良好的支持。这类系统广泛用于字处理、电子表格、游戏和 Internet 访问。常见的例子是 Linux、FreeBSD、Windows 7、Windows 8 和苹果公司的 OS X 。

掌上计算机操作系统

随着硬件越来越小化,我们看到了平板电脑、智能手机和其他掌上计算机系统。掌上计算机或者 PDA(Personal Digital Assistant),个人数字助理 是一种可以握在手中操作的小型计算机。这部分市场已经被谷歌的 Android 系统和苹果的 IOS主导。

嵌入式操作系统

嵌入式操作系统用来控制设备的计算机中运行,这种设备不是一般意义上的计算机,并且不允许用户安装软件。典型的例子有微波炉、汽车、DVD 刻录机、移动电话以及 MP3 播放器一类的设备。所有的软件都运行在 ROM 中,这意味着应用程序之间不存在保护,从而获得某种简化。主要的嵌入式系统有 Linux、QNX 和 VxWorks

传感器节点操作系统

有许多用途需要配置微小传感器节点网络。这些节点是一种可以彼此通信并且使用无线通信基站的微型计算机。这类传感器网络可以用于建筑物周边保护、国土边界保卫、森林火灾探测、气象预测用的温度和降水测量等。

每个传感器节点是一个配有 CPU、RAM、ROM 以及一个或多个环境传感器的实实在在的计算机。节点上运行一个小型但是真是的操作系统,通常这个操作系统是事件驱动的,可以响应外部事件。

实时操作系统

另一类操作系统是实时操作系统,这些系统的特征是将时间作为关键参数。例如,在工业过程控制系统中,工厂中的实时计算机必须收集生产过程的数据并用有关数据控制机器。如果某个动作必须要在规定的时刻发生,这就是硬实时系统。可以在工业控制、民用航空、军事以及类似应用中看到很多这样的系统。另一类系统是 软实时系统,在这种系统中,虽然不希望偶尔违反最终时限,但仍可以接受,并不会引起任何永久性损害。数字音频或多媒体系统就是这类系统。智能手机也是软实时系统。

智能卡操作系统

最小的操作系统运行在智能卡上。智能卡是一种包含一块 CPU 芯片的信用卡。它有非常严格的运行能耗和存储空间的限制。有些卡具有单项功能,如电子支付;有些智能卡是面向 Java 的。这意味着在智能卡的 ROM 中有一个 Java 虚拟机(Java Virtual Machine, JVM)解释器。

操作系统概念

大部分操作系统提供了特定的基础概念和抽象,例如进程、地址空间、文件等,它们是需要理解的核心内容。下面我们会简要介绍一些基本概念,为了说明这些概念,我们会不时的从 UNIX 中提出示例,相同的示例也会存在于其他系统中,我们后面会进行介绍。

进程

操作系统一个很关键的概念就是 进程(Process)。进程的本质就是操作系统执行的一个程序。与每个进程相关的是地址空间(address space),这是从某个最小值的存储位置(通常是零)到某个最大值的存储位置的列表。在这个地址空间中,进程可以进行读写操作。地址空间中存放有可执行程序,程序所需要的数据和它的栈。与每个进程相关的还有资源集,通常包括寄存器(registers)(寄存器一般包括程序计数器(program counter)和堆栈指针(stack pointer))、打开文件的清单、突发的报警、有关的进程清单和其他需要执行程序的信息。你可以把进程看作是容纳运行一个程序所有信息的一个容器。

对进程建立一种直观感觉的方式是考虑建立一种多程序的系统。考虑下面这种情况:用户启动一个视频编辑程序,指示它按照某种格式转换视频,然后再去浏览网页。同时,一个检查电子邮件的后台进程被唤醒并开始运行,这样,我们目前就会有三个活动进程:视频编辑器、Web 浏览器和电子邮件接收程序。操作系统周期性的挂起一个进程然后启动运行另一个进程,这可能是由于过去一两秒钟程序用完了 CPU 分配的时间片,而 CPU 转而运行另外的程序。

像这样暂时中断进程后,下次应用程序在此启动时,必须要恢复到与中断时刻相同的状态,这在我们用户看起来是习以为常的事情,但是操作系统内部却做了巨大的事情。这就像和足球比赛一样,一场完美精彩的比赛是可以忽略裁判的存在的。这也意味着在挂起时该进程的所有信息都要被保存下来。例如,进程可能打开了多个文件进行读取。与每个文件相关联的是提供当前位置的指针(即下一个需要读取的字节或记录的编号)。当进程被挂起时,必须要保存这些指针,以便在重新启动进程后执行的 read调用将能够正确的读取数据。在许多操作系统中,与一个进程有关的所有信息,除了该进程自身地址空间的内容以外,均存放在操作系统的一张表中,称为 进程表(process table),进程表是数组或者链表结构,当前存在每个进程都要占据其中的一项。

所以,一个挂起的进程包括:进程的地址空间(往往称作磁芯映像, core image,纪念过去的磁芯存储器),以及对应的进程表项(其中包括寄存器以及稍后启动该进程所需要的许多其他信息)。

与进程管理有关的最关键的系统调用往往是决定着进程的创建和终止的系统调用。考虑一个典型的例子,有一个称为 命令解释器(command interpreter) 或 shell 的进程从终端上读取命令。此时,用户刚键入一条命令要求编译一个程序。shell 必须先创建一个新进程来执行编译程序,当编译程序结束时,它执行一个系统调用来终止自己的进程。

如果一个进程能够创建一个或多个进程(称为子进程),而且这些进程又可以创建子进程,则很容易找到进程数,如下所示

上图表示一个进程树的示意图,进程 A 创建了两个子进程 B 和进程 C,子进程 B 又创建了三个子进程 D、E、F。

合作完成某些作业的相关进程经常需要彼此通信来完成作业,这种通信称为进程间通信(interprocess communication)。我们在后面会探讨进程间通信。

其他可用的进程系统调用包括:申请更多的内存(或释放不再需要的内存),等待一个子进程结束,用另一个程序覆盖该程序。

有时,需要向一个正在运行的进程传递信息,而该进程并没有等待接收信息。例如,一个进程通过网络向另一台机器上的进程发送消息进行通信。为了保证一条消息或消息的应答不丢失。发送者要求它所在的操作系统在指定的若干秒后发送一个通知,这样如果对方尚未收到确认消息就可以进行重新发送。在设定该定时器后,程序可以继续做其他工作。

在限定的时间到达后,操作系统会向进程发送一个 警告信号(alarm signal)。这个信号引起该进程暂时挂起,无论该进程正在做什么,系统将其寄存器的值保存到堆栈中,并开始重新启动一个特殊的信号处理程,比如重新发送可能丢失的消息。这些信号是软件模拟的硬件中断,除了定时器到期之外,该信号可以通过各种原因产生。许多由硬件检测出来的陷阱,如执行了非法指令或使用了无效地址等,也被转换成该信号并交给这个进程。

系统管理器授权每个进程使用一个给定的 UID(User IDentification)。每个启动的进程都会有一个操作系统赋予的 UID,子进程拥有与父进程一样的 UID。用户可以是某个组的成员,每个组也有一个 GID(Group IDentification)。

在 UNIX 操作系统中,有一个 UID 是 超级用户(superuser),或者 Windows 中的管理员(administrator),它具有特殊的权利,可以违背一些保护规则。在大型系统中,只有系统管理员掌握着那些用户可以称为超级用户。

地址空间

每台计算机都有一些主存用来保存正在执行的程序。在一个非常简单的操作系统中,仅仅有一个应用程序运行在内存中。为了运行第二个应用程序,需要把第一个应用程序移除才能把第二个程序装入内存。

复杂一些的操作系统会允许多个应用程序同时装入内存中运行。为了防止应用程序之间相互干扰(包括操作系统),需要有某种保护机制。虽然此机制是在硬件中实现,但却是由操作系统控制的。

上述观点涉及对计算机主存的管理和保护。另一种同等重要并与存储器有关的内容是管理进程的地址空间。通常,每个进程有一些可以使用的地址集合,典型值从 0 开始直到某个最大值。一个进程可拥有的最大地址空间小于主存。在这种情况下,即使进程用完其地址空间,内存也会有足够的内存运行该进程。

但是,在许多 32 位或 64 位地址的计算机中,分别有 2^32 或 2^64 字节的地址空间。如果一个进程有比计算机拥有的主存还大的地址空间,而且该进程希望使用全部的内存,那该怎么处理?在早期的计算机中是无法处理的。但是现在有了一种虚拟内存的技术,正如前面讲到过的,操作系统可以把部分地址空间装入主存,部分留在磁盘上,并且在需要时来回交换它们。

文件

几乎所有操作系统都支持的另一个关键概念就是文件系统。如前所述,操作系统的一项主要功能是屏蔽磁盘和其他 I/O 设备的细节特性,给程序员提供一个良好、清晰的独立于设备的抽象文件模型。创建文件、删除文件、读文件和写文件 都需要系统调用。在文件可以读取之前,必须先在磁盘上定位和打开文件,在文件读过之后应该关闭该文件,有关的系统调用则用于完成这类操作。

为了提供保存文件的地方,大多数个人计算机操作系统都有目录(directory) 的概念,从而可以把文件分组。比如,学生可以给每个课程都创建一个目录,用于保存该学科的资源,另一个目录可以存放电子邮件,再有一个目录可以存放万维网主页。这就需要系统调用创建和删除目录、将已有文件放入目录中,从目录中删除文件等。目录项可以是文件或者目录,目录和目录之间也可以嵌套,这样就产生了文件系统

进程和文件层次都是以树状的结构组织,但这两种树状结构有不少不同之处。一般进程的树状结构层次不深(很少超过三层),而文件系统的树状结构要深一些,通常会到四层甚至五层。进程树层次结构是暂时的,通常最多存在几分钟,而目录层次则可能存在很长时间。进程和文件在权限保护方面也是有区别的。一般来说,父进程能控制和访问子进程,而在文件和目录中通常存在一种机制,使文件所有者之外的其他用户也能访问该文件。

目录层结构中的每一个文件都可以通过从目录的顶部即 根目录(Root directory) 开始的路径名(path name) 来确定。绝对路径名包含了从根目录到该文件的所有目录清单,它们之间用斜杠分隔符分开,在上面的大学院系文件系统中,文件 CS101 的路径名是 /Faculty/Prof.Brown/Courses/CS101。最开始的斜杠分隔符代表的是根目录 /,也就是文件系统的绝对路径。

出于历史原因,Windows 下面的文件系统以 \ 来作为分隔符,但是 Linux 会以 / 作为分隔符。

在上面的系统中,每个进程会有一个 工作目录(working directory),对于没有以斜线开头给出绝对地址的路径,将在这个工作目录下寻找。如果 /Faculty/Prof.Brown 是工作目录,那么 /Courses/CS101 与上面给定的绝对路径名表示的是同一个文件。进程可以通过使用系统调用指定新的工作目录,从而变更其工作目录。

在读写文件之前,首先需要打开文件,检查其访问权限。若权限许可,系统将返回一个小整数,称作文件描述符(file descriptor),供后续操作使用。若禁止访问,系统则返回一个错误码。

在 UNIX 中,另一个重要的概念是 特殊文件(special file)。提供特殊文件是为了使 I/O 设备看起来像文件一般。这样,就像使用系统调用读写文件一样,I/O 设备也可以通过同样的系统调用进行读写。特殊文件有两种,一种是块儿特殊文件(block special file) 和 字符特殊文件(character special file)。块特殊文件指那些由可随机存取的块组成的设备,如磁盘等。比如打开一个块特殊文件,然后读取第4块,程序可以直接访问设备的第4块而不必考虑存放在该文件的文件系统结构。类似的,字符特殊文件用于打印机、调制解调起和其他接受或输出字符流的设备。按照惯例,特殊文件保存在 /dev 目录中。例如,/devv/lp 是打印机。

还有一种与进程和文件相关的特性是管道,管道(pipe) 是一种虚文件,他可以连接两个进程

如果 A 和 B 希望通过管道对话,他们必须提前设置管道。当进程 A 相对进程 B 发送数据时,它把数据写到管道上,相当于管道就是输出文件。这样,在 UNIX 中两个进程之间的通信就非常类似于普通文件的读写了。

保护

计算机中含有大量的信息,用户希望能够对这些信息中有用而且重要的信息加以保护,这些信息包括电子邮件、商业计划等,管理这些信息的安全性完全依靠操作系统来保证。例如,文件提供授权用户访问。

比如 UNIX 操作系统,UNIX 操作系统通过对每个文件赋予一个 9 位二进制保护代码,对 UNIX 中的文件实现保护。该保护代码有三个位子段,一个用于所有者,一个用于与所有者同组(用户被系统管理员划分成组)的其他成员,一个用于其他人。每个字段中有一位用于读访问,一位用于写访问,一位用于执行访问。这些位就是著名的 rwx位。例如,保护代码 rwxr-x--x 的含义是所有者可以读、写或执行该文件,其他的组成员可以读或执行(但不能写)此文件、而其他人可以执行(但不能读和写)该文件。

shell

操作系统是执行系统调用的代码。编辑器、编译器、汇编程序、链接程序、使用程序以及命令解释符等,尽管非常重要,非常有用,但是它们确实不是操作系统的组成部分。下面我们着重介绍一下 UNIX 下的命令提示符,也就是 shell,shell 虽然有用,但它也不是操作系统的一部分,然而它却能很好的说明操作系统很多特性,下面我们就来探讨一下。

shell 有许多种,例如 sh、csh、ksh 以及 bash等,它们都支持下面这些功能,最早起的 shell 可以追溯到 sh

用户登录时,会同时启动一个 shell,它以终端作为标准输入和标准输出。首先显示提示符(prompt),它可能是一个美元符号($),提示用户 shell 正在等待接收命令,假如用户输入

1
复制代码date

shell 会创建一个子进程,并运行 date 做为子进程。在该子进程运行期间,shell 将等待它结束。在子进程完成时,shell 会显示提示符并等待下一行输入。

用户可以将标准输出重定向到一个文件中,例如

1
复制代码date > file

同样的,也可以将标准输入作为重定向

1
复制代码sort <file1> file2

这会调用 sort 程序来接收 file1 的内容并把结果输出到 file2。

可以将一个应用程序的输出通过管道作为另一个程序的输入,因此有

1
复制代码cat file1 file2 file3 | sort > /dev/lp

这会调用 cat 应用程序来合并三个文件,将其结果输送到 sort 程序中并按照字典进行排序。sort 应用程序又被重定向到 /dev/lp ,显然这是一个打印操作。

系统调用

我们已经可以看到操作系统提供了两种功能:为用户提供应用程序抽象和管理计算机资源。对于大部分在应用程序和操作系统之间的交互主要是应用程序的抽象,例如创建、写入、读取和删除文件。计算机的资源管理对用户来说基本上是透明的。因此,用户程序和操作系统之间的接口主要是处理抽象。为了真正理解操作系统的行为,我们必须仔细的分析这个接口。

多数现代操作系统都有功能相同但是细节不同的系统调用,引发操作系统的调用依赖于计算机自身的机制,而且必须用汇编代码表达。任何单 CPU 计算机一次执行执行一条指令。如果一个进程在用户态下运行用户程序,例如从文件中读取数据。那么如果想要把控制权交给操作系统控制,那么必须执行一个异常指令或者系统调用指令。操作系统紧接着需要参数检查找出所需要的调用进程。操作系统紧接着进行参数检查找出所需要的调用进程。然后执行系统调用,把控制权移交给系统调用下面的指令。大致来说,系统调用就像是执行了一个特殊的过程调用,但是只有系统调用能够进入内核态而过程调用则不能进入内核态。

为了能够了解具体的调用过程,下面我们以 read 方法为例来看一下调用过程。像上面提到的那样,会有三个参数,第一个参数是指定文件、第二个是指向缓冲区、第三个参数是给定需要读取的字节数。就像几乎所有系统调用一样,它通过使用与系统调用相同的名称来调用一个函数库,从而从C程序中调用:read。

1
复制代码count = read(fd,buffer,nbytes);

系统调用在 count 中返回实际读出的字节数。这个值通常与 nbytes 相同,但也可能更小。比如在读过程中遇到了文件尾的情况。

如果系统调用不能执行,不管是因为无效的参数还是磁盘错误,count 的值都会被置成 -1,然后在全局变量 errno 中放入错误信号。程序应该进场检查系统调用的结果以了解是否出错。

系统调用是通过一系列的步骤实现的,为了更清楚的说明这个概念,我们还以 read 调用为例,在准备系统调用前,首先会把参数压入堆栈,如下所示

C 和 C++ 编译器使用逆序(必须把第一个参数赋值给 printf(格式字符串),放在堆栈的顶部)。第一个参数和第三个参数都是值调用,但是第二个参数通过引用传递,即传递的是缓冲区的地址(由 & 指示),而不是缓冲的内容。然后是 C 调用系统库的 read 函数,这也是第四步。

在由汇编语言写成的库过程中,一般把系统调用的编号放在操作系统所期望的地方,如寄存器(第五步)。然后执行一个 TRAP 指令,将用户态切换到内核态,并在内核中的一个固定地址开始执行第六步。TRAP 指令实际上与过程调用指令非常相似,它们后面都跟随一个来自远处位置的指令,以及供以后使用的一个保存在栈中的返回地址。

TRAP 指令与过程调用指令存在两个方面的不同

  • TRAP 指令会改变操作系统的状态,由用户态切换到内核态,而过程调用不改变模式
  • 其次,TRAP 指令不能跳转到任意地址上。根据机器的体系结构,要么跳转到一个单固定地址上,或者指令中有一 8 位长的字段,它给定了内存中一张表格的索引,这张表格中含有跳转地址,然后跳转到指定地址上。

跟随在 TRAP 指令后的内核代码开始检查系统调用编号,然后dispatch给正确的系统调用处理器,这通常是通过一张由系统调用编号所引用的、指向系统调用处理器的指针表来完成第七步。此时,系统调用处理器运行第八步,一旦系统调用处理器完成工作,控制权会根据 TRAP 指令后面的指令中返回给函数调用库第九步。这个过程接着以通常的过程调用返回的方式,返回到客户应用程序,这是第十步。然后调用完成后,操作系统还必须清除用户堆栈,然后增加堆栈指针(increment stackpointer),用来清除调用 read 之前压入的参数。从而完成整个 read 调用过程。

在上面的第九步中我们说道,控制可能返回 TRAP 指令后面的指令,把控制权再移交给调用者这个过程中,系统调用会发生阻塞,从而避免应用程序继续执行。这么做是有原因的。例如,如果试图读键盘,此时并没有任何输入,那么调用者就必须被阻塞。在这种情形下,操作系统会检查是否有其他可以运行的进程。这样,当有用户输入 时候,进程会提醒操作系统,然后返回第 9 步继续运行。

下面,我们会列出一些常用的 POSIX 系统调用,POSIX 系统调用大概有 100 多个,它们之中最重要的一些调用见下表

进程管理

调用 说明
pid = fork() 创建与父进程相同的子进程
pid = waitpid(pid, &statloc,options) 等待一个子进程终止
s = execve(name,argv,environp) 替换一个进程的核心映像
exit(status) 终止进程执行并返回状态

文件管理

调用 说明
fd = open(file, how,…) 打开一个文件使用读、写
s = close(fd) 关闭一个打开的文件
n = read(fd,buffer,nbytes) 把数据从一个文件读到缓冲区中
n = write(fd,buffer,nbytes) 把数据从缓冲区写到一个文件中
position = iseek(fd,offset,whence) 移动文件指针
s = stat(name,&buf) 取得文件状态信息

目录和文件系统管理

调用 说明
s = mkdir(nname,mode) 创建一个新目录
s = rmdir(name) 删去一个空目录
s = link(name1,name2) 创建一个新目录项 name2,并指向 name1
s = unlink(name) 删去一个目录项
s = mount(special,name,flag) 安装一个文件系统
s = umount(special) 卸载一个文件系统

其他

调用 说明
s = chdir(dirname) 改变工作目录
s = chmod(name,mode) 修改一个文件的保护位
s = kill(pid, signal) 发送信号给进程
seconds = time(&seconds) 获取从 1970 年1月1日至今的时间

上面的系统调用参数中有一些公共部分,例如 pid 系统进程 id,fd 是文件描述符,n 是字节数,position 是在文件中的偏移量、seconds 是流逝时间。

从宏观角度上看,这些系统调所提供的服务确定了多数操作系统应该具有的功能,下面分别来对不同的系统调用进行解释

用于进程管理的系统调用

在 UNIX 中,fork 是唯一可以在 POSIX 中创建进程的途径,它创建一个原有进程的副本,包括所有的文件描述符、寄存器等内容。在 fork 之后,原有进程以及副本(父与子)就分开了。在 fork 过程中,所有的变量都有相同的值,虽然父进程的数据通过复制给子进程,但是后续对其中任何一个进程的修改不会影响到另外一个。fork 调用会返回一个值,在子进程中该值为 0 ,并且在父进程中等于子进程的 进程标识符(Process IDentified,PID)。使用返回的 PID,就可以看出来哪个是父进程和子进程。

在多数情况下, 在 fork 之后,子进程需要执行和父进程不一样的代码。从终端读取命令,创建一个子进程,等待子进程执行命令,当子进程结束后再读取下一个输入的指令。为了等待子进程完成,父进程需要执行 waitpid 系统调用,父进程会等待直至子进程终止(若有多个子进程的话,则直至任何一个子进程终止)。waitpid 可以等待一个特定的子进程,或者通过将第一个参数设为 -1 的方式,等待任何一个比较老的子进程。当 waitpid 完成后,会将第二个参数 statloc 所指向的地址设置为子进程的退出状态(正常或异常终止以及退出值)。有各种可使用的选项,它们由第三个参数确定。例如,如果没有已经退出的子进程则立刻返回。

那么 shell 该如何使用 fork 呢?在键入一条命令后,shell 会调用 fork 命令创建一个新的进程。这个子进程会执行用户的指令。通过使用 execve 系统调用可以实现系统执行,这个系统调用会引起整个核心映像被一个文件所替代,该文件由第一个参数给定。下面是一个简化版的例子说明 fork、waitpid 和 execve 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码#define TRUE 1

/* 一直循环下去 */
while(TRUE){

/* 在屏幕上显示提示符 */
type_prompt();

/* 从终端读取输入 */
read_command(command,parameters)

/* fork 子进程 */
if(fork() != 0){

/* 父代码 */
/* 等待子进程执行完毕 */
waitpid(-1, &status, 0);
}else{

/* 执行命令 */
/* 子代码 */
execve(command,parameters,0)
}
}

一般情况下,execve 有三个参数:将要执行的文件名称,一个指向变量数组的指针,以及一个指向环境数组的指针。这里对这些参数做一个简要的说明。

先看一个 shell 指令

1
复制代码cp file1 file2

此命令把 file1 复制到 file2 文件中,在 shell 执行 fork 之后,子进程定位并执行文件拷贝,并将源文件和目标文件的名称传递给它。

cp 的主程序(以及包含其他大多数 C 程序的主程序)包含声明

1
复制代码main(argc,argv,envp)

其中 argc 是命令行中参数数目的计数,包括程序名称。对于上面的例子,argc 是3。第二个参数argv 是数组的指针。该数组的元素 i 是指向该命令行第 i 个字符串的指针。在上面的例子中,argv[0] 指向字符串 cp,argv[1] 指向字符串 file1,argv[2] 指向字符串 file2。main 的第三个参数是指向环境的指针,该环境是一个数组,含有 name = value 的赋值形式,用以将诸如终端类型以及根目录等信息传送给程序。这些变量通常用来确定用户希望如何完成特定的任务(例如,使用默认打印机)。在上面的例子中,没有环境参数传递给 execve ,所以环境变量是 0 ,所以 execve 的第三个参数为 0 。

可能你觉得 execve 过于复杂,这时候我要鼓励一下你,execve 可能是 POSIX 的全部系统调用中最复杂的一个了,其他都比较简单。作为一个简单的例子,我们再来看一下 exit ,这是进程在执行完成后应执行的系统调用。这个系统调用有一个参数,它的退出状态是 0 - 255 之间,它通过 waitpid 系统调用中的 statloc 返回给父级。

UNIX 中的进程将内存划分成三个部分:text segment,文本区,例如程序代码,data segment,数据区,例如变量,stack segment,栈区域。数据向上增长而堆栈向下增长,如下图所示

上图能说明三个部分的内存分配情况,夹在中间的是空闲区,也就是未分配的区域,堆栈在需要时自动的挤压空闲区域,不过数据段的扩展是显示地通过系统调用 brk 进行的,在数据段扩充后,该系统调用指向一个新地址。但是,这个调用不是 POSIX 标准中定义的,对于存储器的动态分配,鼓励程序员使用 malloc 函数,而 malloc 的内部实现则不是一个适合标准化的主题,因为几乎没有程序员直接使用它。

用于文件管理的系统调用

许多系统调用都与文件系统有关,要读写一个文件,必须先将其打开。这个系统调用通过绝对路径名或指向工作目录的相对路径名指定要打开文件的名称,而代码 O_RDONLY、 O_WRONLY 或 O_RDWR 的含义分别是只读、只写或者两者都可以,为了创建一个新文件,使用 O_CREATE 参数。然后可使用返回的文件描述符进行读写操作。接着,可以使用 close 关闭文件,这个调用使得文件描述符在后续的 open 中被再次使用。

最常用的调用还是 read 和 write,我们再前面探讨过 read 调用,write 具有与 read 相同的参数。

尽管多数程序频繁的读写文件,但是仍有一些应用程序需要能够随机访问一个文件的任意部分。与每个文件相关的是一个指向文件当前位置的指针。在顺序读写时,该指针通常指向要读出(写入)的下一个字节。Iseek 调用可以改变该位置指针的值,这样后续的 read 或 write 调用就可以在文件的任何地方开始。

Iseek 有三个参数,position = iseek(fd,offset,whence),第一个是文件描述符,第二个是文件位置,第三个是说明该文件位置是相对于文件起始位置,当前位置还是文件的结尾。在修改了指针之后,Iseek 所返回的值是文件中的绝对位置。

UNIX 为每个文件保存了该文件的类型(普通文件、特殊文件、目录等)、大小,最后修改时间以及其他信息,程序可以通过 stat 系统调用查看这些信息。s = stat(name,&buf),第一个参数指定了被检查的文件;第二个参数是一个指针,该指针指向存放这些信息的结构。对于一个打开的文件而言,fstat 调用完成同样的工作。

用于目录管理的系统调用

下面我们探讨目录和整个文件系统的系统调用,上面探讨的是和某个文件有关的系统调用。 mkdir 和 rmdir 分别用于创建s = mkdir(nname,mode) 和删除 s = rmdir(name) 空目录,下一个调用是 s = link(name1,name2) 它的作用是允许同一个文件以两个或者多个名称出现,多数情况下是在不同的目录中使用 link ,下面我们探讨一下 link 是如何工作的

图中有两个用户 ast 和 jim,每个用户都有他自己的一个目录和一些文件,如果 ast 要执行一个包含下面系统调用的应用程序

1
复制代码link("/usr/jim/memo", "/usr/ast/note");

jim 中的 memo 文件现在会进入到 ast 的目录中,在 note 名称下。此后,/usr/jim/memo和 /usr/ast/note 会有相同的名称。

用户目录是保存在 /usr,/user,/home 还是其他位置,都是由本地系统管理员决定的。

要理解 link 是如何工作的需要清楚 link 做了什么操作。UNIX 中的每个文件都有一个独一无二的版本,也称作 i - number,i-编号,它标示着不同文件的版本。这个 i - 编号是 i-nodes,i-节点 表的索引。每个文件都会表明谁拥有这个文件,这个磁盘块的位置在哪,等等。目录只是一个包含一组(i编号,ASCII名称)对应的文件。UNIX 中的第一个版本中,每个目录项都会有 16 个字节,2 个字节对应 i - 编号和 14 个字节对应其名称。现在需要一个更复杂的结构需要支持长文件名,但是从概念上讲一个目录仍是一系列(i-编号,ASCII 名称)的集合。在上图中,mail 的 i-编号为 16,依此类推。link 只是利用某个已有文件的 i-编号,创建一个新目录项(也许用一个新名称)。在上图 b 中,你会发现有两个相同的 70 i-编号的文件,因此它们需要有相同的文件。如果其中一个使用了 unlink 系统调用的话,其中一个会被移除,另一个将保留。如果两个文件都移除了,则 UNIX 会发现该文件不存在任何没有目录项(i-节点中的一个域记录着指向该文件的目录项),就会把该文件从磁盘中移除。

就像我们上面提到过的那样,mount 系统 s = mount(special,name,flag) 调用会将两个文件系统合并为一个。通常的情况是将根文件系统分布在硬盘(子)分区上,并将用户文件分布在另一个(子)分区上,该根文件系统包含常用命令的二进制(可执行)版本和其他使用频繁的文件。然后,用户就会插入可读取的 USB 硬盘。

通过执行 mount 系统调用,USB 文件系统可以被添加到根文件系统中,

如果用 C 语言来执行那就是

1
复制代码mount("/dev/sdb0","/mnt",0)

这里,第一个参数是 USB 驱动器 0 的块特殊文件名称,第二个参数是被安装在树中的位置,第三个参数说明将要安装的文件系统是可读写的还是只读的。

当不再需要一个文件系统时,可以使用 umount 移除之。

其他系统调用

除了进程、文件、目录系统调用,也存在其他系统调用的情况,下面我们来探讨一下。我们可以看到上面其他系统调用只有四种,首先来看第一个 chdir,chdir 调用更改当前工作目录,在调用

1
复制代码chdir("/usr/ast/test");

后,打开 xyz 文件,会打开 /usr/ast/test/xyz 文件,工作目录的概念消除了总是需要输入长文件名的需要。

在 UNIX 系统中,每个文件都会有保护模式,这个模式会有一个读-写-执行位,它用来区分所有者、组和其他成员。chmod 系统调用提供改变文件模式的操作。例如,要使一个文件除了对所有者之外的用户可读,你可以执行

1
复制代码chmod("file",0644);

kill 系统调用是用户和用户进程发送信号的方式,如果一个进程准备好捕捉一个特定的信号,那么在信号捕捉之前,会运行一个信号处理程序。如果进程没有准备好捕捉特定的信号,那么信号的到来会杀掉该进程(此名字的由来)。

POSIX 定义了若干时间处理的进程。例如,time 以秒为单位返回当前时间,0 对应着 1970 年 1月 1日。在一台 32 位字的计算机中,time 的最大值是 (2^32) - 1秒,这个数字对应 136 年多一点。所以在 2106 年,32 位的 UNIX 系统会发飙。如果读者现在有 32 位 UNIX 系统,建议在 2106 年更换位 64 位操作系统(偷笑~)。

Win 32 API

上面我们提到的都是 UNIX 系统调用,现在我们来聊聊 Win 32 中的系统调用。Windows 和 UNIX 在各自的编程方式上有着根本的不同。UNIX 程序由执行某些操作或执行其他操作的代码组成,进行系统调用以执行某些服务。Windows 系统则不同,Windows 应用程序通常是由事件驱动的。主程序会等待一些事件发生,然后调用程序去处理。最简单的事件处理是键盘敲击和鼠标滑过,或者是鼠标点击,或者是插入 USB 驱动,然后操作系统调用处理器去处理事件,更新屏幕和更新程序内部状态。这是与 UNIX 不同的设计风格。

当然,Windows 也有系统调用。在 UNIX 中,系统调用(比如 read)和系统调用所使用的调用库(例如 read)几乎是一对一的关系。而在 Windows 中,情况则大不相同。首先,函数库的调用和实际的系统调用几乎是不对应的。微软定义了一系列过程,称为 Win32应用编程接口(Application Programming Interface),程序员通过这套标准的接口来实现系统调用。这个接口支持从 Windows 95 版本以来所有的 Windows 版本。

Win32 API 调用的数量是非常巨大的,有数千个多。但这些调用并不都是在内核态的模式下运行时,有一些是在用户态的模型下运行。Win32 API 有大量的调用,用来管理视窗、几何图形、文本、字体、滚动条、对话框、菜单以及 GUI 的其他功能。为了使图形子系统在内核态下运行,需要系统调用,否则就只有函数库调用。

我们把关注点放在和 Win32 系统调用中来,我们可以简单看一下 Win32 API 中的系统调用和 UNIX 中有什么不同(并不是所有的系统调用)

UNIX Win32 说明
fork CreateProcess 创建一个新进程
waitpid WaitForSingleObject 等待一个进程退出
execve none CraeteProcess = fork + servvice
exit ExitProcess 终止执行
open CreateFile 创建一个文件或打开一个已有的文件
close CloseHandle 关闭文件
read ReadFile 从单个文件中读取数据
write WriteFile 向单个文件写数据
lseek SetFilePointer 移动文件指针
stat GetFileAttributesEx 获得不同的文件属性
mkdir CreateDirectory 创建一个新的目录
rmdir RemoveDirectory 移除一个空的目录
link none Win32 不支持 link
unlink DeleteFile 销毁一个已有的文件
mount none Win32 不支持 mount
umount none Win32 不支持 mount,所以也不支持mount
chdir SetCurrentDirectory 切换当前工作目录
chmod none Win32 不支持安全
kill none Win32 不支持信号
time GetLocalTime 获取当前时间

上表中是 UNIX 调用大致对应的 Win32 API 系统调用,简述一下上表。CreateProcess 用于创建一个新进程,它把 UNIX 中的 fork 和 execve 两个指令合成一个,一起执行。它有许多参数用来指定新创建进程的性质。Windows 中没有类似 UNIX 中的进程层次,所以不存在父进程和子进程的概念。在进程创建之后,创建者和被创建者是平等的。WaitForSingleObject 用于等待一个事件,等待的事件可以是多种可能的事件。如果有参数指定了某个进程,那么调用者将等待指定的进程退出,这通过 ExitProcess 来完成。

然后是6个文件操作,在功能上和 UNIX 的调用类似,然而在参数和细节上是不同的。和 UNIX 中一样,文件可以打开,读取,写入,关闭。SetFilePointer 和 GetFileAttributesEx 设置文件的位置并取得文件的属性。

Windows 中有目录,目录分别用 CreateDirectory 以及 RemoveDirectory API 调用创建和删除。也有对当前的目录的标记,这可以通过 SetCurrentDirectory 来设置。使用GetLocalTime 可获得当前时间。

Win32 接口中没有文件的链接、文件系统的 mount、umount 和 stat ,当然, Win32 中也有大量 UNIX 中没有的系统调用,特别是对 GUI 的管理和调用。

操作系统结构

下面我们会探讨操作系统的几种结构,主要包括单体结构、分层系统、微内核、客户-服务端系统、虚拟机和外核等。下面以此来探讨一下

单体系统

到目前为止,在大多数系统中,整个系统在内核态以单一程序的方式运行。整个操作系统是以程序集合来编写的,链接在一块形成一个大的二进制可执行程序。使用此技术时,如果系统中的每个过程都提供了前者所需的一些有用的计算,则它可以自由调用任何其他过程。在单体系统中,调用任何一个所需要的程序都非常高效,但是上千个不受限制的彼此调用往往非常臃肿和笨拙,而且单体系统必然存在单体问题,那就是只要系统发生故障,那么任何系统和应用程序将不可用,这往往是灾难性的。

在单体系统中构造实际目标程序时,会首先编译所有单个过程(或包含这些过程的文件),然后使用系统链接器将它们全部绑定到一个可执行文件中

对于单体系统,往往有下面几种建议

  • 需要有一个主程序,用来调用请求服务程序
  • 需要一套服务过程,用来执行系统调用
  • 需要一套服务程序,用来辅助服务过程调用

在单体系统中,对于每个系统调用都会有一个服务程序来保障和运行。需要一组实用程序来弥补服务程序需要的功能,例如从用户程序中获取数据。可将各种过程划分为一个三层模型

除了在计算机初启动时所装载的核心操作系统外,许多操作系统还支持额外的扩展。比如 I/O 设备驱动和文件系统。这些部件可以按需装载。在 UNIX 中把它们叫做 共享库(shared library),在 Windows 中则被称为 动态链接库(Dynamic Link Library,DLL)。他们的扩展名为 .dll,在 C:\Windows\system32 目录下存在 1000 多个 DLL 文件,所以不要轻易删除 C 盘文件,否则可能就炸了哦。

分层系统

分层系统使用层来分隔不同的功能单元。每一层只与该层的上层和下层通信。每一层都使用下面的层来执行其功能。层之间的通信通过预定义的固定接口通信。

分层系统是由 E.W.Dijkstar 和他的学生在荷兰技术学院所开发的 THE 系统。

把上面单体系统进一步通用化,就变为了一个层次式结构的操作系统,它的上层软件都是在下层软件的基础之上构建的。该系统分为六层,如下所示

层号 功能
5 操作员
4 用户程序
3 输入/输出管理
2 操作员-进程通信
1 存储器和磁鼓管理
0 处理器分配和多道程序编程

处理器在 0 层运行,当中断发生或定时器到期时,由该层完成进程切换;在第 0 层之上,系统由一些连续的进程组成,编写这些进程时不用再考虑在单处理器上多进程运行的细节。内存管理在第 1 层,它分配进程的主存空间。第 1 层软件保证一旦需要访问某一页面,该页面必定已经在内存中,并且在页面不需要的时候将其移出。

第 2 层处理进程与操作员控制台(即用户)之间的通信。第 3 层管理 I/O 设备和相关的信息流缓冲区。第 4 层是用户程序层,用户程序不用考虑进程、内存、控制台或 I/O 设备管理等细节。系统操作员在第 5 层。

微内核

在分层方式中,设计者要确定在哪里划分 内核-用户 的边界。传统上,所有的层都在内核中,但是这样做没有必要。事实上,尽可能减少内核态中功能可能是更好的做法。因为内核中的错误很难处理,一旦内核态中出错误会拖累整个系统。

所以,为了实现高可靠性,将操作系统划分成小的、层级之间能够更好定义的模块是很有必要的,只有一个模块 — 微内核 — 运行在内核态,其余模块可以作为普通用户进程运行。由于把每个设备驱动和文件系统分别作为普通用户进程,这些模块中的错误虽然会使这些模块崩溃,但是不会使整个系统死机。

MINIX 3 是微内核的代表作,它的具体结构如下

在内核的外部,系统的构造有三层,它们都在用户态下运行,最底层是设备驱动器。由于它们都在用户态下运行,所以不能物理的访问 I/O 端口空间,也不能直接发出 I/O 命令。相反,为了能够对 I/O 设备编程,驱动器构建一个结构,指明哪个参数值写到哪个 I/O 端口,并声称一个内核调用,这样就完成了一次调用过程。

位于用户态的驱动程序上面是服务器层,包含有服务器,它们完成操作系统的多数工作。由一个或多个文件服务器管理着文件系统,进程管理器创建、销毁和管理进程。服务器中有一个特殊的服务器称为 再生服务器(reincarnation server),它的任务就是检查服务器和驱动程序的功能是否正确,一旦检查出来错误,它就会补上去,无需用户干预。这种方式使得系统具有可恢复性,并具有较高的可靠性。

微内核中的内核还具有一种 机制 与 策略 分离的思想。比如系统调度,一个比较简单的调度算法是,对每个进程赋予一个优先级,并让内核执行具有最高优先级的进程。这里,内核机制就是寻找最高的优先级进程并运行。而策略(赋予进程优先级)可以在用户态中的进程完成。在这种模式中,策略和机制是分离的,从而使内核变得更小。

客户-服务器模式

微内核思想的策略是把进程划分为两类:服务器,每个服务器用来提供服务;客户端,使用这些服务。这个模式就是所谓的 客户-服务器模式。

客户-服务器模式会有两种载体,一种情况是一台计算机既是客户又是服务器,在这种方式下,操作系统会有某种优化;但是普遍情况下是客户端和服务器在不同的机器上,它们通过局域网或广域网连接。

客户通过发送消息与服务器通信,客户端并不需要知道这些消息是在本地机器上处理,还是通过网络被送到远程机器上处理。对于客户端而言,这两种情形是一样的:都是发送请求并得到回应。

越来越多的系统,包括家里的 PC,都成为客户端,而在某地运行的大型机器则成为服务器。许多 web 就是以这种方式运行的。一台 PC 向某个服务器请求一个 Web 页面,服务器把 Web 页面返回给客户端,这就是典型的客服-服务器模式

文章参考:

《现代操作系统》第四版

baike.baidu.com/item/操作系统/1…

《Modern Operating System》forth edition

faculty.cs.niu.edu/~hutchins/c…

www.computerhope.com/jargon/c/cl…

《B站-操作系统》

www.bilibili.com/video/av955…

en.wikipedia.org/wiki/System…

c.biancheng.net/cpp/html/23…

www.dossier-andreas.net/software_ar…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

利用idea快速开始一个springboot项目

发表于 2020-02-27

一.利用idea创建一个springboot项目

1.找到File->New->Poject

2.左侧选择Spring Initializr Project SDK选好jdk(jdk的版本需要在1.8以上) 然后直接点击Next

3.设置好自己的包名和项目名 其他的默认不用修改 点击next

4.如下图:在左侧的Web中找到Spring Web勾选上(可能idea的版本不一样 有些是Web) 选择要依赖的版本 点击next

5.点击Finish !

6.可以把用不到的东西删掉

7.编写一个class 写一个简单的controller接口

8.可以找到启动类启动 也可以找到idea的启动窗口管理启动

9.运行成功

10.浏览器访问成功 一个简单springboot项目就创建好啦

二.将springboot项目打包部署到服务器上

1.先把项目打成一个jar包(利用maven):

可以在Terminal 输入命令:mvn clean package

也可以在idea中的Maven Project窗口中找到-》Lifecycle->package

2.成功之后在项目的target目录下就可以看到我们打的包了

3.把打好的jar放到服务器上 启动jar包:java -jar +包名

4.访问成功

总结:

springboot是一个快速开始开发的一个框架,它内置servlet容器,不需要我们额外安装配置。无论是本地还是服务器上都可以超快的将一个应用程序运行起来!!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

最通俗易懂的 Java 10 新特性讲解

发表于 2020-02-26

Hello world : ) 微信搜「 程序猿阿朗 」可以订阅。点赞再看,动力无限。

本文 Github.com/niumoo/Java… 和 程序猿阿朗博客 已经收录,有很多知识点和系列文章。

Java 10

自从 Java 9 开始,Oracle 调整了 Java 版本的发布策略,不再是之前的 N 年一个大版本,取而代之的是 6 个月一个小版本,三年一个大版本,这样可以让 Java 的最新改变迅速上线,而小版本的维护周期缩短到下个版本发布之前,大版本的维护周期则是 3 年之久。而 10 就是这么一个小版本,因为 Java 的后续版本基本都会包含之前新特性,所以还是把 Java 10 带来的改变单独写一写。

  1. JEP 322 - 基于时间的版本号

就像上面说的,Java 调整了发布策略,为了适应这种发布节奏,随着改变的还有 Java 版本号的记录方式。

版本号的新模式是:$FEATURE.$INTERIM.$UPDATE.$PATCH

  • $FEATURE :基于发布版本,如 Java 10 的 10 。
  • $INTERIM :问题修复和功能增强时 + 1,默认是 0 。
  • $UPDATE :在进行兼容更新,修复新功能安全问题时 +1。
  • $PATCH :特殊问题修复时 +1。

查看自己的 Java 10 版本。

1
2
3
4
java复制代码$ java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
  1. JEP 286 - 局部类型推断

JEP 286 提案让 Java 增加了局部类型推断(Local-Variable Type Inference)功能,这让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。这其实只是一个新的语法糖,底层并没有变化,在编译时就已经把 var 转化成具体的数据类型了,但是这样可以减少代码的编写。

你可以像下面这样使用 var 语法。

1
2
3
4
5
java复制代码var hashMap = new HashMap<String, String>();
hashMap.put("微信","wn8398");
var string = "hello java 10";
var stream = Stream.of(1, 2, 3, 4);
var list = new ArrayList<String>();

如果你反编译编译后的这段代码,你会发现还是熟悉的代码片段。

1
2
3
4
5
java复制代码HashMap<String, String> hashMap = new HashMap();
hashMap.put("微信", "wn8398");
String string = "hello java 10";
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
ArrayList<String> list = new ArrayList();

var 看似好用,其实也有很多限制,官方介绍了 var 只能用于下面的几种情况。

  1. 仅限带有初始化的程序的局部变量。
  2. for 循环或者增强for 循环中。
  3. for循环中的声明。

下面演示三种使用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public static void testVar() {
// 情况1,没有初始化会报错
// var list;
var list = List.of(1, 2, 3, 4);
// 情况2
for (var integer : list) {
System.out.println(integer);
}
// 情况3
for (var i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}

尽管对 var 的使用场景增加了很多限制,但在实际使用时你还是要注意,就像下面的代码,你可能一眼并不能看出 result 的数据类型。

1
2
java复制代码var query = "xxx";
var result = dbUtil.executeQuery(query);
  1. JEP 317 - 基于 Java 的 JIT 编译器(实验性)

这个功能让基于 Java 开发的 JIT 编译器 Graal 结合 Java 10 用在 Linux / x64 平台上,这是一个实验性的 JIT 编译器,有人说这也是 Java 10 中最具有未来感的引入。Graal 其实在 Java 9 中就已经引入了,它带来了 Java 中的 AOT (Ahead Of Time)编译,还支持多种语言,如 Js、Python、Ruby、R、以及其他基于 JVM (如 Java、Kotlin)的和基于 LLVM (如 C、C++)的语言。

想切换到 Graal 可以使用下面的 jvm 参数。

1
shell复制代码-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

这里面有一点我觉得很有意思,看这个图。

Graal 由 Java 编写

这就很有意思了,Graal 是 Java 语言编写的,用 Java 编写的编译器,然后用来将 Java 字节码编译机器代码。

Graal 官网:www.graalvm.org/

  1. JEP 310 - 类数据共享

JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,Java 10 引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。

  1. JEP 307 - G1 并行全GC

早在 Java 9 时就已经引入了 G1 垃圾收集器,G1 的优点很多。而在 Java 10 中还是做了小小调整,当 G1 的并发收集线程不能快速的完成全 GC 时,就会自动切换到并行收集,这可以减少在最坏情况下的 GC 速度。

  1. JEP 314 - Unicode 语言标签扩展

这个提案让 JDK 实现了最新的 LDML 规范中指定的更多的扩展。

主要增加了下面几个扩展方法。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of

尝试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码Currency chinaCurrency = Currency.getInstance(Locale.CHINA);
Currency usCurrency = Currency.getInstance(Locale.US);
System.out.println("本地货币:" + chinaCurrency);
System.out.println("US.货币:" + usCurrency);

String displayName = Locale.getDefault().getDisplayName();
String displayLanguage = Locale.getDefault().getDisplayLanguage();
String displayCountry = Locale.getDefault().getDisplayCountry();
System.out.println("本地名称:" + displayName);
System.out.println("本地语言:" + displayLanguage);
System.out.println("本地国家:" + displayCountry);
int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
System.out.println("本地每周第一天:" + firstDayOfWeek);

输出结果。

1
2
3
4
5
6
shell复制代码本地货币:CNY
US.货币:USD
本地名称:中文 (中国)
本地语言:中文
本地国家:中国
本地每周第一天:1
  1. API 更新

Java 10 删除了部分 API,也增加了一些实用方法。比如可以通过 Collection.copyOf 复制得到一个不可改变集合,即使原来的集合元素发生了变化也不会有影响。

1
2
3
4
5
6
7
8
java复制代码var list = new ArrayList<String>();
list.add("wechat");
list.add("wn8398");
List<String> copyList = List.copyOf(list);
list.add("test");
System.out.println(copyList);
// result
// [wechat, wn8398]

也为 Optional 增加了一个新的方法 orElseThrow。调用这个方法也可以获取到 optional 中的 value , 但是如果 value 为 null ,就会抛出异常。

另外在 Stream 最后收集数据的时候,Collectors 可以直接指定收集的集合为不可变集合,像下面这样。

1
2
java复制代码list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());

其他更新

Java 10 的更新内容不止这些,上面只是列举了常用的以及比较有意思的新特性。还有部分更新如:

  1. JEP 312:Thread-Local Handshakes,JVM 内部功能,可以提高 JVM 性能。
  2. JEP 313:删除了 javah 工具,说是删除,其实功能已经包含在 Java 8 中的 javac 里。
  3. JEP 316:让 JVM 可以在备用的存储设备(如 NV-DIMM)上分配堆内存,而不用更改程序代码。
  4. JEP 319:在JDK中提供一组默认的根证书颁发机构(CA)证书。

文章案例都已经上传到 Github:niumoo/jdk-feature

Hello world : ) 我是阿朗,一线技术工具人,认认真真写文章。

文章持续更新,可以关注公众号「 程序猿阿朗 」一起成长。
本文已经收到 Github.com/niumoo/Java… 和 「未读代码 」 ,有很多知识点和系列文章。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot图文教程6—SpringBoot中过滤器

发表于 2020-02-25

有天上飞的概念,就要有落地的实现

  • 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍
  • 先赞后看,养成习惯

SpringBoot 图文系列教程技术大纲

鹿老师的Java笔记

鹿老师的Java笔记

SpringBoot 图文教程系列文章目录

  1. SpringBoot图文教程1「概念+案例 思维导图」「基础篇上」
  2. SpringBoot图文教程2—日志的使用「logback」「log4j」
  3. SpringBoot图文教程3—「‘初恋’情结」集成Jsp
  4. SpringBoot图文教程4—SpringBoot 实现文件上传下载
  5. SpringBoot图文教程5—SpringBoot 中使用Aop

前言

过滤器是Javaweb非常基础的一个概念,属于Servlet的一部分。本文将会使用SpringBoot配置过滤器。在代码实现之前先通过一个简单的小案例解释一下什么是过滤器。

过滤器filter的概念:Servlet中提供的技术,可以过滤浏览器发出的请求,并且决定放行请求还是中断请求。

  • 浏览器对服务器的请求,会先经过过滤器,再到达服务器
  • 服务器对浏览器的响应,也会先经过过滤器,最后再响应给浏览器
  • 基于过滤器的机制,我们可以在过滤器中对请求和响应做一些处理,可以在过滤器中决定是否放行,例如:校验请求中有没有敏感字符串,校验有没有Session等。

举个栗子:

  • 过滤器就像疫情期间村口的路卡,你不管进村儿还是出村儿都需要经过路卡

  • 路卡的工作人员(过滤器)会在你经过的时候对你做一些”操作“,量量体温,问询一下,然后对你一些处理,放行或者让你原路返回。

SpringBoot中过滤器的使用

本文所有的内容,将会在下面的demo上进行操作。 请去Git仓库下载:gitee.com/bingqilinpe…

1.创建过滤器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
复制代码/**  
 * @WebFilter时Servlet3.0新增的注解,原先实现过滤器,需要在web.xml中进行配置,而现在通过此注解,启动启动时会自动扫描自动注册。
 *
 * @WebFilter filterName 定义注册的过滤器的名字
 * urlPatterns 定义要拦截所有的请求
 *
 */
@WebFilter(filterName="userFilter",urlPatterns={"/*"})
public class UserFilter implements Filter {
    Logger logger = LoggerFactory.getLogger(UserFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.info("请求处理");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
//        简单的session校验
        if (request.getSession().getAttribute("user")!=null) {
            //        请求放行
            filterChain.doFilter(request, response);
        }else {

        }

    }

    @Override
    public void destroy() {
        logger.info("销毁");
    }
}

2.SpringBoot配合过滤器扫描

@WebFilter时Servlet3.0新增的注解,原先实现过滤器,需要在web.xml中进行配置,而现在通过此注解,启动启动时会自动扫描自动注册。

在SpringBoot中配置过滤器扫描只需要在启动类加入@ServletComponentScan注解即可。

3.设置多个过滤器的执行顺序

你一定听过这样的一个词儿:过滤器链路,什么是过滤器链路呢?就是有多个过滤器,就像有多个路卡是一样一样的。当你有多个过滤器之后,就需要规定各个过滤器的指定顺序了。那么怎么指定过滤器的执行顺序呢?

SpringMvc中通过web.xml可以设置

在SpringBoot中可以通过FilterRegistrationBean进行过滤器的注册。

  1. 创建两个Filter类 删除@WebFilter注解即可


2. 在启动类中通过@Bean的方式配置FilterRegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
复制代码@SpringBootApplication  
@ServletComponentScan
public class AppRun {
    public static void main(String[] args) {
        //        参数:启动类类对象 主函数形参名
        SpringApplication.run(AppRun.class,args);
    }

    @Bean
    public FilterRegistrationBean  filterRegistrationBean(UserFilter userFilter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();

        registration.setFilter(userFilter);
        //过滤器名称
        registration.setName("userFilter");
        //拦截路径
        registration.addUrlPatterns("/*");
        //设置顺序
        registration.setOrder(10);
        return registration;
    }


    @Bean
    public FilterRegistrationBean  filterRegistrationBean2(User2Filter user2Filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
//        设置过滤器
        registration.setFilter(user2Filter);
        //过滤器名称
        registration.setName("user2Filter");
        //拦截路径
        registration.addUrlPatterns("/*");
        //设置顺序
        registration.setOrder(20);
        return registration;
    }

    @Bean
    public UserFilter userFilter(){
        return new UserFilter();
    }

    @Bean
    public User2Filter user2Filter(){
        return new User2Filter();
    }
}

注册多个时,就注册多个FilterRegistrationBean即可。启动之后的效果如下:

总结

以上就是SpringBoot中过滤器的简单使用,本文为基础篇,所以关于过滤器的应用会在后续文章中写出。

恭喜你完成了本章的学习,为你鼓掌!如果本文对你有帮助,请帮忙点赞,评论,转发,这对作者很重要,谢谢。

让我们再次回顾本文的学习目标

  • 掌握SpringBoot中过滤器的使用

要掌握SpringBoot更多的用法,请持续关注本系列教程。

下面体贴的我给朋友萌还准备了一些 自测面试题和项目案例,希望你能够成热打铁,将知识夯扎实。

上期自测面试题答案

见面试题集锦 gitee.com/bingqilinpe…

自测面试题(答案见下期)

无

上期自测实现项目小案例答案

见码云仓库 gitee.com/bingqilinpe…

自测实现项目小案例(答案见下期)

本次需求:

练习文中demo

求关注,求点赞,求转发

欢迎关注本人公众号:鹿老师的Java笔记,将在长期更新Java技术图文教程和视频教程,Java学习经验,Java面试经验以及Java实战开发经验。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

代码演示Mybatis-Generator 扩展自定义生成

发表于 2020-02-25

Mybatis-Generator 可自动生成Model、Dao、Mapper代码,但其自带生成的代码存在以下问题:

  • 生成的注释不是我们想要的,我们期望的是根据数据库表、字段生成不同的注释;
  • 分页代码生成缺失,每个公司的分页方式不同,尤其是老久项目或已发布API,不能随意变动,那么如何自适应分页代码生成;
  • Mapper.xml没有group by相关代码生成;
  • 重复生成代码时,Mapper.xml并不是覆盖原代码,而是对内容进行了追加;
  • 序列化,mybatis-generator内置了SerializablePlugin,但仅对Model,并没有对 Example序列化,在一些开发中是不够的;
  • 对Service Layer代码没有生成。

实际上,mybatis-generator提供了PluginAdapter供我们来继承,进行个性化的一些扩展(Plugin的相关内容是阅读本文的前置条件)如果不熟悉的同学,请自行补充,本文不对其进行相关介绍)。同时,本文不可能涵盖所有业务所需的扩展点,但是基本样板已有,可参考本文代码继续进行扩展。

一、注释的自定义生成

根据数据库表或字段的COMMENT生成注释。@Date 生成的时间可根据需要自己定义格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
复制代码package run.override;
import java.util.Date;
import java.util.Properties;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.InnerClass;
import org.mybatis.generator.api.dom.java.InnerEnum;
import org.mybatis.generator.api.dom.java.JavaElement;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.internal.DefaultCommentGenerator;
import org.mybatis.generator.internal.util.StringUtility;
/**
* Comment Generator
* @ClassName CommentGenerator
* @Description
* @author Marvis
*/
public class CommentGenerator extends DefaultCommentGenerator {
private Properties properties;
private boolean suppressDate;
private boolean suppressAllComments;

public CommentGenerator() {
this.properties = new Properties();
this.suppressDate = false;
this.suppressAllComments = false;
}

public void addJavaFileComment(CompilationUnit compilationUnit) {

compilationUnit.addFileCommentLine("/*** copyright (c) 2019 Marvis ***/");
}
/**
* XML file Comment
*/
public void addComment(XmlElement xmlElement) {
if (this.suppressAllComments) {
return;
}

}

public void addRootComment(XmlElement rootElement) {
}

public void addConfigurationProperties(Properties properties) {
this.properties.putAll(properties);

this.suppressDate = StringUtility.isTrue(properties.getProperty("suppressDate"));

this.suppressAllComments = StringUtility.isTrue(properties.getProperty("suppressAllComments"));
}

protected void addJavadocTag(JavaElement javaElement, boolean markAsDoNotDelete) {
StringBuilder sb = new StringBuilder();
sb.append(" * ");
sb.append("@date");
String s = getDateString();
if (s != null) {
sb.append(' ');
sb.append(s);
}
javaElement.addJavaDocLine(sb.toString());
}

protected String getDateString() {
if (this.suppressDate) {
return null;
}
return new Date().toString();
}
/**
* Comment of Example inner class(GeneratedCriteria ,Criterion)
*/
public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable) {
if (this.suppressAllComments) {
return;
}

innerClass.addJavaDocLine("/**");
innerClass.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable().getDomainObjectName()+ "<p/>");
innerClass.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable().toString());
addJavadocTag(innerClass, false);
innerClass.addJavaDocLine(" */");
}

public void addEnumComment(InnerEnum innerEnum, IntrospectedTable introspectedTable) {
if (this.suppressAllComments) {
return;
}

StringBuilder sb = new StringBuilder();

innerEnum.addJavaDocLine("/**");
innerEnum.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable().getAlias()+ "<p/>");
innerEnum.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable());
innerEnum.addJavaDocLine(sb.toString());

addJavadocTag(innerEnum, false);

innerEnum.addJavaDocLine(" */");
}
/**
* entity filed Comment
*/
public void addFieldComment(Field field, IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (this.suppressAllComments) {
return;
}

// if(introspectedColumn.getRemarks() != null && !introspectedColumn.getRemarks().trim().equals(""))

field.addJavaDocLine("/**");
field.addJavaDocLine(" * " + introspectedColumn.getRemarks());
field.addJavaDocLine(" * @author " );
field.addJavaDocLine(" * @date " + getDateString() );
field.addJavaDocLine(" * @return");
field.addJavaDocLine(" */");
}
/**
* Comment of EXample filed
*/
public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
if (this.suppressAllComments) {
return;
}
field.addJavaDocLine("/**");
addJavadocTag(field, false);
field.addJavaDocLine(" */");
}
/**
* Comment of Example method
*/
public void addGeneralMethodComment(Method method, IntrospectedTable introspectedTable) {
if (this.suppressAllComments) {
return;
}
}
/**
*
* entity Getter Comment
*/
public void addGetterComment(Method method, IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (this.suppressAllComments) {
return;
}
method.addJavaDocLine("/**");


method.addJavaDocLine(" * @return " + introspectedTable.getFullyQualifiedTable().getAlias() + " : " + introspectedColumn.getRemarks());
method.addJavaDocLine(" */");
}

public void addSetterComment(Method method, IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (this.suppressAllComments) {
return;
}

StringBuilder sb = new StringBuilder();

method.addJavaDocLine("/**");

Parameter parm = (Parameter) method.getParameters().get(0);
sb.append(" * @param ");
sb.append(parm.getName());
sb.append(" : ");
sb.append(introspectedColumn.getRemarks());
method.addJavaDocLine(sb.toString());
method.addJavaDocLine(" */");
}

/**
* Comment of Example inner class(Criteria)
*/
public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
if (this.suppressAllComments) {
return;
}

innerClass.addJavaDocLine("/**");
innerClass.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable().getAlias()+ "<p/>");
innerClass.addJavaDocLine(" * " + introspectedTable.getFullyQualifiedTable().toString());
addJavadocTag(innerClass, markAsDoNotDelete);

innerClass.addJavaDocLine(" */");
}

Model 类注释(表的描述): MySQL。

1)EntityCommentPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
复制代码package run.override.model;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.List;

import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.internal.JDBCConnectionFactory;
import org.mybatis.generator.internal.util.StringUtility;

/**
* Comment of Entity,only support MySQL
* @ClassName CommentPlugin
* @Description
* @author Marvis
*/
public class EntityCommentPlugin extends PluginAdapter {

@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
addModelClassComment(topLevelClass, introspectedTable);
return super.modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
}

@Override
public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {

addModelClassComment(topLevelClass, introspectedTable);
return super.modelRecordWithBLOBsClassGenerated(topLevelClass, introspectedTable);
}

protected void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
String tableComment = getTableComment(table);

topLevelClass.addJavaDocLine("/**");
if(StringUtility.stringHasValue(tableComment))
topLevelClass.addJavaDocLine(" * " + tableComment + "<p/>");
topLevelClass.addJavaDocLine(" * " + table.toString() + "<p/>");
topLevelClass.addJavaDocLine(" * @date " + new Date().toString());
topLevelClass.addJavaDocLine(" *");
topLevelClass.addJavaDocLine(" */");
}

/**
* @author Marvis
* @date Jul 13, 2017 4:39:52 PM
* @param table
*/
private String getTableComment(FullyQualifiedTable table) {
String tableComment = "";
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try {
JDBCConnectionFactory jdbc = new JDBCConnectionFactory(context.getJdbcConnectionConfiguration());
connection = jdbc.getConnection();
statement = connection.createStatement();
rs = statement.executeQuery("SHOW CREATE TABLE " + table.getIntrospectedTableName());

if (rs != null && rs.next()) {
String createDDL = rs.getString(2);
int index = createDDL.indexOf("COMMENT='");
if (index < 0) {
tableComment = "";
} else {
tableComment = createDDL.substring(index + 9);
tableComment = tableComment.substring(0, tableComment.length() - 1);
}
}

} catch (SQLException e) {

} finally {
closeConnection(connection, statement, rs);
}
return tableComment;
}
/**
*
* @author Marvis
* @date Jul 13, 2017 4:45:26 PM
* @param connection
* @param statement
* @param rs
*/
private void closeConnection(Connection connection, Statement statement, ResultSet rs) {
try {
if (null != rs)
rs.close();
} catch (SQLException e) {

e.printStackTrace();
} finally {
try {
if (statement != null)
statement.close();
} catch (Exception e) {
e.printStackTrace();

} finally {

try {
if (connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* This plugin is always valid - no properties are required
*/
@Override
public boolean validate(List<String> warnings) {
return true;
}
}

二、分页和分组代码生成

这里,我对Dao Model进行了通用方法的抽取,建立通用基类。同时,对其进行了一些扩展,增加分页和分组。

先对基类进行介绍。

1)BaseMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
复制代码package cn.xxx.core.base.dao;

import java.util.List;

import org.apache.ibatis.annotations.Param;

public interface BaseMapper<T, Example, ID> {

long countByExample(Example example);

int deleteByExample(Example example);

int deleteByPrimaryKey(ID id);

int insert(T record);

int insertSelective(T record);

List<T> selectByExample(Example example);

T selectByPrimaryKey(ID id);

int updateByExampleSelective(@Param("record") T record, @Param("example") Example example);

int updateByExample(@Param("record") T record, @Param("example") Example example);

int updateByPrimaryKeySelective(T record);

int updateByPrimaryKey(T record);

}

2)BaseExample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
复制代码package cn.xxx.core.base.model;
/**
* BaseExample 基类
* @ClassName BaseExample
* @Description 增加分页参数
* @author Marvis
* @date Jul 31, 2017 11:26:53 AM
*/
public abstract class BaseExample {

protected PageInfo pageInfo;
protected String groupByClause;

public PageInfo getPageInfo() {
return pageInfo;
}

public void setPageInfo(PageInfo pageInfo) {
this.pageInfo = pageInfo;
}

public String getGroupByClause() {
return groupByClause;
}

public void setGroupByClause(String groupByClause) {
this.groupByClause = groupByClause;
}

}

3)PageInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
复制代码package cn.xxx.core.base.model;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
* 分页查询参数类
*
* @author
*
*/
public class PageInfo {

public static final int Default_PageSize = 20;

// 当前页码
protected int currentPage = 1;

// 总页数
protected int totalPage;

// 总记录数
protected int totalCount;

// 每页条数
protected int pageSize = Default_PageSize;

// 开始
protected int pageBegin = 0;

// 结束
protected int pageEnd = 20;

/**
* bean起始坐标(不包含)
*/
private Integer pageBeginId = null;

public static final String PageQuery_classname = "pageInfo";

/**
* 将分布参数传入处理,最终计算出当前页码PageQuery_currPage,开始坐标PageQuery_star,
* 结束坐标PageQuery_end,总页数PageQuery_Psize
* <p/>
* 页数从1开始计数
*
* @param totalCount
* 记录总数
* @param pageSize
* 每页显示个数
* @param currentPage
* 当前页码
*/
public void setPageParams(int totalCount, int pageSize, int currentPage) {

this.totalPage = pageSize == 0 ? 1 : (int) Math.ceil((double) totalCount / (double) pageSize);

this.totalCount = totalCount;
this.pageSize = pageSize;
this.currentPage = currentPage;

float Psize_l = totalCount / (float) (this.pageSize);
if (currentPage < 2) {
currentPage = 1;
pageBegin = 0;
} else if (currentPage > Psize_l) {
if (Psize_l == 0) {
currentPage = 1;
} else {
currentPage = (int) Math.ceil(Psize_l);
}

pageBegin = (currentPage - 1) * this.pageSize;
} else {
pageBegin = (currentPage - 1) * this.pageSize;
}
pageSize = (int) Math.ceil(Psize_l);
this.pageEnd = currentPage * this.pageSize;

if (this.currentPage <= 0 || this.currentPage > this.totalPage)
this.pageSize = 0;
}

/**
* 将分布参数传入处理,最终计算出当前页码PageQuery_currPage,开始坐标PageQuery_star,
* 结束坐标PageQuery_end,总页数PageQuery_Psize
*
* @param infoCount
* 记录总数
*/
public void setPageParams(int totalCount) {
this.setPageParams(totalCount, this.pageSize, this.currentPage);
}

@Override
public String toString() {
return "PageInfo [currentPage=" + currentPage + ", totalPage=" + totalPage + ", totalCount=" + totalCount
+ ", pageSize=" + pageSize + ", pageBegin=" + pageBegin + ", pageEnd=" + pageEnd + ", pageBeginId="
+ pageBeginId + "]";
}

public int getCurrentPage() {
return currentPage;
}

public int getTotalPage() {
return totalPage;
}

public int getTotalCount() {
return totalCount;
}

/**
* 每页显示个数
*/
public int getPageSize() {
return pageSize;
}

@JsonIgnore
public int getPageBegin() {
return pageBegin;
}

@JsonIgnore
public int getPageEnd() {
return pageEnd;
}

/**
* bean起始id(不包含)
*/
@JsonIgnore
public Integer getPageBeginId() {
return pageBeginId;
}

/**
* 请求页
*/
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}

/**
* 每页显示个数
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}

4)PaginationPlugin

分页扩展。并且Example继承BaseExample。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
复制代码package run.override.pagination;

import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;

import run.override.mapper.SqlMapIsMergeablePlugin;
import run.override.proxyFactory.FullyQualifiedJavaTypeProxyFactory;

public class PaginationPlugin extends SqlMapIsMergeablePlugin {
@Override
public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

FullyQualifiedJavaType baseExampleType = FullyQualifiedJavaTypeProxyFactory.getBaseExampleInstance();
topLevelClass.setSuperClass(baseExampleType);

topLevelClass.addImportedType(baseExampleType);
return super.modelExampleClassGenerated(topLevelClass, introspectedTable);
}

@Override
public boolean sqlMapSelectByExampleWithBLOBsElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {

XmlElement isNotNullElement1 = new XmlElement("if");
isNotNullElement1.addAttribute(new Attribute("test", "groupByClause != null"));
isNotNullElement1.addElement(new TextElement("group by ${groupByClause}"));
element.addElement(5, isNotNullElement1);
XmlElement isNotNullElement = new XmlElement("if");
isNotNullElement.addAttribute(new Attribute("test", "pageInfo != null"));
isNotNullElement.addElement(new TextElement("limit #{pageInfo.pageBegin} , #{pageInfo.pageSize}"));
element.addElement(isNotNullElement);

return super.sqlMapUpdateByExampleWithBLOBsElementGenerated(element, introspectedTable);
}

@Override
public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {

XmlElement isNotNullElement1 = new XmlElement("if");
isNotNullElement1.addAttribute(new Attribute("test", "groupByClause != null"));
isNotNullElement1.addElement(new TextElement("group by ${groupByClause}"));
element.addElement(5, isNotNullElement1);

XmlElement isNotNullElement = new XmlElement("if");
isNotNullElement.addAttribute(new Attribute("test", "pageInfo != null"));
isNotNullElement.addElement(new TextElement("limit #{pageInfo.pageBegin} , #{pageInfo.pageSize}"));
element.addElement(isNotNullElement);

return super.sqlMapUpdateByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
}

@Override
public boolean sqlMapCountByExampleElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {

XmlElement answer = new XmlElement("select");

String fqjt = introspectedTable.getExampleType();

answer.addAttribute(new Attribute("id", introspectedTable.getCountByExampleStatementId()));
answer.addAttribute(new Attribute("parameterType", fqjt));
answer.addAttribute(new Attribute("resultType", "java.lang.Integer"));

this.context.getCommentGenerator().addComment(answer);

StringBuilder sb = new StringBuilder();
sb.append("select count(1) from ");
sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());

XmlElement ifElement = new XmlElement("if");
ifElement.addAttribute(new Attribute("test", "_parameter != null"));
XmlElement includeElement = new XmlElement("include");
includeElement.addAttribute(new Attribute("refid", introspectedTable.getExampleWhereClauseId()));
ifElement.addElement(includeElement);

element.getElements().clear();
element.getElements().add(new TextElement(sb.toString()));
element.getElements().add(ifElement);
return super.sqlMapUpdateByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
}
}

5)FullyQualifiedJavaTypeProxyFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
复制代码package run.override.proxyFactory;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;

public class FullyQualifiedJavaTypeProxyFactory extends FullyQualifiedJavaType{

private static FullyQualifiedJavaType pageInfoInstance = new FullyQualifiedJavaType("cn.xxx.core.base.model.PageInfo");
private static FullyQualifiedJavaType baseExampleInstance = new FullyQualifiedJavaType("cn.xxx.core.base.model.BaseExample");
private static FullyQualifiedJavaType baseMapperInstance = new FullyQualifiedJavaType("cn.xxx.core.base.dao.BaseMapper");
private static FullyQualifiedJavaType baseServiceInstance = new FullyQualifiedJavaType("cn.xxx.core.base.service.BaseService");
private static FullyQualifiedJavaType baseServiceImplInstance = new FullyQualifiedJavaType("cn.xxx.core.base.service.impl.BaseServiceImpl");

public FullyQualifiedJavaTypeProxyFactory(String fullTypeSpecification) {
super(fullTypeSpecification);
}

public static final FullyQualifiedJavaType getPageInfoInstanceInstance() {

return pageInfoInstance;
}
public static final FullyQualifiedJavaType getBaseExampleInstance() {

return baseExampleInstance;
}

public static final FullyQualifiedJavaType getBaseMapperInstance() {

return baseMapperInstance;
}
public static final FullyQualifiedJavaType getBaseServiceInstance() {

return baseServiceInstance;
}
public static final FullyQualifiedJavaType getBaseServiceImplInstance() {

return baseServiceImplInstance;
}
}

三、Dao 生成代码简化

1)ClientDaoPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
复制代码package run.override.dao;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;

import run.override.model.EntityCommentPlugin;
import run.override.proxyFactory.FullyQualifiedJavaTypeProxyFactory;

/**
* javaClient("XMLMAPPER") extended
*
* @ClassName ClientDaoPlugin
* @Description Mapper.java
* @author Marvis
*/
public class ClientDaoPlugin extends EntityCommentPlugin {

@Override
public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {

JavaTypeResolver javaTypeResolver = new JavaTypeResolverDefaultImpl();
FullyQualifiedJavaType calculateJavaType = javaTypeResolver
.calculateJavaType(introspectedTable.getPrimaryKeyColumns().get(0));

FullyQualifiedJavaType superInterfaceType = new FullyQualifiedJavaType(
new StringBuilder("BaseMapper<")
.append(introspectedTable.getBaseRecordType())
.append(",")
.append(introspectedTable.getExampleType())
.append(",")
.append(calculateJavaType.getShortName())
.append(">")
.toString()
);
FullyQualifiedJavaType baseMapperInstance = FullyQualifiedJavaTypeProxyFactory.getBaseMapperInstance();

interfaze.addSuperInterface(superInterfaceType);
interfaze.addImportedType(baseMapperInstance);

List<Method> changeMethods = interfaze.getMethods().stream()
.filter(method -> method.getName().endsWith("WithBLOBs")
|| method.getReturnType().toString().endsWith("WithBLOBs")
|| Arrays.toString(method.getParameters().toArray()).contains("WithBLOBs"))
.collect(Collectors.toList());

interfaze.getMethods().retainAll(changeMethods);

if (changeMethods.isEmpty())
interfaze.getImportedTypes().removeIf(javaType -> javaType.getFullyQualifiedName().equals("java.util.List")
|| javaType.getFullyQualifiedName().equals("org.apache.ibatis.annotations.Param"));

return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}

}

四、修正

重复生成时Mapper.xml不是覆盖原代码,而是对内容进行了追加。

1)SqlMapIsMergeablePlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码package run.override.mapper;

import org.mybatis.generator.api.GeneratedXmlFile;
import org.mybatis.generator.api.IntrospectedTable;
import run.override.dao.ClientDaoPlugin;

public class SqlMapIsMergeablePlugin extends ClientDaoPlugin {

@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
//重新生成代码,xml内容覆盖
sqlMap.setMergeable(false);
return super.sqlMapGenerated(sqlMap, introspectedTable);
}
}

五、序列化自定义扩展

增加Example的序列化,并增加@SuppressWarnings("serial")注解。

1)SerializablePlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
复制代码package run.override;

import java.util.List;
import java.util.Properties;

import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.TopLevelClass;

public class SerializablePlugin extends PluginAdapter {
private FullyQualifiedJavaType serializable;
private FullyQualifiedJavaType gwtSerializable;
private boolean addGWTInterface;
private boolean suppressJavaInterface;

public SerializablePlugin() {
this.serializable = new FullyQualifiedJavaType("java.io.Serializable");
this.gwtSerializable = new FullyQualifiedJavaType("com.google.gwt.user.client.rpc.IsSerializable");
}

@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
this.addGWTInterface = Boolean.valueOf(properties.getProperty("addGWTInterface")).booleanValue();
this.suppressJavaInterface = Boolean.valueOf(properties.getProperty("suppressJavaInterface")).booleanValue();
}
@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
makeSerializable(topLevelClass, introspectedTable);
return true;
}
@Override
public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
makeSerializable(topLevelClass, introspectedTable);
return true;
}
@Override
public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {
makeSerializable(topLevelClass, introspectedTable);
return true;
}

@Override
public boolean modelExampleClassGenerated(TopLevelClass topLevelClass,IntrospectedTable introspectedTable){
makeSerializable(topLevelClass, introspectedTable);
return true;
}

protected void makeSerializable(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
if (this.addGWTInterface) {
topLevelClass.addImportedType(this.gwtSerializable);
topLevelClass.addSuperInterface(this.gwtSerializable);
}

if (!(this.suppressJavaInterface)) {
topLevelClass.addImportedType(this.serializable);
topLevelClass.addSuperInterface(this.serializable);
topLevelClass.addAnnotation("@SuppressWarnings(\"serial\")");

}
}

/**
* This plugin is always valid - no properties are required
*/
@Override
public boolean validate(List<String> warnings) {
return true;
}
}

六、服务层代码自定义生成

重写Context,ConfigurationParser,MyBatisGeneratorConfigurationParser,增加服务层生成逻辑。

先对Service基类进行介绍。

1)BaseService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
复制代码package cn.xxx.core.base.service;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import cn.xxx.core.base.model.BaseExample;
import cn.xxx.core.base.model.PageInfo;

public interface BaseService<T, Example extends BaseExample, ID> {

long countByExample(Example example);

int deleteByExample(Example example);

int deleteByPrimaryKey(ID id);

int insert(T record);

int insertSelective(T record);

List<T> selectByExample(Example example);

/**
* return T object
* @author Marvis
* @date May 23, 2018 11:37:11 AM
* @param example
* @return
*/
T selectByCondition(Example example);
/**
* if pageInfo == null<p/>
* then return result of selectByExample(example)
* @author Marvis
* @date Jul 13, 2017 5:24:35 PM
* @param example
* @param pageInfo
* @return
*/
List<T> selectByPageExmple(Example example, PageInfo pageInfo);

T selectByPrimaryKey(ID id);

int updateByExampleSelective(@Param("record") T record, @Param("example") Example example);

int updateByExample(@Param("record") T record, @Param("example") Example example);

int updateByPrimaryKeySelective(T record);

int updateByPrimaryKey(T record);
}

2)BaseServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
复制代码package cn.xxx.core.base.service.impl;

import java.util.List;

import cn.xxx.core.base.dao.BaseMapper;
import cn.xxx.core.base.model.BaseExample;
import cn.xxx.core.base.model.PageInfo;
import cn.xxx.core.base.service.BaseService;

public abstract class BaseServiceImpl<T, Example extends BaseExample, ID> implements BaseService<T, Example, ID> {

private BaseMapper<T, Example, ID> mapper;

public void setMapper(BaseMapper<T, Example, ID> mapper) {
this.mapper = mapper;
}

public long countByExample(Example example) {
return mapper.countByExample(example);
}

@Override
public int deleteByExample(Example example) {
return mapper.deleteByExample(example);
}

@Override
public int deleteByPrimaryKey(ID id) {
return mapper.deleteByPrimaryKey(id);
}

@Override
public int insert(T record) {
return mapper.insert(record);
}

@Override
public int insertSelective(T record) {
return mapper.insertSelective(record);
}

@Override
public List<T> selectByExample(Example example) {
return mapper.selectByExample(example);
}
@Override
public T selectByCondition(Example example) {

List<T> datas = selectByExample(example);
return datas != null && datas.size() == 0 ? null : datas.get(0);
}

@Override
public List<T> selectByPageExmple(Example example, PageInfo pageInfo) {

if(pageInfo != null){

example.setPageInfo(pageInfo);
pageInfo.setPageParams(Long.valueOf(this.countByExample(example)).intValue());
}
return this.selectByExample(example);
}

@Override
public T selectByPrimaryKey(ID id) {
return mapper.selectByPrimaryKey(id);
}

@Override
public int updateByExampleSelective(T record, Example example) {
return mapper.updateByExampleSelective(record, example);
}

@Override
public int updateByExample(T record, Example example) {
return mapper.updateByExample(record, example);
}

@Override
public int updateByPrimaryKeySelective(T record) {
return mapper.updateByPrimaryKeySelective(record);
}

@Override
public int updateByPrimaryKey(T record) {
return mapper.updateByPrimaryKey(record);
}
}

3)ServiceLayerPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
复制代码package run.override.service;

import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;
import run.override.pagination.PaginationPlugin;
import run.override.proxyFactory.FullyQualifiedJavaTypeProxyFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class ServiceLayerPlugin extends PaginationPlugin {
/**
* 生成额外java文件
*/
@Override
public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {

ContextOverride context = (ContextOverride) introspectedTable.getContext();

ServiceGeneratorConfiguration serviceGeneratorConfiguration;

if ((serviceGeneratorConfiguration = context.getServiceGeneratorConfiguration()) == null)
return null;

String targetPackage = serviceGeneratorConfiguration.getTargetPackage();
String targetProject = serviceGeneratorConfiguration.getTargetProject();
String implementationPackage = serviceGeneratorConfiguration.getImplementationPackage();

CompilationUnit addServiceInterface = addServiceInterface(introspectedTable, targetPackage);
CompilationUnit addServiceImplClazz = addServiceImplClazz(introspectedTable, targetPackage,
implementationPackage);

GeneratedJavaFile gjfServiceInterface = new GeneratedJavaFile(addServiceInterface, targetProject,
this.context.getProperty("javaFileEncoding"), this.context.getJavaFormatter());
GeneratedJavaFile gjfServiceImplClazz = new GeneratedJavaFile(addServiceImplClazz, targetProject,
this.context.getProperty("javaFileEncoding"), this.context.getJavaFormatter());

List<GeneratedJavaFile> list = new ArrayList<>();
list.add(gjfServiceInterface);
list.add(gjfServiceImplClazz);
return list;
}

protected CompilationUnit addServiceInterface(IntrospectedTable introspectedTable, String targetPackage) {

String entityClazzType = introspectedTable.getBaseRecordType();
String serviceSuperPackage = targetPackage;

String entityExampleClazzType = introspectedTable.getExampleType();
String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();

JavaTypeResolver javaTypeResolver = new JavaTypeResolverDefaultImpl();

FullyQualifiedJavaType calculateJavaType = javaTypeResolver
.calculateJavaType(introspectedTable.getPrimaryKeyColumns().get(0));

StringBuilder builder = new StringBuilder();

FullyQualifiedJavaType superInterfaceType = new FullyQualifiedJavaType(

builder.append("BaseService<")
.append(entityClazzType)
.append(",")
.append(entityExampleClazzType)
.append(",")
.append(calculateJavaType.getShortName()).append(">").toString());

Interface serviceInterface = new Interface(
builder.delete(0, builder.length())
.append(serviceSuperPackage)
.append(".")
.append(domainObjectName)
.append("Service")
.toString()
);

serviceInterface.addSuperInterface(superInterfaceType);
serviceInterface.setVisibility(JavaVisibility.PUBLIC);

FullyQualifiedJavaType baseServiceInstance = FullyQualifiedJavaTypeProxyFactory.getBaseServiceInstance();
FullyQualifiedJavaType modelJavaType = new FullyQualifiedJavaType(entityClazzType);
FullyQualifiedJavaType exampleJavaType = new FullyQualifiedJavaType(entityExampleClazzType);
serviceInterface.addImportedType(baseServiceInstance);
serviceInterface.addImportedType(modelJavaType);
serviceInterface.addImportedType(exampleJavaType);
serviceInterface.addFileCommentLine("/*** copyright (c) 2019 Marvis ***/");


this.additionalServiceMethods(introspectedTable, serviceInterface);
return serviceInterface;
}

protected CompilationUnit addServiceImplClazz(IntrospectedTable introspectedTable, String targetPackage,
String implementationPackage) {

String entityClazzType = introspectedTable.getBaseRecordType();
String serviceSuperPackage = targetPackage;
String serviceImplSuperPackage = implementationPackage;
String entityExampleClazzType = introspectedTable.getExampleType();

String javaMapperType = introspectedTable.getMyBatis3JavaMapperType();

String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();

JavaTypeResolver javaTypeResolver = new JavaTypeResolverDefaultImpl();
FullyQualifiedJavaType calculateJavaType = javaTypeResolver
.calculateJavaType(introspectedTable.getPrimaryKeyColumns().get(0));

StringBuilder builder = new StringBuilder();

FullyQualifiedJavaType superClazzType = new FullyQualifiedJavaType(

builder.append("BaseServiceImpl<")
.append(entityClazzType)
.append(",")
.append(entityExampleClazzType)
.append(",")
.append(calculateJavaType.getShortName()).append(">")
.toString()
);

FullyQualifiedJavaType implInterfaceType = new FullyQualifiedJavaType(

builder.delete(0, builder.length())
.append(serviceSuperPackage)
.append(".")
.append(domainObjectName)
.append("Service")
.toString()
);

TopLevelClass serviceImplClazz = new TopLevelClass(

builder.delete(0, builder.length())
.append(serviceImplSuperPackage)
.append(".")
.append(domainObjectName)
.append("ServiceImpl")
.toString()
);

serviceImplClazz.addSuperInterface(implInterfaceType);
serviceImplClazz.setSuperClass(superClazzType);
serviceImplClazz.setVisibility(JavaVisibility.PUBLIC);
serviceImplClazz.addAnnotation("@Service");

FullyQualifiedJavaType baseServiceInstance = FullyQualifiedJavaTypeProxyFactory.getBaseServiceImplInstance();
FullyQualifiedJavaType modelJavaType = new FullyQualifiedJavaType(entityClazzType);
FullyQualifiedJavaType exampleJavaType = new FullyQualifiedJavaType(entityExampleClazzType);
serviceImplClazz
.addImportedType(new FullyQualifiedJavaType("org.springframework.beans.factory.annotation.Autowired"));
serviceImplClazz.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Service"));
serviceImplClazz.addImportedType(baseServiceInstance);
serviceImplClazz.addImportedType(modelJavaType);
serviceImplClazz.addImportedType(exampleJavaType);
serviceImplClazz.addImportedType(implInterfaceType);

FullyQualifiedJavaType logType = new FullyQualifiedJavaType("org.slf4j.Logger");
FullyQualifiedJavaType logFactoryType = new FullyQualifiedJavaType("org.slf4j.LoggerFactory");
Field logField = new Field();
logField.setVisibility(JavaVisibility.PRIVATE);
logField.setStatic(true);
logField.setFinal(true);
logField.setType(logType);
logField.setName("logger");
logField.setInitializationString(
builder.delete(0, builder.length())
.append("LoggerFactory.getLogger(")
.append(domainObjectName)
.append("ServiceImpl.class)")
.toString()
);

logField.addAnnotation("");
logField.addAnnotation("@SuppressWarnings(\"unused\")");
serviceImplClazz.addField(logField);
serviceImplClazz.addImportedType(logType);
serviceImplClazz.addImportedType(logFactoryType);

String mapperName = builder.delete(0, builder.length())
.append(Character.toLowerCase(domainObjectName.charAt(0)))
.append(domainObjectName.substring(1))
.append("Mapper")
.toString();

FullyQualifiedJavaType JavaMapperType = new FullyQualifiedJavaType(javaMapperType);

Field mapperField = new Field();
mapperField.setVisibility(JavaVisibility.PUBLIC);
mapperField.setType(JavaMapperType);// Mapper.java
mapperField.setName(mapperName);
mapperField.addAnnotation("@Autowired");
serviceImplClazz.addField(mapperField);
serviceImplClazz.addImportedType(JavaMapperType);

Method mapperMethod = new Method();
mapperMethod.setVisibility(JavaVisibility.PUBLIC);
mapperMethod.setName("setMapper");
mapperMethod.addBodyLine("super.setMapper(" + mapperName + ");");
mapperMethod.addAnnotation("@Autowired");

serviceImplClazz.addMethod(mapperMethod);
serviceImplClazz.addFileCommentLine("/*** copyright (c) 2019 Marvis ***/");

serviceImplClazz
.addImportedType(new FullyQualifiedJavaType("org.springframework.beans.factory.annotation.Autowired"));

this.additionalServiceImplMethods(introspectedTable, serviceImplClazz, mapperName);

return serviceImplClazz;
}

protected void additionalServiceMethods(IntrospectedTable introspectedTable, Interface serviceInterface) {

if (this.notHasBLOBColumns(introspectedTable))
return;

introspectedTable.getGeneratedJavaFiles().stream().filter(file -> file.getCompilationUnit().isJavaInterface()
&& file.getCompilationUnit().getType().getShortName().endsWith("Mapper")).map(GeneratedJavaFile::getCompilationUnit).forEach(
compilationUnit -> ((Interface) compilationUnit).getMethods().forEach(

m -> serviceInterface.addMethod(this.additionalServiceLayerMethod(serviceInterface, m))));
}

protected void additionalServiceImplMethods(IntrospectedTable introspectedTable, TopLevelClass clazz,
String mapperName) {

if (this.notHasBLOBColumns(introspectedTable))
return;

introspectedTable.getGeneratedJavaFiles().stream().filter(file -> file.getCompilationUnit().isJavaInterface()
&& file.getCompilationUnit().getType().getShortName().endsWith("Mapper")).map(GeneratedJavaFile::getCompilationUnit).forEach(
compilationUnit -> ((Interface) compilationUnit).getMethods().forEach(m -> {

Method serviceImplMethod = this.additionalServiceLayerMethod(clazz, m);
serviceImplMethod.addAnnotation("@Override");
serviceImplMethod.addBodyLine(this.generateBodyForServiceImplMethod(mapperName, m));

clazz.addMethod(serviceImplMethod);
}));
}


private boolean notHasBLOBColumns(IntrospectedTable introspectedTable) {
return !introspectedTable.hasBLOBColumns();
}

private Method additionalServiceLayerMethod(CompilationUnit compilation, Method m) {

Method method = new Method();
method.setVisibility(JavaVisibility.PUBLIC);
method.setName(m.getName());

List<Parameter> parameters = m.getParameters();

method.getParameters().addAll(parameters.stream().peek(param -> param.getAnnotations().clear()).collect(Collectors.toList()));
method.setReturnType(m.getReturnType());
compilation.addImportedType(
new FullyQualifiedJavaType(m.getReturnType().getFullyQualifiedNameWithoutTypeParameters()));
return method;
}

private String generateBodyForServiceImplMethod(String mapperName, Method m) {
StringBuilder sbf = new StringBuilder("return ");
sbf.append(mapperName).append(".").append(m.getName()).append("(");

boolean singleParam = true;
for (Parameter parameter : m.getParameters()) {

if (singleParam)
singleParam = !singleParam;
else
sbf.append(", ");
sbf.append(parameter.getName());

}

sbf.append(");");
return sbf.toString();
}

}

4)ContextOverride

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
复制代码package run.override.service;

import java.util.List;

import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.ModelType;

public class ContextOverride extends Context{
//添加ServiceGeneratorConfiguration
private ServiceGeneratorConfiguration serviceGeneratorConfiguration;

public ContextOverride(ModelType defaultModelType) {
super(defaultModelType);
}

public ServiceGeneratorConfiguration getServiceGeneratorConfiguration() {
return serviceGeneratorConfiguration;
}

public void setServiceGeneratorConfiguration(ServiceGeneratorConfiguration serviceGeneratorConfiguration) {
this.serviceGeneratorConfiguration = serviceGeneratorConfiguration;
}

@Override
public void validate(List<String> errors) {
if(serviceGeneratorConfiguration != null)
serviceGeneratorConfiguration.validate(errors, this.getId());

super.validate(errors);
}

public XmlElement toXmlElement() {

XmlElement xmlElement = super.toXmlElement();
if (serviceGeneratorConfiguration != null)
xmlElement.addElement(serviceGeneratorConfiguration.toXmlElement());
return xmlElement;
}
}

5)MyBatisGeneratorConfigurationParserOverride

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
复制代码package run.override.service;

import java.util.Properties;

import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.JavaClientGeneratorConfiguration;
import org.mybatis.generator.config.ModelType;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.config.xml.MyBatisGeneratorConfigurationParser;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.util.StringUtility;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MyBatisGeneratorConfigurationParserOverride extends MyBatisGeneratorConfigurationParser {

public MyBatisGeneratorConfigurationParserOverride(Properties extraProperties) {
super(extraProperties);
}

private void parseJavaServiceGenerator(Context context, Node node) {

ContextOverride contextOverride = ContextOverride.class.cast(context); ////替换Context

ServiceGeneratorConfiguration serviceGeneratorConfiguration = new ServiceGeneratorConfiguration();

contextOverride.setServiceGeneratorConfiguration(serviceGeneratorConfiguration);
Properties attributes = parseAttributes(node);

String targetPackage = attributes.getProperty("targetPackage");
String targetProject = attributes.getProperty("targetProject");
String implementationPackage = attributes.getProperty("implementationPackage");

serviceGeneratorConfiguration.setTargetPackage(targetPackage);
serviceGeneratorConfiguration.setTargetProject(targetProject);
serviceGeneratorConfiguration.setImplementationPackage(implementationPackage);

NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE && "property".equals(childNode.getNodeName()))
parseProperty(serviceGeneratorConfiguration, childNode);
}

}

@Override
public Configuration parseConfiguration(Element rootNode) throws XMLParserException {
Configuration configuration = new Configuration();

NodeList nodeList = rootNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); ++i) {
Node childNode = nodeList.item(i);

if (childNode.getNodeType() != 1) {
continue;
}

if ("properties".equals(childNode.getNodeName()))
parseProperties(configuration, childNode);
else if ("classPathEntry".equals(childNode.getNodeName()))
parseClassPathEntry(configuration, childNode);
else if ("context".equals(childNode.getNodeName())) {
parseContext(configuration, childNode);
}
}

return configuration;
}

private void parseContext(Configuration configuration, Node node) {
Properties attributes = parseAttributes(node);
String defaultModelType = attributes.getProperty("defaultModelType");
String targetRuntime = attributes.getProperty("targetRuntime");
String introspectedColumnImpl = attributes.getProperty("introspectedColumnImpl");
String id = attributes.getProperty("id");
ModelType mt = defaultModelType != null ? ModelType.getModelType(defaultModelType) : null;
Context context = new ContextOverride(mt);
context.setId(id);
if (StringUtility.stringHasValue(introspectedColumnImpl))
context.setIntrospectedColumnImpl(introspectedColumnImpl);
if (StringUtility.stringHasValue(targetRuntime))
context.setTargetRuntime(targetRuntime);
configuration.addContext(context);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != 1)
continue;

if ("property".equals(childNode.getNodeName())) {
parseProperty(context, childNode);
continue;
}
if ("plugin".equals(childNode.getNodeName())) {
parsePlugin(context, childNode);
continue;
}
if ("commentGenerator".equals(childNode.getNodeName())) {
parseCommentGenerator(context, childNode);
continue;
}
if ("jdbcConnection".equals(childNode.getNodeName())) {
parseJdbcConnection(context, childNode);
continue;
}
if ("connectionFactory".equals(childNode.getNodeName())) {
parseConnectionFactory(context, childNode);
continue;
}
if ("javaModelGenerator".equals(childNode.getNodeName())) {
parseJavaModelGenerator(context, childNode);
continue;
}
if ("javaTypeResolver".equals(childNode.getNodeName())) {
parseJavaTypeResolver(context, childNode);
continue;
}
if ("sqlMapGenerator".equals(childNode.getNodeName())) {
parseSqlMapGenerator(context, childNode);
continue;
}
if ("javaClientGenerator".equals(childNode.getNodeName())) {
parseJavaClientGenerator(context, childNode);
continue;
}
if ("javaServiceGenerator".equals(childNode.getNodeName())) {
parseJavaServiceGenerator(context, childNode);
continue;
}
if ("table".equals(childNode.getNodeName()))
parseTable(context, childNode);
}
}

private void parsePlugin(Context context, Node node) {
PluginConfiguration pluginConfiguration = new PluginConfiguration();
context.addPluginConfiguration(pluginConfiguration);
Properties attributes = parseAttributes(node);
String type = attributes.getProperty("type");
pluginConfiguration.setConfigurationType(type);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() == 1 && "property".equals(childNode.getNodeName()))
parseProperty(pluginConfiguration, childNode);
}

}

private void parseJavaClientGenerator(Context context, Node node) {
JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);
Properties attributes = parseAttributes(node);
String type = attributes.getProperty("type");
String targetPackage = attributes.getProperty("targetPackage");
String targetProject = attributes.getProperty("targetProject");
String implementationPackage = attributes.getProperty("implementationPackage");
javaClientGeneratorConfiguration.setConfigurationType(type);
javaClientGeneratorConfiguration.setTargetPackage(targetPackage);
javaClientGeneratorConfiguration.setTargetProject(targetProject);
javaClientGeneratorConfiguration.setImplementationPackage(implementationPackage);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() == 1 && "property".equals(childNode.getNodeName()))
parseProperty(javaClientGeneratorConfiguration, childNode);
}

}
}

6)ServiceGeneratorConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
复制代码package run.override.service;

import java.util.List;

import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.PropertyHolder;
import org.mybatis.generator.internal.util.StringUtility;
import org.mybatis.generator.internal.util.messages.Messages;

public class ServiceGeneratorConfiguration extends PropertyHolder {

private String targetPackage;
private String implementationPackage;
private String targetProject;
/**
*
*/
public ServiceGeneratorConfiguration() {
super();
}
public String getTargetPackage() {
return targetPackage;
}
public void setTargetPackage(String targetPackage) {
this.targetPackage = targetPackage;
}
public String getImplementationPackage() {
return implementationPackage;
}
public void setImplementationPackage(String implementationPackage) {
this.implementationPackage = implementationPackage;
}
public String getTargetProject() {
return targetProject;
}
public void setTargetProject(String targetProject) {
this.targetProject = targetProject;
}
public XmlElement toXmlElement() {
XmlElement answer = new XmlElement("javaServiceGenerator");

if (targetPackage != null) {
answer.addAttribute(new Attribute("targetPackage", targetPackage));
}

if (implementationPackage != null) {
answer.addAttribute(new Attribute("implementationPackage", targetPackage));
}
if (targetProject != null) {
answer.addAttribute(new Attribute("targetProject", targetProject));
}


addPropertyXmlElements(answer);

return answer;
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public void validate(List errors, String contextId) {
if (!StringUtility.stringHasValue(getTargetProject()))
errors.add(Messages.getString("ValidationError.102", contextId));
if (!StringUtility.stringHasValue(getTargetPackage()))
errors.add(Messages.getString("ValidationError.112", "ServiceGenerator", contextId));
if (!StringUtility.stringHasValue(getImplementationPackage()))
errors.add(Messages.getString("ValidationError.120", contextId));
}

}

7)ConfigurationParserOverride

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
复制代码package run.override.service;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.config.xml.MyBatisGeneratorConfigurationParser;
import org.mybatis.generator.config.xml.ParserEntityResolver;
import org.mybatis.generator.config.xml.ParserErrorHandler;
import org.mybatis.generator.exception.XMLParserException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ConfigurationParserOverride extends ConfigurationParser {

private List<String> warnings;
private List<String> parseErrors;
private Properties extraProperties;

public ConfigurationParserOverride(List<String> warnings) {
this(null, warnings);
}

public ConfigurationParserOverride(Properties extraProperties, List<String> warnings) {
super(extraProperties, warnings);
this.extraProperties = extraProperties;

if (warnings == null)
this.warnings = new ArrayList<>();
else {
this.warnings = warnings;
}

this.parseErrors = new ArrayList<>();
}

@Override
public Configuration parseConfiguration(File inputFile) throws IOException, XMLParserException {
FileReader fr = new FileReader(inputFile);

return parseConfiguration(fr);
}

@Override
public Configuration parseConfiguration(InputStream inputStream) throws IOException, XMLParserException {
InputSource is = new InputSource(inputStream);

return parseConfiguration(is);
}

@Override
public Configuration parseConfiguration(Reader reader) throws IOException, XMLParserException {
InputSource is = new InputSource(reader);

return parseConfiguration(is);
}

private Configuration parseConfiguration(InputSource inputSource) throws IOException, XMLParserException {
this.parseErrors.clear();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new ParserEntityResolver());

ParserErrorHandler handler = new ParserErrorHandler(this.warnings, this.parseErrors);

builder.setErrorHandler(handler);

Document document = null;
try {
document = builder.parse(inputSource);
} catch (SAXParseException e) {
throw new XMLParserException(this.parseErrors);
} catch (SAXException e) {
if (e.getException() == null)
this.parseErrors.add(e.getMessage());
else {
this.parseErrors.add(e.getException().getMessage());
}
}

if (this.parseErrors.size() > 0) {
throw new XMLParserException(this.parseErrors);
}

Element rootNode = document.getDocumentElement();
Configuration config = parseMyBatisGeneratorConfiguration(rootNode);

if (this.parseErrors.size() > 0) {
throw new XMLParserException(this.parseErrors);
}

return config;
} catch (ParserConfigurationException e) {
this.parseErrors.add(e.getMessage());
throw new XMLParserException(this.parseErrors);
}
}

private Configuration parseMyBatisGeneratorConfiguration(Element rootNode) throws XMLParserException {

//替换MyBatisGeneratorConfigurationParser
MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParserOverride(
this.extraProperties);

return parser.parseConfiguration(rootNode);
}

}

七、PluginChain

通过继承,把以上扩展Plugin串起来(SerializablePlugin一些项目中可能不需要,故不加入Chain。同时,其他也可以根据需要对Chain进行更改)。

1
2
3
4
5
复制代码package run.override;

import run.override.service.ServiceLayerPlugin;
public class PluginChain extends ServiceLayerPlugin {
}

八、generatorConfig.xml

增加javaServiceGenerator相关配置标签。本文使用内部DTD做示例,亦可通过外部DTD或xsd来实现。

1)generatorConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
复制代码<?xml version="1.0" encoding="UTF-8"?>
<!-- 内部DTD 亦可通过外部DTD来实现-->
<!DOCTYPE generatorConfiguration

[
<!ELEMENT generatorConfiguration (properties?, classPathEntry*, context+)>

<!ELEMENT properties EMPTY>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED>
<!--
括号里是声明出现的次序:
*: 出现任意次,包括0次
?: 出现最多一次
|:选择之一
+: 出现最少1次
如果没有上述符号:必须且只能出现一次
-->
<!ELEMENT context (property*, plugin*, commentGenerator?, (connectionFactory | jdbcConnection), javaTypeResolver?,
javaModelGenerator, sqlMapGenerator, javaClientGenerator, javaServiceGenerator,table+)>
<!ATTLIST context id ID #REQUIRED
defaultModelType CDATA #IMPLIED
targetRuntime CDATA #IMPLIED
introspectedColumnImpl CDATA #IMPLIED>

<!ELEMENT connectionFactory (property*)>
<!ATTLIST connectionFactory
type CDATA #IMPLIED>

<!ELEMENT jdbcConnection (property*)>
<!ATTLIST jdbcConnection
driverClass CDATA #REQUIRED
connectionURL CDATA #REQUIRED
userId CDATA #IMPLIED
password CDATA #IMPLIED>

<!ELEMENT classPathEntry EMPTY>
<!ATTLIST classPathEntry
location CDATA #REQUIRED>

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED>

<!ELEMENT plugin (property*)>
<!ATTLIST plugin
type CDATA #REQUIRED>

<!ELEMENT javaModelGenerator (property*)>
<!ATTLIST javaModelGenerator
targetPackage CDATA #REQUIRED
targetProject CDATA #REQUIRED>

<!ELEMENT javaTypeResolver (property*)>
<!ATTLIST javaTypeResolver
type CDATA #IMPLIED>

<!ELEMENT sqlMapGenerator (property*)>
<!ATTLIST sqlMapGenerator
targetPackage CDATA #REQUIRED
targetProject CDATA #REQUIRED>

<!ELEMENT javaClientGenerator (property*)>
<!ATTLIST javaClientGenerator
type CDATA #REQUIRED
targetPackage CDATA #REQUIRED
targetProject CDATA #REQUIRED
implementationPackage CDATA #IMPLIED>

<!ELEMENT javaServiceGenerator (property*)>
<!ATTLIST javaServiceGenerator
targetPackage CDATA #REQUIRED
implementationPackage CDATA #REQUIRED
targetProject CDATA #REQUIRED>

<!ELEMENT table (property*, generatedKey?, domainObjectRenamingRule?, columnRenamingRule?, (columnOverride | ignoreColumn | ignoreColumnsByRegex)*) >
<!ATTLIST table
catalog CDATA #IMPLIED
schema CDATA #IMPLIED
tableName CDATA #REQUIRED
alias CDATA #IMPLIED
domainObjectName CDATA #IMPLIED
mapperName CDATA #IMPLIED
sqlProviderName CDATA #IMPLIED
enableInsert CDATA #IMPLIED
enableSelectByPrimaryKey CDATA #IMPLIED
enableSelectByExample CDATA #IMPLIED
enableUpdateByPrimaryKey CDATA #IMPLIED
enableDeleteByPrimaryKey CDATA #IMPLIED
enableDeleteByExample CDATA #IMPLIED
enableCountByExample CDATA #IMPLIED
enableUpdateByExample CDATA #IMPLIED
selectByPrimaryKeyQueryId CDATA #IMPLIED
selectByExampleQueryId CDATA #IMPLIED
modelType CDATA #IMPLIED
escapeWildcards CDATA #IMPLIED
delimitIdentifiers CDATA #IMPLIED
delimitAllColumns CDATA #IMPLIED>

<!ELEMENT columnOverride (property*)>
<!ATTLIST columnOverride
column CDATA #REQUIRED
property CDATA #IMPLIED
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
isGeneratedAlways CDATA #IMPLIED
delimitedColumnName CDATA #IMPLIED>

<!ELEMENT ignoreColumn EMPTY>
<!ATTLIST ignoreColumn
column CDATA #REQUIRED
delimitedColumnName CDATA #IMPLIED>


<!ELEMENT ignoreColumnsByRegex (except*)>
<!ATTLIST ignoreColumnsByRegex
pattern CDATA #REQUIRED>

<!ELEMENT except EMPTY>
<!ATTLIST except
column CDATA #REQUIRED
delimitedColumnName CDATA #IMPLIED>

<!ELEMENT generatedKey EMPTY>
<!ATTLIST generatedKey
column CDATA #REQUIRED
sqlStatement CDATA #REQUIRED
identity CDATA #IMPLIED
type CDATA #IMPLIED>

<!ELEMENT domainObjectRenamingRule EMPTY>
<!ATTLIST domainObjectRenamingRule
searchString CDATA #REQUIRED
replaceString CDATA #IMPLIED>

<!ELEMENT columnRenamingRule EMPTY>
<!ATTLIST columnRenamingRule
searchString CDATA #REQUIRED
replaceString CDATA #IMPLIED>

<!ELEMENT commentGenerator (property*)>
<!ATTLIST commentGenerator
type CDATA #IMPLIED>
]
>

<generatorConfiguration>
<context id="ables" targetRuntime="MyBatis3">
<!--
添加Plugin
-->
<plugin type="run.override.PluginChain" />
<plugin type="run.override.SerializablePlugin" />
<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
<commentGenerator type="run.override.CommentGenerator"/>

<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=utf8"
userId="xxx" password="xxx">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="cn.xxx.elecsign.model" targetProject=".\src">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="true" />
</javaModelGenerator>

<sqlMapGenerator targetPackage="mapper.cn.xxx.elecsign.dao" targetProject=".\src">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>

<javaClientGenerator type="XMLMAPPER" targetPackage="cn.xxx.elecsign.dao" targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- javaServiceGenerator -->
<javaServiceGenerator targetPackage="cn.xxx.elecsign.dly.service"
implementationPackage = "cn.xxx.elecsign.dly.service.impl" targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaServiceGenerator>

<!-- 批次表,针对批量的异步操作 -->
<table tableName="table" domainObjectName="Table"
alias="table">
<generatedKey column="id" sqlStatement="MySql" identity="true" />
</table>
</context>
</generatorConfiguration>

九、main启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
复制代码 package run.generator;


import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.internal.DefaultShellCallback;

import run.override.service.ConfigurationParserOverride;

public class Generator {

public void generator() throws Exception{

List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.xml");
//替换ConfigurationParser
ConfigurationParserOverride cp = new ConfigurationParserOverride(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);

myBatisGenerator.generate(null);

}
public static void main(String[] args) throws Exception {
try {
Generator generator = new Generator();
generator.generator();
} catch (Exception e) {
e.printStackTrace();
}

}

}

至此,对mybatis-generator的扩展生成代码完成。

来源:宜信技术学院

作者:马伟伟

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

有的线程它死了,于是它变成一道面试题 来吧,一起分析一波 抛

发表于 2020-02-24

有些线程它活着,但它躺在池中碌碌无为;
有的线程它死了,于是它变成一道面试题。

这次的文章,要从一次阿里巴巴的面试说起。

我记得那天是周一,刚刚经历过周末过的放松,干劲十足的我正在键盘上疯狂的输出。这时,我的手机响了起来,拿起一看,是来自杭州的电话,心想这次是要给我推荐股票呢还是要让我贷款呢。我接起了电话,准备调戏一番。那边响起一个声音:”你好,请问是xxx吗?这边是杭州阿里巴巴,现在有时间进行电话面试吗?”。说实在的,听完这句话后,我感觉我已经身在杭州,干劲十足的在杭州的阿里的工位上”修福报”。但是我现在正在疯狂输出,没有时间,于是我说:”不好意思,现在没有时间,可以约在今天晚上8点钟吗?”.

晚上如约接到了电话。我们直奔主题,在你来我往中进行了友好的技术交流。具体的面试过程就不详述了,后面有机会整理一份面试分享。整个面试过程中,有这么一道题给我留下了深刻的印象:

一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

需要说明一下,文中讨论的线程池都是Executors线程池。

对于Executors线程池我可以说是烂熟于心,因为工作中用的比较的多,阅读过其源码。也是我作为面试官时必问的几个范围之一,比如以下问题:

了解JDK Executors线程池吗?
知道JDK提供了哪些默认的实现吗?
看过阿里巴巴java开发手册吗?知道为啥不允许使用默认的实现吗?
你们没有用默认的吧?那来介绍一下你们自定义线程池的几个常用参数呗?
你这个几个参数的值是怎么得来的呀?算出来的?怎么算出来的?
线程池里面的任务是IO密集型的还是计算密集型的呢?
好,现在我们有一个自定义线程池了,来说一下你这个线程池的工作流程呗?
那你这个线程池满了怎么办呀?拒绝?咋拒绝?有哪些拒绝策略呢?
别紧张,随便说两个就行。
……
回到开始说的阿里巴巴java开发手册不允许使用默认实现,你回答说可能会引起OOM,那我们聊聊JVM吧

……


这一系列关于线程池的连环炮,就是我作为面试官时必问的几个问题。别问为什么,因为我们的招聘JD上明确写了:熟悉多线程编程。而这些问题,我觉得是熟悉多线程编程的基础。这里我也不解答了,这种文章网上还是挺多的,可以去了解一下。

这块真的很重要,我也多次给我的小伙伴强调:

来吧,一起分析一波

好了现在回到阿里的面试官问我的这道面试题:

一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

先说说我当时的回答,因为心里没底,我的回答很犹豫也很烂!如下:

我的回答总结起来三句话:

1.抛出堆栈异常 —这句话对了一半!
2.不影响其他线程任务 —这句话全对!
3.这个线程会被放回线程池—这句话全错!

测试用例写起来


抛出堆栈异常为啥对了一半?
=============

先让程序跑起来,我们用事实说话:


从执行结果我们看出

当执行方式是execute时,可以看到堆栈异常的输出。

当执行方式是submit时,堆栈异常没有输出。

那么我们怎么拿到submit执行方式的堆栈异常呢,看图说话:


所以,现在知道为什么回答:抛出堆栈异常只对了一半吧。

execute方法执行时,会抛出(打印)堆栈异常。

submit方法执行时,返回结果封装在future中,如果调用future.get()方法则必须进行异常捕获,从而可以抛出(打印)堆栈异常。

你以为这一部分写到这里就完事了?那不行啊,你心里没有一个疑问吗?为啥execute直接抛出异常,submit没有直接抛出异常呢?
源码之下无秘密:

当执行方式是executes时:

在java.util.concurrent.ThreadPoolExecutor#runWorker中抛出了异常:


在java.lang.ThreadGroup#uncaughtException进行了异常处理:


这个uncaughtException是何许人也,看java doc上咋说的:


这个方法是JVM调用的,我们只需要指定我们想要的处理方式即可。

那我们怎么指定呢:


当执行方式是submit时:


其本质也是调用了execute方法,所以它还是回到java.util.concurrent.ThreadPoolExecutor#runWorker方法:


向前,继续跟进去看看:


java.util.concurrent.FutureTask#setException干啥了啊,瞅一眼:


深呼吸,整理好思路,我们马上走向最终的真相:


好了,第一个议题**【抛出堆栈异常为啥对了一半?】**讨论完毕。在源码里面走了一趟,现在我们可以给出这一部分的满分答案了。

不影响其他线程任务,回答正确

这一部分我们直接上代码,运行起来看结果吧:


代码和运行结果是不会骗人的:

线程池中一个线程异常了后,不影响其他线程任务

大家注意线程名称这个细节:1,2,3,4,6。魔鬼都在细节里啊,这个点我下面会讲,先在这里把问题抛出来:我就纳闷了,怎么没有5啊?!

这个线程会被放回线程池为啥全错了?

我们去源码里面寻找答案:


让源码给出答案:


5号线程去哪里了?

new Worker()方法会告诉你:5去哪里了。


再配上这张由我这个灵魂画师亲自操刀画的图,一起食用,味道更佳:


现在我们知道为啥:我回答这个线程会被放回线程池为啥全错了吧。还附送你一个线程名称变化的细节,不客气,关注一下就好。

总结一下

当一个线程池里面的线程异常后:

1.当执行方式是execute时,可以看到堆栈异常的输出。

2.当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常。

3.不会影响线程池里面其他线程的正常执行。

4.线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。

5.不要背答案,要理解,要深入,上面说完后记得在问问面试官,需要我从源码的角度讲一讲吗?这逼装的,礼貌而不失风度。

以上,我关于《一个线程池中的线程异常了,那么线程池会怎么处理这个线程?》这个问题的见解就表达完毕,仅代表个人观点,欢迎有不同意见的小伙伴,一起讨论,一起进步。

最后说一点

这篇文章是我上周五推完上一篇文章之后就在构思并且着手准备了。大部分内容都是思考于晚上睡觉前的半小时,写于周末和工作日的早上早起的一小时。

其实想到写什么内容并不难,难的是你对内容的把控。关于技术性的语言,我是反复推敲,查阅大量文章来进行证伪,总之慎言慎言再慎言,毕竟做技术,我认为是一件非常严谨的事情,我常常想象自己就是在故宫修文物的工匠,在工匠精神的认知上,目前我可能和他们还差的有点远,但是我时常以工匠精神要求自己。就像我在群里表达的:对于技术文章我尽量保证周推,全力保证质量。

最后,再感叹一次:

有些线程它活着,但它躺在池中碌碌无为;
有些线程也活着,但它一刻不停忙到飞起;
有的线程它死了,被抛弃,被回收,
但是它无怨无悔,

因为它是死在执行任务的路上,
它凭借自己最后的一声呐喊
“为了新兄弟,移除我吧!”
最后,变成一道面试题。

我还没答上来。

欢迎关注公众号【why技术】,坚持输出原创。分享技术、品味生活,愿你我共同进步。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

面试总被问分库分表怎么办?你可以这样怼他

发表于 2020-02-24

整理了一些Java方面的架构、面试资料(微服务、集群、分布式、中间件等),有需要的小伙伴可以关注公众号【程序员内点事】,无套路自行领取

更多优选

  • 一口气说出 9种 分布式ID生成方式,面试官有点懵了
  • 3万字总结,Mysql优化之精髓
  • 为了不复制粘贴,我被逼着学会了JAVA爬虫
  • 技术部突然宣布:JAVA开发人员全部要会接口自动化测试框架
  • Redis 5种数据结构及对应使用场景,全会面试要加分的

引言

微服务、分布式大行其道的当下,中、高级Java工程师面试题中高并发、大数据量、分库分表等已经成

了面试的高频词汇,这些知识不了解面试通过率不会太高。你可以不会用,但你不能不知道,就是这么

一种现状。技术名词大多晦涩难懂,不要死记硬背理解最重要,当你捅破那层窗户纸,发现其实它也就

那么回事。

一、为什么要分库分表

关系型数据库以MySQL为例,单机的存储能力、连接数是有限的,它自身就很容易会成为系统的瓶

颈。当单表数据量在百万以里时,我们还可以通过添加从库、优化索引提升性能。一旦数据量朝着千万

以上趋势增长,再怎么优化数据库,很多操作性能仍下降严重。为了减少数据库的负担,提升数据库响

应速度,缩短查询时间,这时候就需要进行分库分表。

二、如何分库分表

分库分表就是要将大量数据分散到多个数据库中,使每个数据库中数据量小响应速度快,以此来提升数

据库整体性能。核心理念就是对数据进行切分(Sharding),以及切分后如何对数据的快速定位与整合。

针对数据切分类型,大致可以分为:垂直(纵向)切分和水平(横向)切分两种。

1、垂直切分

垂直切分又细分为垂直分库和垂直分表

垂直分库

垂直分库是基于业务分类的,和我们常听到的微服务治理观念很相似,每一个独立的服务都拥有自己的

数据库,需要不同业务的数据需接口调用。而垂直分库也是按照业务分类进行划分,每个业务有独立数

据库,这个比较好理解。

在这里插入图片描述

垂直分表

垂直分表是基于数据表的列为依据切分的,是一种大表拆小表的模式。

例如:一个order表有很多字段,把长度较大且访问不频繁的字段,拆分出来创建一个单独的扩展表work_extend进行存储。

order表:

id workNo price describe …..
int(12) int(2) int(15) varchar(2000)

拆分后

order核心表:

id workNo price …..
int(12) int(2) int(15)

work_extend表:

id workNo describe …..
int(12) int(2) varchar(2000)

数据库是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段

长度也都较短,可以加载更多数据到内存中,增加查询的命中率,减少磁盘IO,以此来提升数据库性能。

优点:

  • 业务间解耦,不同业务的数据进行独立的维护、监控、扩展
  • 在高并发场景下,一定程度上缓解了数据库的压力

缺点:

  • 提升了开发的复杂度,由于业务的隔离性,很多表无法直接访问,必须通过接口方式聚合数据,
  • 分布式事务管理难度增加
  • 数据库还是存在单表数据量过大的问题,并未根本上解决,需要配合水平切分
2、水平切分

前边说了垂直切分还是会存在单表数据量过大的问题,当我们的应用已经无法在细粒度的垂直切分时,依旧存在单库读写、存储性能瓶颈,这时就要配合水平切分一起了。

水平切分将一张大数据量的表,切分成多个表结构相同,而每个表只占原表一部分数据,然后按不同的条件分散到多个数据库中。

假如一张order表有2000万数据,水平切分后出来四个表,order_1、order_2、order_3、order_4,每张表数据500万,以此类推。

order_1表:

水平切分又分有库内分表和分库分表

库内分表

库内分表虽然将表拆分,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分布到不同机器的库上,还在竞争同一个物理机的CPU、内存、网络IO。在这里插入图片描述

分库分表

分库分表则是将切分出来的子表,分散到不同的数据库中,从而使得单个表的数据量变小,达到分布式的效果。

优点:

  • 解决高并发时单库数据量过大的问题,提升系统稳定性和负载能力
  • 业务系统改造的工作量不是很大

缺点:

  • 跨分片的事务一致性难以保证
  • 跨库的join关联查询性能较差
  • 扩容的难度和维护量较大,(拆分成几千张子表想想都恐怖)

三、数据该往哪个库的表存?

分库分表以后会出现一个问题,一张表会出现在多个数据库里,到底该往哪个库的表里存呢?

1、根据取值范围

按照时间区间或ID区间来切分,举个栗子:假如我们切分的是用户表,可以定义每个库的User表里只存10000条数据,第一个库userId从1 ~ 9999,第二个库10000 ~ 20000,第三个库20001~ 30000……以此类推。

优点:

  • 单表数据量是可控的
  • 水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移
  • 能快速定位要查询的数据在哪个库

缺点:

  • 由于连续分片可能存在数据热点,如果按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
2、hash取模

hash取模mod(对hash结果取余数 (hash() mod N))的切分方式比较常见,还拿User表举例,对数据库从0到N-1进行编号,对User表中userId字段进行取模,得到余数i,i=0存第一个库,i=1存第二个库,i=2存第三个库….以此类推。

这样同一个用户的数据都会存在同一个库里,用userId作为条件查询就很好定位了

优点:

  • 数据分片相对比较均匀,不易出现某个库并发访问的问题

缺点:

  • 但这种算法存在一些问题,当某一台机器宕机,本应该落在该数据库的请求就无法得到正确的处理,这时宕掉的实例会被踢出集群,此时算法变成hash(userId) mod N-1,用户信息可能就不再在同一个库中。

四、分库分表后会有哪些坑?

1、事务一致性问题

由于表分布在不同库中,不可避免会带来跨库事务问题。一般可使用”XA协议”和”两阶段提交”处理,但是这种方式性能较差,代码开发量也比较大。

通常做法是做到最终一致性的方案,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。

2、分页、排序的坑

日常开发中分页、排序是必备功能,而多库进行查询时limit分页、order by排序,着实让人比较头疼。

分页需按照指定字段进行排序,如果排序字段恰好是分片字段时,通过分片规则就很容易定位到分片的位置;一旦排序字段非分片字段时,就需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户,过程比较复杂。

3、全局唯一主键问题

由于分库分表后,表中的数据同时存在于多个数据库,而某个分区数据库的自增主键已经无法满足全局

唯一,所以此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID。可

以参考我之前写的这篇文章《一口气说出 9种 分布式ID生成方式,面试官有点懵了》

五、分库分表工具?

自己开发分库分表工具的工作量是巨大的,好在业界已经有了很多比较成熟的分库分表中间件,我们可

以将更多的时间放在业务实现上

  • sharding-jdbc(当当)
  • TSharding(蘑菇街)
  • Atlas(奇虎360)
  • Cobar(阿里巴巴)
  • MyCAT(基于Cobar)
  • Oceanus(58同城) Vitess(谷歌)

今天就说这么多,如果本文对您有一点帮助,希望能得到您一个点赞👍哦

您的认可才是我写作的动力!

整理了一些Java方面的架构、面试资料(微服务、集群、分布式、中间件等),有需要的小伙伴可以关注公众号【程序员内点事】,无套路自行领取

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java服务-CPU过载问题复盘

发表于 2020-02-24

上周公司的服务有一个大版本上线,上线后服务器频繁出现CPU使用率长时间百分之三四百,服务不可用。因为公司系统目前单机运行,出现问题后只能麻烦运营同学一次又一次重启Tomcat,不能保留现场,一时间手忙脚乱。目前问题已经解决,在这里做一下记录已便以后遇见问题快速解决。

1、首先确定机器卡死是不是Java服务的问题,用“top”命令看一下机器整体进程情况

窗口观察一段时间,发现Java进程一直cpu占用率很好,内存占用也很大。
2、定位异常线程,“top -H -p78066”

记下几个长时间占用率高的线程。
通过命令“jstack 78066 > jstack.txt ”可以打印Java进程当前各个线程的堆栈,文本信息中nid是线程id的十六进制。我们把定位到的几个线程线程ID转化十六进制,“printf “%x\n” 2777”。搜索jstack.txt如下图

基本上确定是GC占用cpu过多。命令“jstat -gcutil 78066” 可以查看具体GC情况。
3、查看内存情况,命令“jmap -histo:live 78066 | more ” 查看当前线程当前存活的对象,看排在前几位的对象。

可以看见排在前面的两个业务对象异常,order对象和user对象有几百万个,由此猜测肯定有错误代码将大量的库记录加载到内存里面。但是我们还是不知道该改哪个代码。
4、下载一个内存镜像,具体分析,命令“jmap -dump:live,format=b,file=/data/0223.bin 78066”。使用mat软件 ,下载地址:www.eclipse.org/mat/downloa… 下载一个独立运行版本即可。

把bin文件导入,分析工具提供的报表比较多,比较用的三个是Histogram可以列出内存中的对象,对象的个数以及大小。Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。Leak Suspects通过MA自动分析泄漏的原因。我们直接打开Leak Suspects可见如下

下面是推荐的可以代码,点开堆栈就可以定位问题代码。
最终根据mat报告中的堆栈信息定位到了代码错误,是有一个查询把一个超大的查询结果加载到内存中,导致内存吃紧,GC拼命回收,最终导致服务假死不可用。

结语:在代码中出现空条件查询肯定会出现问题,一般可以在mybatis层面设置拦截器,出现空条件查询就抛出错误(www.cnblogs.com/cmfwm/p/802…
另外遇到此类问题还可以考虑的方向还有就是数据库连接问题,我们连接池用的是druid,它自带一个监控界面“druid monitor”,可以关注下图中两个参数,看看有没有未关闭的连接。

1
2
3
复制代码参考资料:https://www.cnblogs.com/xiaoL/p/9774095.html
https://www.cnblogs.com/AloneSword/p/3821569.htm
https://www.cnblogs.com/loong-hon/p/10475143.html

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

13 个最火的 SpringBoot 实战开源项目推荐!总有

发表于 2020-02-24

更多开源项目推荐:github.com/Snailclimb/…

Guide 哥注:下面这些推荐的项目几乎都和 Spring Boot 有关,毕竟这年头没有理由再搞 SSM/SSH 这些东西了。

商城系统

Guide 哥注:下面的商城系统大多比较复杂比如 mall ,如果没有 Java 基础和 Spring Boot 都还没有摸熟的话不推荐过度研究下面几个项目或者使用这些项目当作毕业设计。

  1. mall :mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。
  2. mall-swarm : mall-swarm 是一套微服务商城系统,采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch 等核心技术,同时提供了基于 Vue 的管理后台方便快速搭建系统。
  3. onemall :mall 商城,基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
  4. litemall : 又一个小商城。litemall = Spring Boot 后端 + Vue 管理员前端 + 微信小程序用户前端 + Vue 用户移动端。

博客/论团/其他

Guide 哥注:下面这几个项目都是非常适合 Spring Boot 初学者学习的,下面的大部分项目的总体代码架构我都看过,个人觉得还算不错,不会误导没有实际做过项目的老哥,特别是前两个项目 vhr 和 favorites-web 。

  1. vhr :微人事是一个前后端分离的人力资源管理系统,项目采用 SpringBoot+Vue 开发。
  2. favorites-web :云收藏 Spring Boot 2.X 开源项目。云收藏是一个使用 Spring Boot 构建的开源网站,可以让用户在线随时随地收藏的一个网站,在网站上分类整理收藏的网站或者文章。
  3. VBlog :V 部落,Vue+SpringBoot 实现的多用户博客管理平台!
  4. My-Blog : My Blog 是由 SpringBoot + Mybatis + Thymeleaf 等技术实现的 Java 博客系统,页面美观、功能齐全、部署简单及完善的代码,一定会给使用者无与伦比的体验。
  5. community :开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。

权限管理系统

Guide 哥注:权限管理系统在企业级的项目中一般都是非常重要的,如果你需求去实际了解一个不错的权限系统是如何设计的话,推荐你可以参考下面这些开源项目。

  1. Spring-Cloud-Admin :Cloud-Admin 是国内首个基于 Spring Cloud 微服务化开发平台,具有统一授权、认证后台管理系统,其中包含具备用户管理、资源权限管理、网关 API 管理等多个模块,支持多业务系统并行开发,可以作为后端服务的开发脚手架。代码简洁,架构清晰,适合学习和直接项目中使用。核心技术采用 Spring Boot2 以及 Spring Cloud Gateway 相关核心组件,前端采用 vue-element-admin 组件。
  2. pig:(gitee)基于 Spring Boot 2.2、 Spring Cloud Hoxton & Alibaba、 OAuth2 的 RBAC 权限管理系统。
  3. FEBS-Shiro :Spring Boot 2.1.3,Shiro1.4.0 & Layui 2.5.4 权限管理系统。
  4. eladmin : 项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue 的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由。

开源项目推荐

作者的其他开源项目推荐:

  1. JavaGuide:【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
  2. springboot-guide : 适合新手入门以及有经验的开发人员查阅的 Spring Boot 教程(业余时间维护中,欢迎一起维护)。
  3. programmer-advancement : 我觉得技术人员应该有的一些好习惯!
  4. spring-security-jwt-guide :从零入门 !Spring Security With JWT(含权限验证)后端部分代码。

公众号

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…831832833…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%