迭代

通过迭代达到整洁的目的

  • 运行所有测试
  • 不可重复
  • 表达程序员的意图
  • 尽可能减少类和方法的数量
分享到

函数

函数

怎样才能让函数表达其意图?该给函数赋予哪些属性,好让读者一看就明白函数属于怎样的程序?

  • 短小:短小注意代码块的缩进。
  • 只做一件事情
  • 每个函数一个抽象层级:向下规则,每个函数后面都跟着位于下一抽象层级的函数,查看函数时,能够逐层向下阅读了。
  • SWITH:这个现在支持枚举了,建议在较低抽象层使用枚举。
  • 使用描述性名称:不要担心名称长,长而具有描述性的名称比短小费解的名称要好。也比注释要好,命名的方式要保持一致。
  • 函数的参数之一:最好是零,如果出现3个以上尽量使用对象。不要使用标识参数(true false)
  • 函数的参数之二:避免使用输出参数,避免将输入参数再作为输出参数使用。
  • 分隔指令与询问:函数要么做什么事,要么回答什么事,不可二者兼得。函数应该是修改某对象的状态,或者是返回该对象的有关信息。
  • 使用异常替代返回错误码。
  • try catch代码的抽离:这个规则简单而言,如果出现try关键字,应该是这个函数的第一个关键字,错误处理本身就是一件事情,应该符合只做一件事的原则。在springboot中现在有全局异常处理的功能,用起来还是很爽的,实践经验建议统一处理比较好
  • 不要重复自己:重复是软件中一切邪恶的更远。马丁福勒的重构一书中也提到需要消灭重复,抽象函数。
  • 不要使用goto
  • 写函数和写文章一样,不要指望着一开始就写的很好,可以写完后慢慢打磨出你想要的样子。
分享到

RESTFul风格的接口

RESTFul风哥的接口

  • 网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备等等)。
  • 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现”API First”的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。

    一、协议

    API与用户的通信协议,总是使用HTTPs协议。

二、域名

应该尽量将API部署在专用域名之下。

1
https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

1
https://example.org/api/

三、版本(Versioning)

应该将API的版本号放入URL。

1
https://api.example.com/v1/

  • 另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

    四、路径(Endpoint)

  • 路径又称”终点”(endpoint),表示API的具体网址。
  • 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
  • 举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
    1
    2
    3
    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals
    https://api.example.com/v1/employees

五、HTTP动词

  • 对于资源的具体操作类型,由HTTP动词表示。
  • 常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

    1
    2
    3
    4
    5
    6
    7
    8
    GET(SELECT):从服务器取出资源(一项或多项)。
    POST(CREATE):在服务器新建一个资源。
    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    DELETE(DELETE):从服务器删除资源。
    还有两个不常用的HTTP动词。
    HEAD:获取资源的元数据。
    OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
  • 下面是一些例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    GET /zoos:列出所有动物园
    POST /zoos:新建一个动物园
    GET /zoos/ID:获取某个指定动物园的信息
    PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
    PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
    DELETE /zoos/ID:删除某个动物园
    GET /zoos/ID/animals:列出某个指定动物园的所有动物
    DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
    ```
    ### 六、过滤信息(Filtering)
    * 如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
    * 下面是一些常见的参数。
    ``` bash
    ?limit=10:指定返回记录的数量
    ?offset=10:指定返回记录的开始位置。
    ?page=2&per_page=100:指定第几页,以及每页的记录数。
    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    ?animal_type_id=1:指定筛选条件
  • 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

