邱永臣与世界(A)

拜托你不要闹了组长
什么?真是叫人担心
真是任性的家伙
你的长相也不适合小孩子观赏…
谁稀罕那种扁扁的洗衣板…
你这个年纪还有什么不好意思的欧巴桑
这种小事就不要计较了嘛,这不是小事~
原来如此,我完全明白了,说明的真有趣,原来是这样哦
还不晓得将来会变成怎样咧
也可以这么说了,对了,你要去哪里啊…
我忽然想独自漫步在陌生的街道,好想去远方流浪
没有啊,随便聊聊而已嘛
你好奸诈,你根本就没有相信我
这么幼稚的把戏,我早就玩腻了
啰嗦的女人会惹人讨厌
小心变成大屁股的老太婆。
我没有那么好。
讲不过我,就随便编个理由来应付。
你看起来很烦恼,是不是男朋友打来的。
随便,都可以。
没关系,我只想一直抱着你。
真受不了你们女人。
你看着我我会不好意思的。
我没有那种心情。
我好累。
好了,反正人生不就这样吗?
你年纪也不小了,应该学会自己尿尿了。
我是在百忙中抽空过来的。
不客气,拜拜。
看你这么想要,就送给你吧。
我只是想要老师开心一点,你为什么要生气呢?
你有什么要拜托我就直说好了<( ̄︶ ̄)>
真是伤脑筋。
真是拿你们这些小孩子没方法,我就陪你们玩好了。
别人说的话他是绝对不会听的。
《蜡笔小新》

31.地球另一极的大聪说:『读书的时候你就喜欢偷换概念,真是江山易改本性难移,明明是找别人麻烦,偏偏借口说什么要追求完美,容不得瑕疵』
邱永臣尴尬的回答道:『那也不是我的本意啊,我确实是看那个男的不爽,让他注意点形象嘛,我确实是觉得他的外表有问题呀』
大聪苦笑:『呵,我看你就是不想活了,就和你追随的那位神人一样,世界的规则捆绑不了你,你已经跳出三界外不在五行中』

30.江湖是什么?有人的地方就是江湖。

29.安全感是个很玄的东西。
明白,了解,已知晓。

28.离开公司回家的路上,一个面容清秀的女生双手抱膝,坐在建行门口的梯道上,忘我地注视着街上的车流。
(那一瞬间,我回想起女魔头连诗洁和彭苏婷,也不知道那两尊天神现况如何,也不知挂了没有,阿西吧)

27.你好,我叫邱永臣,是红叶集团的设计师,请多多关照。

26.“吾名为凉宫春日,我对普通的人类没有兴趣。你们之中要是有外星人、未来人、异世界的人、超能力者,就尽管来找我吧!以上。”
到头来,一个奇怪的人都没找到…

25.齐普夫法则和布尔检索模型告诉我们:任何一个网页都必定出现某76个字。

24.“你们不知道我的器量有多大…来这里是为了学习技能提升自我,若是没法提升,我便是离开。”此人真决断

23.如果世间真有上帝,面对无比复杂深奥的人间,他的言将是无言,他的语将是无语。

22.香农在论文里提出“信息熵”的概念,意思是越复杂的东西越难以用语言描述。(平时年轻人动不动就说一句我爱你,可见爱是多么简单)。所以每天哔哩哔哩不停的人,其表达的内容都很肤浅,说的都是废话,而废话正是最具攻击力和防御力的话。

21.匹夫无罪,怀璧其罪。

20.在梦里,那是我的故乡。

19.罗大佑在歌词里唱:“爱情这东西我明白,但它永远是什么?”那说的是他自己。

18.一堆人在那叫啊唱啊,信仰耶稣赞美主,一个声音在大喊:“野蛮蛮,放马过来!”

17.在一个移动的体系里看另一个体系的时间和距离,得用一种叫“洛伦兹变换”的光学方式,简单的说,就是对时间或距离,得除或乘一个小写的伽马。

16.和老同志交流了一番,邱永臣对人与人的相处合作才有了新认识新思想新境界,哎呀呀,在爱的文化里浸淫多年,是会让人失去辨别真伪的能力滴。

15.雄兔脚扑朔,雌兔眼迷离,双兔伴地走,你怎么分得清哪个是女同哪个是男同?

14.按照惯例,周六午夜在街边漫无目的地踱步。和湛江、广州一样,除了骑电瓶的老外与Taxi,路上一个人都没有。

13.我只想说一句:“我不喜欢去喜欢除了我之外的人,也不喜欢哪个女的或是哪个男的,至少现在不喜欢,,不喜欢有什么女朋友或很好的人。”

12.你说一个程序员,想写什么代码都不能写,只能写别人的代码,这还有什么乐趣呢,想到这,邱永臣只能哭笑不得。

11.惶惶忽,我发现我既不是同性恋,也不是异性恋,我是自恋,挺好。

10.在梦里,人经常从一个地方转移到另一个地方,上海,北京,西安,京东,温哥华,纽约,哥本哈根,月球,火星,然后是太阳系外,银河系外,然后梦就醒了。

