通过迭代达到整洁的目的
- 运行所有测试
- 不可重复
- 表达程序员的意图
- 尽可能减少类和方法的数量
怎样才能让函数表达其意图?该给函数赋予哪些属性,好让读者一看就明白函数属于怎样的程序?
应该尽量将API部署在专用域名之下。1
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。1
https://example.org/api/
应该将API的版本号放入URL。1
https://api.example.com/v1/
1 | https://api.example.com/v1/zoos |
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
1 | GET(SELECT):从服务器取出资源(一项或多项)。 |
下面是一些例子。
1 | GET /zoos:列出所有动物园 |
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
1 | 1xx为消息类,该类状态码用于表示服务器临时回应。 |
1 |
|
您也可以通过以下方式注入自定义的validator
1 |
|
2.1 JSR-303 Bean Validation API摘要
JSR-303标准化了Java平台的验证约束声明和元数据。通过使用该标准化的API,您可以对域模型的属性进行申明式的验证,在运行时作用于这些域。您可以使用许多内置约束,同样您也可以自定义自己的验证。
考虑下面的示例,它显示了一个具有两个属性的简单PersonForm模型:
1 | public class PersonForm { |
JSR-303允许您针对此类属性定义声明性验证约束,如下例所示:
1 | public class PersonForm { |
当JSR-303验证器验证该类的实例时,将强制执行这些约束。
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
JSR303定义的校验类型
1 | 空检查 |
在检验入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
1. 分组:@Validated 支持分组,不展开讲了,@Valid不支持分组
2. 注解地方
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。
搞个表格直观一点:
1 | 类型/是否可以 | 使用在类型上 | 使用在方法上 | 使用在方法参数上 | 构造函数 | 成员属性(字段) |
1 | public class Item { |
Item带有很多属性,属性里面有:pid、vid、pidName和vidName,如下所示:
1 | public class Prop { |
属性这个实体也有自己的验证机制,比如pid和vid不能为空,pidName和vidName不能为空等。
现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:
1 |
|
在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。
为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。
我们修改Item类如下所示:
1 | public class Item { |
然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。
总结一下@Validated和@Valid在嵌套验证功能上的区别:
@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
对于每个注解抛什么异常需要进行总结和归纳,便于在统一异常处理的地方做处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestController {
("/NotEmpty")
public String notEmpty(@NotEmpty String me){
return "NotEmpty";
}
("/NotNull")
public String notNull(@NotNull String me){
return "NotNull";
}
("/NotBlank")
public String notBlank(@NotBlank String me){
return "NotBlank";
}
("/Null")
public String snull(@Null String me){
return "Null";
}
}
这三个注解在不传“me”这个参数的时候,都抛ConstraintViolationException异常,该异常 继承于ValidationException 继承于RuntimeException
注意:正对于这种非嵌套类型的校验,必须要在类上使用@Validated注解,只能用这个,原因后面分析下(T0D0) .不能用其他的,也不能在其他地方加。
对于嵌套类型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//嵌套类型
("/item/add0")
public void addItem0(@Validated Item item) {
System.out.println("dosmt0");
}
("/item/add1")
public void addItem1(@Validated Item item, BindingResult bindingResult) {
System.out.println("dosmt1"+bindingResult);
}
("/item/add2")
public void addItem2(@Valid Item item, BindingResult bindingResult) {
System.out.println("dosmt2"+bindingResult);
}
会抛BindException,如果我们没有统一的ExceptionHandler的话,会走spring的DefaultHandlerExceptionResolver,然后将结果绑定到DefaultMessageSourceResolvable里的BindingResult里
DefaultHandlerExceptionResolver的优先级是最低的,所以一般会使用其他优先级高的处理器
我们仅用默认优先级即可,当时messageResolver还是会用默认的。
如果使用“/item/add1” 带了BindingResult的话则不会抛异常,为空的结果直接绑定到这个结果对象里
但是如果使用“/item/add2”中的@Valid 则会抛一个ConstraintViolationException的异常,这个使用的时候要小心,具体原因后面分析。(T0D0)
先建议大家用@Valid 这个毕竟是java正统的JSR303注解,除非一些不可用的地方使用spring的
IO是一个通用的概念,即数据从一个地方移动到另一个地方,对一个实体来说,可以看成数据从外部进入,以及从实体输出到外部。
具体来说,常见的IO请求有网络IO,磁盘IO。
那么因为CPU的工作频率远远快过和其连接的外部硬件,例如磁盘,所以CPU在IO的时候经常会需要等待外部硬件完成当前任务,完成之后,才能进行下一个任务,这种情况常常称为IO阻塞,即CPU直到等待IO操作返回前,不能继续运行。IO阻塞对于CPU强大的运算能力是一个巨大的浪费。
所以有了多线程,多线程是同步运行的多个任务。不过由于CPU核数的问题,许多线程实际上并不是真正并行而只是通过快速切分时间片来模拟的。简单来说就是你执行一点,我执行一点,来回切换,创造出一种同时运行任务的假象。(多线程的本质是切换CPU时间片轮流执行,造成多个线程同步执行的假象)
多线程的底层机制是由操作系统实现的,当一个线程遇到IO阻塞时,例如读写文件,操作系统可能会暂时挂起该线程,从而让其他线程优先执行,也就是将多出来的时间片切分给其他的线程,直到等待该线程的IO操作返回,再重新调度该线程运行。
CPU密集型(CPU-bound)
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,RSA加解密算法,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些。
IO密集型(I/O bound)
IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
除了同步IO之外,系统可能还支持异步IO,即IO不阻塞,对IO设备发出读写命令之后立即返回执行下一条命令,而IO设备的返回结果则在将来未知的某个时间点通过信号来回调。这也是nodeJS底层的实现机制。
CPU密集型 vs IO密集型
我们可以把任务分为计算(CPU)密集型和IO密集型。
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
总之,计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。
参考:
https://blog.csdn.net/o83290102o5/article/details/78723329
https://blog.csdn.net/youanyyou/article/details/78990156
1 |
|
两个或多个线程等待彼此锁释放而无法继续运行形成死锁。