七、状态码(Status Codes)

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
  1xx为消息类,该类状态码用于表示服务器临时回应。
    100 continue 表示出的请求已经被服务器接收,游览器应当继续发送请求的其余部分(HTTP1.1)

    101 switching pototcols 服务器将遵从客户的请求转换到另外一种协议(HTTP1.1)。

  2xx 表示浏览器端请求被处理成功#
    200 ok 一切正常

    201 created 服务器已经创建了文档,location 头给出了他的URL。

    202 accepted 已经接收请求,但是尚未处理完成。

    203 non-authoritative information 文档已经正常的返回,但一些应答头可能不正确,因为使用的是的文档的拷贝(HTTP 1.1新)。

    204 no content 没有新文档,游览器应该继续显示原来的文档,这个跟下面的304非常相似。

    205 Reset content 没有新的内容,到那时游览器应该重置它所显示的内容,用来强制清楚表单输入内容(HTTP1.1 新)

    206 partial content 客户发送了一个带有range头的GET请求,服务器完成了它(HTTP1.1 新)。注意 通过Range 可以实现断点续传。

  3xx 重定向。#
    300 Multiple choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出,如果服务器要提出优先选择,则应该在location 应答头指明。

    301 Mulitiple permanently 客户请求的文档在其他地方,新的url在location 头中给出,浏览器应该自动的访问新的URL。

    302 Found 类似301,但新的URL应该被视为临时性的替代,而不是永久性的,注意,在HTTP1.0中对应的状态信息moved Temporatily。出现该状态码,浏览器能够给自动访问新的URL,因此他是一个很有用的状态代码。

    注意这个状态代码有时候可以和301替换使用,例如,如果浏览器错误的请求http:// host/~user(缺少了后面的斜杠,有的服务器返回301,有的返回302)。严格的说,我们只能假定原来的请求是GET时浏览器才会自动重定向。

    303 see other 类似于301/302,不同之处在于,如果原来的请求是post,location头指定的重定向目标文档应该通过get提取(http 1.1 新)。

    304 not modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供if -modified -since 头表示客户端执行比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

    305 use proxy 客户请求的文档应该通过location 头所指明的代理服务器提取(HTTP 1.1新)。

    307 temporary redirect 和302(found)相同,许多浏览器会错误的相应302应该进行重定向,即使原来的请求是post,即使它实际上只在post请求的应答是303时,才能重定向。由于这个原因,HTTP1.1新增了307,以便更加清楚的区分几个状态代码,当出现303应答时,浏览器可以跟随重定向的get和post请求,如是307应答,则浏览器只能跟随对get的请求的重定向。

  400 错误#
    400 Bad Request 请求出现语法错误。

    401 unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含-WWW-Authenticate头,浏览器据此显示用户名字和密码对话框,然后再填写合适的authorization头后再次发送请求。

    403 Forbidden 资源不可用。服务器理解客户的需求,但是拒绝处理他通常由于服务器上文件或目录的权限设置问题。

    404 NO Found 无法找到指定位置的资源,也是一个常用的应答。

    405 Method not allowed 请求方法(GET、POST、HEAD、Delete、put、trace等)对指定的资源不适用。(HTTP 1.1新)

    406 not acceptable 指定的资源已经找到,但是mime类型和客户在accpet头中所指定的不兼容(HTTP 1.1新)

    407 proxy authentication reqired 类似于401 ,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)

    408 request timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)

    409 conflict 通常和put 请求有关,由于请求和资源的当前状态相冲突,因此请求不能成功(HTTP 1.1新)

    410 Gone 所请求的文档已经不在可用,而且服务器不知道应该重新到哪一个地址,他和404的不同在于,返回407表示文档永久的离开了指定的位置,而404表示由于位置的原因文档不可用。(HTTP 1.1新)

    411 length required 服务器不能处理请求,除非客户发送一个contene-length头(HTTP 1.1新)

    412 preconfition Failed请求头中指定的一些前提条件失败(HTTP 1.1新)

    413 request entity too large 目标文档的大小超过服务器当前原意处理的大小。如果服务器认为自己能够稍后再处理请求,则应该提供一个retry-After头(HTTP 1.1新)

    414 Request URL Too loog URL太长( HTTP 1.1新)

    416 required range not satisfiable 服务器不能满足客户在请求中的指定range 头(HTTP 1.1新)

  5xx服务器错误#
    500 internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求

    501 Not lmplemented 服务器不支持请求所需要的功能。例如,客户发出来了一个服务器不支持的put请求。

    502Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。

    503 service unavilable 服务器由于维护或者负载过重未能应答。例如,servlet 可能在数据库连接池已满的情况下返回503.服务器返回503时可以提供一个retry-after头。

    504 gateway timeout 作为代理或网关服务器使用,表示不能及时的从远程服务器获得应答(HTTP 1.1新)

    505 HTTPversion not supported 服务器不支持请求中所指明的HTTP版本。(HTTP 1.1新)