9.睡觉前,室友和他女友用微信在嗡嗡说情话。在睡梦里,邱永臣做着另一个世界才会存在的事。一个小时后,被吵醒,居然还是室友在说情话……

8.脸色苍白浑身颤抖的人们,如果有机会,我将向你们述说爱的文化如何夺走一个人的理智。

7.人总是要做些什么的,哪怕是在路边躺着,占个位置,影响了世界的运转,总比庸庸无为好上一茬,和死人和尸体便是有区别。

6.此时此刻,邱永臣有种仿若投胎转世的错觉。

5.那一刻,邱永臣感动得眼泪都出来了,他像在大学时一样大声狂喊:“Do it! man, do it! Do that stupid fucking thing”,然后以头将地,用拳头不停地砸枕头,全然忘记今天打听到喜欢的女孩已经结婚的事实。

4.来咯喂,走过看过千万别错过了喂,年轻程序员,身强体壮,战斗力强,持久力惊人,每天工作16小时不在话下,年轻易调教,物美价廉,这位美女,要不要来一打?[流汗]

3.她说:“我们这里非常的多元文化,瞧那,那伊朗人,对面坐着意大利人,那中国人,对面坐着台湾人。”
邱永臣心里边可好笑了,这中国人最怕别人说台湾人不是中国人一部分,再说那台湾哥们还是外省人,动不动就说他们上海人怎么怎么滴。

2.这年头下载个老外的软件也挺难的。日本——>美国服务器——>阿里云——>本机(软件违禁级别比较高,我的廉价vpn穿不出去)

也不知道我在万恶帝国主义的疆域上路过时,他们是否想抓住我,研究研究我到底是谁,审查审查,严肃地问一句:“你丫到底是中国人还是亚洲人!”

1.剧情发展至此,何从感叹地背了一段中国当代诗人关于量子力学的作品:“人生的道路虽然漫长,但紧要关头就那么几步。”

部署Rest架构的Web应用(美团云)

先前,创建且在本地成功运行rest架构的项目,该怎么样部署到远程机器上呢?

导出war包

在导出包前,先设置artifacts的打包过程(file->project structure->Artifacts->选中rcs-web:war->勾选build on make)。接着,在本地运行一遍项目,找到打包好的war包。

搭建远程环境

(手头有台美团云,所以就用它做示例。)

jdk

现在(2016-11-07 21:32:03)美团云默认装载JKD1.7,如果没有的话,可以手动安装:

1
yum install java-1.8.0-openjdk.x86_64

而后验证:

1
java -version

tomcat7

(注:tomcat6带不起上一篇博文里的Rest架构项目)
从tomcat官网下载tomcat7(推荐使用wget),然后使用以下命令启动tomcat:

1
2
3
4
tar -zxv -f apache-tomcat-7.0.29.tar.gz // 解压压缩包
rm -rf apache-tomcat-7.0.29.tar.gz // 删除压缩包
mv apache-tomcat-7.0.29 tomcat
/usr/local/tomcat/bin/startup.sh //启动tomcat

(可以考虑让tomcat7开机自启)

编辑tomcat的权限文件:

1
emacs /usr/local/tomcat/conf/tomcat-users.xml

加入几行:

1
2
3
4
<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<role rolename="manager-status"/>
<user username="root" password="root" roles="admin-gui,tomcat,role1,manage-status,manager-gui"/>

然后重启tomcat7。

部署应用

访问http://ip:8080/manager/html,在”war file to deploy”一栏里发布之前导出的war包。

再次访问http://ip:8080/rcs-web/user/,即可看到浏览器渲染出的JSON

Rest风格的后端web(SpringMVC)

前后端分离

天啦噜,我们希望『前后端分离』,前端的组件是css/js/html/图片,负责显示数据。数据打哪儿来?答案是后端。

后端服务器专门服务于前端,兢兢业业,勤勤恳恳,前端要什么,后端就给什么。

比如,前端向后端发出请求:www.ilovcecl.com/users/,目的是获取用户列表;逻辑复杂的后端服务器掐指一算,洞悉前端要的数据,便以JSON格式返回用户列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"id": 3,
"name": "Jerome",
"age": 45,
"salary": 30000
},
{
"id": 4,
"name": "Silvia",
"age": 50,
"salary": 40000
}
]

可见,前后端分离的过程中,后端会以JSON格式返回数据,数据URI符合Rest风格(当然前后端通信除了Rest外还有其它架构)。

本文给出的正是以JSON风格返回数据、符合Rest风格的后端web项目,不依赖于xml配置,结构简单。

操作说明

你可以从GitHub下载项目源码:https://github.com/qiuyongchen/rcs-web/archive/v1.0.0.zip
使用IntelliJ IDEA导入项目,自动刷新pom依赖(部分IDEA版本有Bug无法刷新,请在shell中输入mvn clean package,手动刷新);导入完成后配置tomcat,启动项目,访问http:localhost:8080/user/,耐心等待几秒钟,JSON即展现你眼前。

你也可以一步步地创建项目,添加文件,最后启动。

项目结构

本项目里,除了一个pom文件外,其余的均是java文件,不依赖传统的spring配置xml。
结构图:

src

pom文件

整个项目里唯一的配置有木有!!

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ilovecl</groupId>
<artifactId>rcs-web</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<name>rcs-web</name>

<properties>
<springframework.version>4.2.0.RELEASE</springframework.version>
<jackson.version>2.5.3</jackson.version>
</properties>

<dependencies>
<!--SpringMVC Rest begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<!--SpringMVC Rest end-->

<!--jackson begin-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--jackson end-->

<!--servlet begin-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!--servlet end-->
</dependencies>


<!--编译管理-->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>src/main/java</warSourceDirectory>
<warName>rcs-web</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<finalName>rcs-web</finalName>
</build>
</project>

项目初始化 WebInitilizer.java

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
package config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
* spring会自动在源码包中(pom->warSourceDirectory)寻找继承
* AbstractAnnotationConfigDispatcherServletInitializer的类,
* 将其作为web项目的初始化类
* Created by qiuyongchen on 2016/11/5.
*/
public class WebInitilizer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{WebConfiguration.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

}

项目配置类 WebConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package config;

/**
* 如果源码直接放在pom->warSourceDirectory下,
* 则basePackages为'.'即可;
* 如果源码放在更深层级,
* 则需指定目录层级,如'com.xxx'
* Created by qiuyongchen on 2016/11/5.
*/

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = ".")
public class WebConfiguration {

}

控制类 RcsController.java

一个web项目最重要的类…

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
package controller;

/**
* 凡是带有注解@RestController的类,
* 都被当做controller处理
* Created by qiuyongchen on 2016/11/5.
*/

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import model.User;
import service.UserService;

@RestController
public class RcsController {

@Autowired
UserService userService; //Service which will do all data retrieval/manipulation work


//-------------------Retrieve All Users--------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if (users.isEmpty()) {
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}


//-------------------Retrieve Single User--------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(@PathVariable("id") long id) {
System.out.println("Fetching User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}


//-------------------Create a User--------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
System.out.println("Creating User " + user.getName());

if (userService.isUserExist(user)) {
System.out.println("A User with name " + user.getName() + " already exist");
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}

userService.saveUser(user);

HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}


//------------------- Update a User --------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {
System.out.println("Updating User " + id);

User currentUser = userService.findById(id);

if (currentUser == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}

currentUser.setName(user.getName());
currentUser.setAge(user.getAge());
currentUser.setSalary(user.getSalary());

userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}

//------------------- Delete a User --------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {
System.out.println("Fetching & Deleting User with id " + id);

User user = userService.findById(id);
if (user == null) {
System.out.println("Unable to delete. User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}

userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}

//------------------- Delete All Users --------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
System.out.println("Deleting All Users");

userService.deleteAllUsers();
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}

}

Model类 User.java

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
package model;

/**
* Created by qiuyongchen on 2016/11/5.
*/
public class User {

private long id;

private String name;

private int age;

private double salary;

public User() {
id = 0;
}

public User(long id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id != other.id)
return false;
return true;
}

@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age
+ ", salary=" + salary + "]";
}

}

Service接口 UserService.java

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
package service;

import model.User;

import java.util.List;

/**
* Created by qiuyongchen on 2016/11/5.
*/
public interface UserService {

User findById(long id);

User findByName(String name);

void saveUser(User user);

void updateUser(User user);

void deleteUserById(long id);

List<User> findAllUsers();

void deleteAllUsers();

public boolean isUserExist(User user);

}

Service接口实现类 UserServiceImpl.java

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
package service.impl;

/**
* Created by qiuyongchen on 2016/11/5.
*/

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import model.User;
import service.UserService;

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {

private static final AtomicLong counter = new AtomicLong();

private static List<User> users;

static {
users = populateDummyUsers();
}

public List<User> findAllUsers() {
return users;
}

public User findById(long id) {
for (User user : users) {
if (user.getId() == id) {
return user;
}
}
return null;
}

public User findByName(String name) {
for (User user : users) {
if (user.getName().equalsIgnoreCase(name)) {
return user;
}
}
return null;
}

public void saveUser(User user) {
user.setId(counter.incrementAndGet());
users.add(user);
}

public void updateUser(User user) {
int index = users.indexOf(user);
users.set(index, user);
}

public void deleteUserById(long id) {

for (Iterator<User> iterator = users.iterator(); iterator.hasNext(); ) {
User user = iterator.next();
if (user.getId() == id) {
iterator.remove();
}
}
}

public boolean isUserExist(User user) {
return findByName(user.getName()) != null;
}

private static List<User> populateDummyUsers() {
List<User> users = new ArrayList<User>();
users.add(new User(counter.incrementAndGet(), "Sam", 30, 70000));
users.add(new User(counter.incrementAndGet(), "Tom", 40, 50000));
users.add(new User(counter.incrementAndGet(), "Jerome", 45, 30000));
users.add(new User(counter.incrementAndGet(), "Silvia", 50, 40000));
return users;
}

public void deleteAllUsers() {
users.clear();
}

}

技术栈.分布式监控CAT

Cat是什么?