八、错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{error: "Invalid API key" }
```

### 九、返回结果
``` bash
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
```
### 十、Hypermedia API
* RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
``` bash
{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
```
*上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
``` bash
{ "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", // ...
}
```
* 从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果
``` bash
{ "message": "Requires authentication", "documentation_url": "https://developer.github.com/v3" }
  • 上面代码表示,服务器给出了提示信息,以及文档的网址。

    十一、其他

  • (1)API的身份认证应该使用OAuth 2.0框架。
  • (2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
  • RESTful的优点:
    • 可更高效利用缓存来提高响应速度
    • 通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
    • 浏览器即可作为客户端,简化软件需求
    • 相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
    • 不需要额外的资源发现机制
    • 在软件技术演进中的长期的兼容性更好
分享到

Unix的五种网络模型

概念

  • blocking IO(阻塞IO)
  • nonblocking IO(非阻塞IO)
  • IO multiplexing(IO多路复用)
  • signal driven IO(信号驱动IO)
  • asynchronous IO(异步IO)

要弄清楚Netty的原理,以及java nio2.0相关的原理,需要了解UNIX底层的IO基本原理。

  • IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:1.等待数据准备 (Waiting for the data to be ready)2.将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。blocking IO 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样
  • 此处应有图片一
  • 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network
    io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。所以,blocking IO的特点就是在IO执行的两个阶段都被block了。non-blocking IO linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
  • 此处应有图片二
  • 从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有。
  • IO multiplexing,IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也 称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
  • 此处应有图片三
  • 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
  • Asynchronous I/O linux下的asynchronous IO其实用得很少。先看一下它的流程:
  • 此处应有图片四
  • 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
  • 此处应有图片五和六
  • 参考:https://blog.csdn.net/historyasamirror/article/details/5778378
    Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”
    《Netty权威指南》第二版
    《Netty实战》
分享到

同步异步阻塞非阻塞

概念

  • 同步:执行一个操作之后,等待结果,然后才继续执行后续的操作。
  • 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
  • 阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
  • 非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。

Netty域Mina的关系

  • mina与netty都是Trustin Lee的作品,所以在很多方面都十分相似,他们线程模型也是基本一致,采用了Reactors in threads模型,即Main Reactor + Sub Reactors的模式。由main reactor处理连接相关的任务:accept、connect等,当连接处理完毕并建立一个socket连接(称之为session)后,给每个session分配一个sub reactor,之后该session的所有IO、业务逻辑处理均交给了该sub reactor。每个reactor均是一个线程,sub reactor中只靠内核调度,没有任何通信且互不打扰。
分享到

Spring Validation的用法

概念

    1. JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
      这包括方便地支持将JSR-303或JSR-349bean验证提供程序引导为springbean默认情况下,如果类路径 上存在Bean验证(比如, Hibernate Validator),LocalValidatorFactoryBean将会被注册为全局验证器,来供在controller层的方法参数上加入@Valid或者@Validated 注解的参数使用。
      在java配置的方式下,你可以通过以下方式来个性化校验器
1
2
3
4
5
6
7
8
9
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

@Override
public Validator getValidator() {
// ...
}
}

您也可以通过以下方式注入自定义的validator

1
2
3
4
5
6
7
8
9
@Controller
public class MyController {

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());

}
}
    1. Spring Validation
      spring3对其校验带来了一些提升支持,首先对JSR-303 Bean Validation API 进行了全面的支持,第二点使用编程的方式,Spring的DataBinder能够验证那些绑定了的验证对象。第三点 Spring MVC支持声明性地验证@Controller的输入。
  • 2.1 JSR-303 Bean Validation API摘要
    JSR-303标准化了Java平台的验证约束声明和元数据。通过使用该标准化的API,您可以对域模型的属性进行申明式的验证,在运行时作用于这些域。您可以使用许多内置约束,同样您也可以自定义自己的验证。
    考虑下面的示例,它显示了一个具有两个属性的简单PersonForm模型:

1
2
3
4
public class PersonForm {
private String name;
private int age;
}

JSR-303允许您针对此类属性定义声明性验证约束,如下例所示:

1
2
3
4
5
6
7
8
9
public class PersonForm {

@NotNull
@Size(max=64)
private String name;

@Min(0)
private int age;
}

当JSR-303验证器验证该类的实例时,将强制执行这些约束。

  • 2.2 配置一个bean的验证实现者
    spring 对 Bean Validation API提供了全面的支持。
    这包括方便地支持将JSR-303或JSR-349bean验证提供程序引导为springbean
    它允许你通过注入一个javax.validation.ValidatorFactory或者javax.validation.Validator到您校验需要的地方

JSR303定义的校验类型

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
JSR303定义的校验类型

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
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证注解的元素值长度在min和max区间内

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

@Range(min=, max=) 验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

在检验入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

1.    分组:@Validated 支持分组,不展开讲了,@Valid不支持分组

2.    注解地方
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。
搞个表格直观一点:

1
2
3
4
类型/是否可以 | 使用在类型上 | 使用在方法上 | 使用在方法参数上 | 构造函数 | 成员属性(字段)
--------- | ------------- | ------------- | ------------- | ------------- | -------------
@Validated | 是 | 是 | 是 | 待确认 | 否
@Valid | 否 | 是 | 是 | 是 | 是
  1. 嵌套验证:在比较两者嵌套验证时,先说明下什么叫做嵌套验证。
    比如我们现在有个实体叫做Item:
1
2
3
4
5
6
7
8
9
10
public class Item {

@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;

@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}

Item带有很多属性,属性里面有:pid、vid、pidName和vidName,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Prop {

@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;

@NotNull(message = "vid不能为空")
@Min(value = 1, message = "vid必须为正整数")
private Long vid;

@NotBlank(message = "pidName不能为空")
private String pidName;

@NotBlank(message = "vidName不能为空")
private String vidName;
}

属性这个实体也有自己的验证机制,比如pid和vid不能为空,pidName和vidName不能为空等。
现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:

1
2
3
4
5
6
7
8
@RestController
public class ItemController {

@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}

在上图中,如果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
2
3
4
5
6
7
8
9
10
11
public class Item {

@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;

@Valid // 嵌套验证必须用@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private List<Prop> props;
}