一种实时、全量性的开源监控系统,主要是监控JAVA后端(开源地址:http://github.com/dianping/cat

Cat监控的东西?

代码执行次数;
代码执行耗时(极强!一个URL深入到DB的全过程);
业务指标(诸如支付率、转化率、订单数);
定时上报JVM、GC

Cat的特性有哪些?

a.全量
有足够的能力,记下以往每天的数据(异步地压缩前一天的监测数值,保存到HDFS)。
b.实时
遇到埋点,客户端在MQ通道里发送代码监测情况,服务端异步读取消息队列,分析并保存为当前小时报表(用的正是实时计算!)
c.高可靠
追求高可靠不简单哪,Cat为了尽可能存活得久,站得稳,使用了异步序列化、异步传输(自定义序列化协议,放弃通用序列化协议,不仅如此,还使用Netty的NIO来保证通信)
d.高扩展
消息异步发送到消息队列,历史报表异步发送到HDFS,各个步骤均可以分布式部署,近乎横向地增加机器,扩展性能极强哦~
由于很多步骤都异步化,所以对系统的实时性处理要求不高,就算是实时分析当前小时的报表时遇到瓶颈,Cat也可以多开几个线程,并发消费消息,并进行相应的增量计算。
e.搞吞吐
每天Cat处理的数据高达100T,要将这么多数据都存下来,真不容易!

Cat的消息存储

每个消息的ID都是定制的,格式都是『应用名称-机器IP-时间除以小时得到的整点数-索引INDEX』,存储结构如下图:

根据特定机器,并根据索引index去找到48bits的索引,根据索引找到压缩数据,解压,据偏移地址拿到64K的消息。

关于优化的思考(初期)

交代

总而言之,言而总之,80%问题由20%错误引起(2/8原理适用于金钱分配、权力分配、人口位置、缓存命中、时间效率、问题根源等等等等,非常神奇有木有)。

此次做优化,我渐渐地觉得,写代码追求的不是『越多越好』,也不是『越少越好』,而是『在保证质量的情况下代码尽可能少』。『越多越好』或『越少越好』只有数量一个参数,需要再加上质量参数,质量参数的权重是数量参数的十倍(在特殊情况下甚至能膨胀至百倍千倍)。

业务这玩意不像烧饼,上线后就不管,后续的接口维护优化不可少。业务代码,考验的不是能不能写出来(是个人都能写业务),是写出来的代码质量。

正如28原则,引起我们优化的原因太少,优化到最后,我已觉得自己在做重复工作。

依据现有(2016-10-31)的标准,接口的95线应小于100ms,给前端提供的URL应小于200ms。200ms,这是什么概念?一般的接口实现,代码在CPU里执行时间往往不到5ms,剩余92ms浪费在多线程等待、锁等待、网络IO、SQL执行。

大头SQL

多线程等待或者锁等待不常见,最常见的是网络IO和SQL,其中SQL在接口耗时中占据了大头。

一条有索引的单条件select查询(select * from TABLE where Condition=A limit 1),仅仅花费1ms罢了。这次优化中部分SQL的Condition要么没有索引,要么Condition的种类太少索引相当于不存在;部分SQL并没有limit的概念,动不动就查询几千几万条数据,数据库连接不多写操作不多还好些,如果数据库已经到了瓶颈,一次查询海量数据也是灾难;部分SQL使用了in语句,in语句中有上百个可选性,好可怕。

上述SQL多少可以被原谅,有些超低级错误SQL,好可怕。

循环SQL:『在一段代码里,没有使用批量化SQL(一次查询有限数量),反而在for循环中调用同一个DAO操作。』

仔细想一想,我发觉,SQL是绝对性的瓶颈(在接口耗时中,SQL占比超过50%),业务逻辑越加复杂,SQL没有大突破,怕是要被淘汰。

大头网络

网络连接非常极其以及特别的不稳定,尽管RPC大部分是同机房传输,遇到海量连接并发时,RPC的延迟就不可控了。

偏偏有那么一些人,出于复杂度或业务时间考虑,总是复用现有RPC,而不是开发批量化RPC,于是出现了循环RPC。

循环RPC:『在for循环中调用某RPC,该RPC本可以拥有批量版本。』

最坏的情况莫过于,一个接口祭出循环RPC,祭出的RPC里是循环SQL。

有一种RPC的错误使用方式可能被忽略,那就是使用RPC传输的数据大小。RPC在传输之前必须序列化,序列化几十K数据比较简单,但序列化1M数据就让CPU心寒了。

尺寸对QPS影响也不可小觑:对于1K数据,pigeon服务端qps能到10几w,而1M,大概就只能到几千。

缓存

有些非常关键的接口,比如获取一个用户的详细订单信息,需要查询多个表,调用多个RPC服务,接口的耗时接近100ms。

面对这种棘手情况,可以使用『反第三范式』,将用户的订单冗余到一个表之中,只需调用一个RPC,查询一个表即可。可惜订单的状态多,变动频繁,『反第三范式』的代价太高(保持多个表的实时一致让人伤脑筋)。

除了反范式,也可以使用缓存,将接口即将返回的数据保存的redis里头,下次查询让缓存打头阵。

缓存优点挺多,不仅可以分担数据库的压力,还能减少响应时间,瞬间返回。

可惜缓存的最佳使用情景不多(最佳情景:99.9%的请求均可以在缓存中找到),绝大部分数据仅仅是普通的热点数据,80%的请求对应20%缓存数据,剩余20%请求由于缓存过期,不得不重新查SQL,耗时又一次接近100ms。

也就是说,大部分情况下,缓存只能优化80%的请求,剩余20%请求还是会超时。

搜索

SQL不擅长批量查询(不管是in,还是海量结果的查询),正所谓人各有长短,SQL不行,但搜索非常适合批量查询。

搜索相当于半离线的数据库,每一条数据都有极多索引,只要条件合理,查询非常的快。另外,搜索还能分担数据库的压力。

和数据库相比,搜索的缺点是实时性没保证(增量间隔几秒钟时间),在对实时性要求比较高的场景没法用。(用户支付完,在好几秒时间内显示未支付,真伤脑筋)

但在商家后台等一些项目里,搜索大有发挥之地,理论上,整个商家后台都不必直接查询SQL,只需和搜索打交道即可。

分页

若是没有分页,哪怕使用批量搜索,一次性查询海量数据,怕是神仙也受不了。所以在查询批量数据时,决不能忽略分页。

在前端列表显示数据考虑分页,在搜索时考虑分页,在SQL查询时考虑分页。扩展开来,SQL中若有in关键字,也要限制可选项的数量。

完美结局

综合来看,分页、搜索、缓存和网络IO,无一不是因为数据库吊车尾。

SQL负于Cache

梦萦国庆

2016年的国庆节,部门业务出现离奇的SQL:
每天,当人们从睡梦中醒来又再一次进入梦乡时,短短的12小时,数据库查询次数高达千万。
当时,部门订单量不破1000,何来千万级别的select?

经过一个时辰的调查走访,邱永臣发现了幕后黑手,它不是代码,不是BUG,而是公司内部的订单中心。

原来,在APP改版后,为了适应PM的需求,在用户切换到『我的』tab的瞬间,订单中心便是耗费巨大的时间和资源,轮询全公司上下20个业务部门的服务,调查该用户在各部门的订单,进而计算出总订单数量。

此案怎破?

手段一

向订单中心反馈,要求改『订单中心轮询』为『业务主动推送』。对方果断拒绝,原因:涉众过多,一时半会无法更改。细想之下,也是,似乎有点道理。

手段二

部门内全部放开RPC,只拦截SQL。压力从SQL身上挪开,转到何方?我们想到了KV缓存。部门内的用户挪移至KV缓存,订单中心的轮询到达时,首先让缓存承压,若该用户真属于我部门,放行SQL,从数据库中拿订单信息,不然,别怪我们心狠,拦截该SQL。

做了优化后,SQL从每天的2500W将至50W,而缓存则由0暴增至6000W。

完美结局

从此,凭借巅峰时期QPS承受30W的KV,无论那订单中心再请求,数据库也不会受丝毫影响。

使用 AOP 记录日志

几天前,本教主接到mentor布置的任务:使用 AOP 来记日志,替代掉重复日志的代码。

本教主自号聪明机智寡言侠义小郎君,此事自然是小Case。

躬身亲做AOP

为了使用 AOP,首先我们得认识到 AOP 是什么对吧,术语原理什么的请翻以前的博文,此番写一下 AOP 的种类。

  1. 总所周知,spring 是个强大的容器,本身实现spring AOP。
  2. 除了spring AOP,业内还有个叫 AspectJ 的玩意,它比 spring AOP 强大了很多很多,它有多厉害?它强大到spring AOP只实现了它部分的功能,spring AOP 只能拦截普通方法,AspectJ 却能拦截构造方法和字段。

所以总结地说,下面将会有2种 AOP,一种是 spring AOP,一种是 AspectJ。

一、spring AOP

实现 spring AOP 有两种做法,一种是使用 xml 来定义切面、切点和通知,另一种是使用@Aspect注解(这种做法实际上是前一种的简化版)

使用 xml 来定义切面

此种方法稍微累赘(spring曾因为大量使用xml配置而被诟病,后来慢慢转向使用注解)。

1.首先要写一个切面(aspect):

1
2
3
4
5
6
7
8
9
10
/**
* Created by qiuyongchen on 2016-09-01 17:54:32.
*/
public class OtaOrderLogAspect {

public String OtaTaskLog(){
doSomething()...
}

}

这个切面里的 OtaTaskLog就是一个 advice 通知,还记得我之前说过的话吗?

一个切面里,有切入点,有通知,如果切入点匹配到了被代理的函数,就启动通知。

上边代码里的OtaOrderLogAspect类就是个切面,里边的OtaTaskLog方法相当于通知,若是好运,匹配到目标方法,就将其拦截,调用通知后再执行目标方法。

2.其次需要将OtaOrderLogAspect托管给 spring。

1
2
<bean id = "logAspect"
class="com.xxx. OtaOrderLogAspect"/>

到这里为止,spring 就知道该切面的存在了,那么,该怎么做,才能让 spring 在恰当的代码处启动通知,执行额外的动作呢?答案是使用 spring AOP。

3.使用 spring AOP

使用 spring AOP 特有的 xml 配置,逐个定义 aspect/pointcut/advice,如下:

1
2
3
4
5
<aop:config>
<aop:aspect ref="logAspect">
<aop:before pointcut="execution(* *(..))" method="OtaTaskLog" />
</aop:aspect>
</aop:config>

上面的配置中,定义了切面(就是OtaOrderLogAspect类),定义了一个 before通知,该通知对应的切入点是任意类里的任意方法(execution(* *(..))是一种 AspectJ 切入点表达式语言写出来的,该语言可以用来筛选我们想要拦截的方法,具体语法可浏览:http://www.cnblogs.com/javaee6/p/3779826.html),也定义了一个advice 的核心方法(advice分有 before/after/around 等几种),也就是OtaTaskLog,拦截到目标方法后,先执行OtaTaskLog方法,再执行目标方法。

使用@Aspect注解来创建切面

看了上面的那种写法,您是不是觉得有点繁琐呢?每次定义一个切点或切面都要额外写一个 xml 配置,神仙都觉得麻烦。
于是呢,在众多程序员的吐槽中,springAOP 增加了注解的用法,使用注解,完全不需要配置 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

@Service("otaOrderLogAspect")
@Aspect
@Slf4j
public class OtaOrderLogAspect {

private static final int LOG_MIN = 0;

private static final int LOG_MAX = 3820;

@Resource
private IOtaOrderLogService otaOrderLogService;

/**
* 供应商主动推送的接口处的切点
*/
@Pointcut("execution(* *(int, int)) && @annotation(OtaAopLog))")
public void OtaPushLogPointCut(){}

@Around("OtaPushLogPointCut() && @annotation(otaAopLog)")
public Response OtaPushLog(ProceedingJoinPoint proceedingJoinPoint, OtaAopLog otaAopLog) {
OtaOrderLog orderLog = new OtaOrderLog();

// 获取注解中的参数
int actionType = 0;
actionType = otaAopLog.actionType();
orderLog.setActionType((byte)actionType);

// 记录入参
Object[] args = proceedingJoinPoint.getArgs();
if (args.length == 2) {
orderLog.setOrderId((Integer) args[0]);
orderLog.setParam(String.valueOf(args[1]));
}

// 记录出参
Response response = new Response();
try {
response = (Response) proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}

orderLog.setResponse(JSONObject.toJSONString(response));

// 本地测试时需睡眠主线程,否则异步存数据会失败
// Thread.sleep(10000);

otaOrderLogService.saveLogAsyn(orderLog);
log.info("本次AOP记录的内容主要为:{}", orderLog);
return response;
}
}

使用@Aspect表明该类是切面,使用@Pointcut来过滤目标方法,使用@Around来创建通知。
除了代码,我们需开启 spring 的自动代理:

1
<aop:aspectj-autoproxy/>

做了以上两点,在任何一个拥有两个参数的方法前,加上一个@OtaAopLog注解,AOP 就能自动帮您记日志了。

二、AspectJ

AspectJ 的功能比 springAOP 强大多了,比如,AspectJ 可以在类执行构造方法时进行拦截。
为了使用AspectJ,需要声明独特的类型 aspect:

1
2
3
4
5
6
7
public aspect LogAspect {
pointcut logPointCut() : execute(* *(int, int));

after() returning() : log() {
doSomething()...
}
}

如果要将 bean 注入到LogAspect中,需要特殊的 xml 声明:

1
2
3
<bean class="com.xxx.LogAspect" factory-method="aspectOf">
<property name="xxxName" ref="xxx"/>
</bean>

之所以要这么声明,是因为 AspectJ的切面在 AspectJ 启动时就创建了,spring 无法干预,自然也就没法注入。
而使用切面提供的 aspectOf() 方法,就能获取由 AspectJ 创建的切面的实例,进而 spring 可以注入。

JDK源码之concurrentHashMap

当风再起时,你将找到心中的诗。

HashTable,空有线程同步的特性,因全程使用synchronized关键字,速度过慢,故被抛弃。

HashMap,新时代贵族,在HashTable的基础上做了大量优化而来,被广泛应用于各种场景中,却不再支持线程同步。

ConcurrentHashMap,在 HashTable 的基础上做了另一个方向的优化,在尽量保证速度的同时,支持全并发的读操作,支持部分并发的写操作(默认情况可达到16并发写)。

总数据结构

首先来看看 ConcurrentHashMap 的结构,既然它基于 HashTable 改造而来,它就和 HashTable 有一定的关联性。ConcurrentHashMap 中默认使用了16个Segment存储数据,如:

1
final Segment<K,V>[] segments;

每个 Segment 元素都可以看做一个传统的 Hashtable,拥有自己的写锁,看到这您明白了吗?ConcurrentHashMap 默认使用了16个 HashTable一样的东西,所以它才支持了16并发读。

Segment

再来看看Segment 又是怎么实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* The per-segment table. Elements are accessed via
* entryAt/setEntryAt providing volatile semantics.
*/
transient volatile HashEntry<K,V>[] table;

/**
* The number of elements. Accessed only either within locks
* or among other volatile reads that maintain visibility.
*/
transient int count;

/**
* The total number of mutative operations in this segment.
* Even though this may overflows 32 bits, it provides
* sufficient accuracy for stability checks in CHM isEmpty()
* and size() methods. Accessed only either within locks or
* among other volatile reads that maintain visibility.
*/
transient int modCount;

每个 Segment 段里头又是一个 HashEntry 数组,看到这,有 HashMap/HashTable 基础的同学瞬间就反应过来了:

HashTable 和 HashMap 里边也是用一个数组来存数据,要插入数据时预习算好数组Index,然后再插入到相应的位置,如果该位置已经有数据,就将新数据和已有数据组成一条新链,链头插入 HashEntry 数组中。

没错!就是这样,Concurrent 的结构也是一样的!

值得一提的是,此处的HashEntry 和 HashMap 的 HashEntry 有些不一样,ConcurrentHashMap 的如下:

1
2
3
4
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;

HashMasp 的如下:

1
2
3
4
final K key;
V value;
Entry<K,V> next;
int hash;

可见,hash 与 key 用 final 修饰,表明一生不变放纵不羁的态度,不会发生改变;而 value 和 next 则使用 volatile 修饰,保证了线程之间的可见性。

那么,有童鞋就问了:同样的结构,为什么 ConcurrentHashMap 就支持完全并发的读,而 HashMap 就不支持呢?

具体的原因,请看下面的解释。

ConcurrentHashMap的完全并发读

get 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}

在使用 get 操作时,会首先定位到某个 Segment,再定位该Segment上某个Index的元素,如果是条冲突链,就一直遍历完该链,而不管该链“已被遍历过的地方”发生了什么变化。注意这里的“已被遍历过的地方”,因为读的过程中可能某些元素会被其他线程改变,比如删除等,但由于其删除操作的特殊性,使得其支持并发性。

remove 操作

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
final V remove(Object key, int hash, Object value) {
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
pred = e;
e = next;
}
} finally {
unlock();
}
return oldValue;
}

在删除之前,首先申请重入锁,得到锁后,去遍历冲突链,以便删除数据。

put

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
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}

也是首先申请重入锁,申请到后再遍历冲突链。

值得注意的是,之前的 ConcurrentHashMap 使用了 final 来修饰 HashEntry 中的 next指针,那样每次删除时重新构建一条冲突链而不是申请锁,就能做到绝大部分的并发读。

AOP模块的实现

AOP是什么

要理解 AOP 是什么,请想象着你的面前有一条从左到右的线段,线段的最左端是代码执行起点,最右端则是执行终点。
现在代码执行到一半,也就是线段的中间时,那里藏着个人,他发现代码执行到他那里了,于是他就针对代码的执行做了一些记录,比如计算代码执行的速度和健壮度等资料。

Q:一般情况下,那个人是不会藏着那里的,为什么我们需要他那样藏起来呢?
A:因为我们不希望在代码不知情的情况下,做一些额外的动作。
有人又问了:要那个人藏在那里干嘛,直接在代码里写一些记录的模块不就行了?一般情况下我们是不需要他的,但是,如果有很多线端而我们不需要写多个记录模块呢?这时他就有用了,他可以同时观察多条线段,还能有选择地记录他想要的代码。

而所谓的 AOP,就是面向切面编程,在代码的中间弄一个切面,放入一个“他”,让他来观察,做一些额外的工作,比如做日志等,在许多条不交叉执行的代码中,只需要一个“他”,省时省力。