然后我们在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
@RestController
@Validated
public class TestController {

@GetMapping("/NotEmpty")
public String notEmpty(@NotEmpty String me){
return "NotEmpty";
}

@GetMapping("/NotNull")
public String notNull(@NotNull String me){
return "NotNull";
}

@GetMapping("/NotBlank")
public String notBlank(@NotBlank String me){
return "NotBlank";
}

@GetMapping("/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
//嵌套类型
@PostMapping("/item/add0")
public void addItem0(@Validated Item item) {
System.out.println("dosmt0");
}

@PostMapping("/item/add1")
public void addItem1(@Validated Item item, BindingResult bindingResult) {
System.out.println("dosmt1"+bindingResult);
}

@PostMapping("/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密集型与CPU密集型

IO密集型与CPU密集型

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

分享到

Java的NIO线程模型

概念

  • Thread per Connection(阻塞IO模型): 在没有nio之前,这是传统的java网络编程方案所采用的线程模型。即有一个主循环,socket.accept阻塞等待,当建立连接后,创建新的线程/从线程池中取一个,把该socket连接交由新线程全权处理。这种方案优缺点都很明显,优点即实现简单,缺点则是方案的伸缩性受到线程数的限制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

//创建一个新的ServerSocket用以监听某个端口的socket请求
ServerSocket serverSocket = new ServerSocket(portNumber);
//对accept的调用将被阻塞,直到某个连接被建立
Socket clientSocket = serverSocket.accept();

BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;

while ((request = in.readLine()) != null) {
if ("Done".equals(request)) { //如果客户端请求为Done则退出循环
break;
}
//把每一行读取出的数据交给服务端处理方法
response = processRequest(request);
//将处理结果返回给客户端
out.println(response);
}
  • Reactor in Single Thread: 有了nio后,可以采用IO多路复用机制了。我们抽取出一个单线程版的reactor模型,时序图见下文,该方案只有一个线程,所有的socket连接均注册在了该reactor上,由一个线程全权负责所有的任务。它实现简单,且不受线程数的限制。这种方案受限于使用场景,仅适合于IO密集的应用,不太适合CPU密集的应用,且适合于CPU资源紧张的应用上。
  • Reactor + Thread Pool: 方案2由于受限于使用场景,但为了可以更充分的使用CPU资源,抽取出一个逻辑处理线程池。reactor仅负责IO任务,线程池负责所有其它逻辑的处理。虽然该方案可以充分利用CPU资源,但是这个方案多了进出thread pool的两次上下文切换。
  • Reactors in threads: 基于方案3缺点的考虑,将reactor分成两个部分。main reactor负责连接任务(accept、connect等),sub reactor负责IO、逻辑任务,即mina与netty的线程模型。该方案适应性十分强,可以调整sub reactor的数量适应CPU资源紧张的应用;同时CPU密集型任务时,又可以在业务处理逻辑中将任务交由线程池处理,如方案5。该方案有一个不太明显的缺点,即session没有分优先级,所有session平等对待均分到所有的线程中,这样可能会导致优先级低耗资源的session堵塞高优先级的session,但似乎netty与mina并没有针对这个做优化。
  • Reactors in threads + Threads pool: 这也是我所在公司应用框架采用的模型,可以更为灵活的适应所有的应用场景:调整reactor数量、调整thread pool大小等。
分享到

并发编程

如何减少上下文切换

  • 无锁并发编程
  • CAS算法
  • 使用最少的线程
  • 协程

什么是死锁

两个或多个线程等待彼此锁释放而无法继续运行形成死锁。

如何避免死锁

  • 避免一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源
  • 尝试使用定时锁lock.tryLock(timeout)来替代使用内部锁
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里

volatile

  • lock前缀指令会引起处理器缓存回写到内存
  • 一个处理器的缓存回写到内存会导致其他处理器的缓存无效

synchronized java的所有对象都是锁

  • 对于同步方法,锁的是当前实例对象
  • 对于静态同步方法,锁的是当前类的Class对象
  • 对于同步方法块,锁的是Synchonized括号配置的对象
分享到

蚂蚁金服一面

1. HashMap&ConcurrentHashMap

2. 再谈谈一致hash算法?

3. 乐观锁&悲观锁?

4. 可重入锁&Synchronize?

5. 事务四大特性?

6. 事务的二段提交机制?

7. 聚簇索引&非聚簇索引?

8. 用自己的实践经历说一下索引的使用场景(说一个就要举一个例子)?

9. 当前读&快照读?

10. 类加载过程?

11. 双亲委派机制及使用原因?

12. 说说GC算法?

13. Http&Https的区别

14. Https的加密方式

15. 线程池的核心参数和基本原理

16. 线程池的调优策略

17. 你有什么职业规划

分享到