AOP 概念

advice通知

advice 就是上面所说的“额外工作”,advice 代表着当代码执行到某个方法时你想要做的额外工作,比如记录下该方法执行的次数?

pointcut切入点

pointcut 很容易理解,就是在代码执行过程中被切出的空位,在空位上可以执行你想要的额外工作。举个例子,代码执行过程中会执行4个方法,你希望在执行后3个方法时都记录下它们各自被执行多少次,那么,后三个方法的入口就是切入点:执行后3个方法之前先执行你的额外工作。

advisor 通知器

advisor 将 pointcut 和 advice 连接起来,当代码执行到切入点时,advisor使得代码跳到advice中去,执行完额外工作再跳回来。

target 目标

target就是上面所说的后三个方法,你定义的advice实际上增强了target。

AOP实现

AOP依赖了JDK的动态代理技术,给target生成一个代理对象,想要执行target时,并不是真正地执行target,而是执行代理对象的方法,由代理对象去调用target,由这里可以看出,target是完全不知道代理的存在的(这正是 AOP 的精髓,我们写的 AOP 模块并不影响实际业务的代码,而是悄悄地在幕后工作)

代理对象的简介

在 Spring 中,有两种生成代理对象的方法,一种是利用 JDK 的代理,另一种则是利用 GGLib。生成的代理对象中,包含了advice通知(也就是你想要的额外的工作),也包含了拦截器链和通知器。

生成代理对象时,需要指定target在何方,指定代理哪些方法,指定在target执行前代理对象需要执行的方法,只有这样,在执行 target 前,代理对象中的方法才会被调用,并先target一步执行额外的动作。

代理对象在执行额外的方法时,会进入“拦截链”去一个个对比哪个方法才是额外的方法(匹配的方式就是依据上面的通知器,通知器是用 IOC 容器的方式获得),遍历完拦截链后再执行target(使用反射的方式去执行target)。

拦截链

一般地,拦截链是一系列的拦截器,拦截器则是处在 pointcut 中。当代码跳入代理对象的方法体内后,后一个个地遍历切入点,如果符合,就根据advicor通知器,去唤醒并执行advice通知,直到所有的切入点均被遍历完。

结语

到此为止,AOP就可以正常地在业务代码不知情的情况下运转了。

IOC容器的实现

什么是 IOC

所谓 IOC,inversion of controller,也就是控制反转。控制?控制什么呢?控制着对象之间的依赖关系,也就是说Spring 反转了对象之间的依赖关系。

举个例子,对象A中需要对象B(也叫A依赖着B),A直接new出一个B,此时,依赖关系由A控制;而在IOC中,A不再亲自new出一个B,而是由IOC容器来生成B,并赋给A,此时,依赖关系由IOC 容器控制,也就是说,控制从A反转到IOC 容器

IOC 容器

IOC可以控制着依赖关系,那么IOC容器有多少种呢?

BeanFactory接口

IOC 容器有多个接口,我们可以组合实现不同的接口,得到不同的IOC容器。其中最基本的接口是BeanFactory,该接口提供了最基本的功能指示,实现了接口中的功能,就能得到一个最基本的IOC容器,接口里的功能有getBean/containsBean等。

实现BeanFactory接口的IOC容器只有最简单的功能,甚至连定位BeanDefinition都要额外调用其他的功能模块来完成,对编程人员来说很不方便,所以Spring还有其它的接口,其中,ApplictionContext接口继承了其它许多的接口,功能丰富了许多。

ApplictionContext接口

该接口扩展了许多额外的接口,多了许多功能,如支持不同的信息源(方便了国际化)、支持事件、访问定位资源等等。

IOC的装载物

IOC容器负责控制反正,也就是在一个对象A需要某个对象B时,IOC容器必须及时地给对象A注入一个对象B,所以IOC容器里装着的是对象B或对象C。
有的童鞋问了,容器是怎么保存对象B的呢?直接new出个对象保存起来?
回答:差不多,容器中保存了一种BeanDefiniton,后者代表着对象B或C,不过值得注意的是,IOC 容器里保存的只是对象的相关信息,此时对象还没有被new出来。

IOC的初始化

我们已经知道了IOC容器的种类(甚至可以自定义新的容器),知道 IOC 容器中保存的是BeanDefinition,那么IOC容器是怎么运作的呢?

IOC容器想要运行起来,首先必须得初始化并启动起来,启动包括了三个过程:BeanDefiniton定位,BeanDefinition 解析,BeanDefiniton 注册。

BeanDefinition定位

一般地,我们在使用 IOC 时,必须要指明哪个对象依赖着哪个额外的对象,通常我们是在 xml 文件里定义这些依赖关系。IOC容器启动时,必须首先寻找到这些xml文件,也就是所谓的 BeanDefinition 定位。

BeanDefinition解析

找到了 XML 文件,下一步自然是解析出里面的信息,不然计算机只是拿到了一个XML文件,不知道对象之间的依赖关系呀。对 JVM 中类的加载过程熟悉的童鞋,对解析这一个过程自然也熟悉,无非就是解析 XML 文件,从中提取重要的信息。解析完后,BeanDefinition从XML文件流转到了内存中,成为一种IOC容器能识别是数据结构。

BeanDefinition注册

IOC容器获晓了BeanDefinition,可它也没法管理BeanDefinition呀。为了管理它们,IOC 容器将它们全都放入一个HashMap中,需要时直接从哈希表里获取。

依赖注入

IOC 启动起来后,知道了程序员们所定义的依赖关系,比如A依赖B,已经算是不错了。但是,知道是知道,IOC此时还没有将B注入A,没体现出控制反转的能力。

A第一次向IOC索要B时,容器开始一种被称为依赖注入的过程,生成一个对象B,并注入到A中去。

有童鞋问了,是怎么生成对象B的咧?直接new ?
回答:非也非也,IOC并不直接new对象,而是调用BeanUtils工具类的方法,以JVM反射的方式生成一个对象,或者调用GGLIB 来实例化Bean对象。

又有童鞋问了,是怎么注入到A中的?A是怎么知道B在哪里的?
回答:关键点是A的BeanDefinition里的属性,我们将该属性的值设为B就可以了,IOC 在运行起来后,A会根据自己的BeanDefinition来找到B。

结语

到这里为止,A不必自己new出一个B,借着IOC的帮助就能得到了一个对象B,IOC容器的功能就真正的体现出来了。