使用Spring构建一个 RESTful Web Service

Spring官方英文原文:Building a RESTful Web Service


构建目标

构建一个如下接收HTTP GET请求的的服务

http://localhost:8080/greeting

并且返回用于描述greeting内容的JSON数据

1
{"id":1,"content":"Hello, World!"}

你也可以自定义一个可选的name参数来定制返回的问候语

http://localhost:8080/greeting?name=User

带上name参数条件后返回结果会将原来的World替换

1
{"id":1,"content":"Hello, User!"}

环境准备

  • 任意Java IDE
  • JDK 1.7+
  • Maven 2.0+
  • Spring 4+

如何完成

和大多数Spring入门教程相同,你可以一步一步来,或者跳过你已熟悉的基本设置步骤,无论何种方式,最终将得到可正常运行的代码。
如果一步一步来,直接跳到下一节(使用Maven构建工程)
如果跳过可直接clone如下代码

git clone https://github.com/spring-guides/gs-rest-service.git

使用Maven构建工程

在选的的工程目录下,建立如下子目录结构

1
2
3
4
└── src
└── main
└── java
└── hello

pom.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
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
</properties>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>

创建一个资源描述类

有了工程目录和Maven之后,可以开始创建 web service。
首先考虑服务间交互(service interactions)。
这个服务要处理greeting的GET请求,其可包含一个可选的name 参数。这个请求应当返回一个状态为200 OK 的响应,body中包含描述greeting内容的JSON字符串,如下:

1
2
3
4
{
"id": 1,
"content": "Hello, World!"
}

其中id是greeting内容的唯一标识,content是greeting内容的文本描述字段。

为了多greeting响应建模,可以创建一个资源描述类。提供一个包含field,constructors,getter/setter的java对象:

src/main/java/hello/Greeting.java

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

public class Greeting {

private final long id;
private final String content;

public Greeting(long id, String content) {
this.id = id;
this.content = content;
}

public long getId() {
return id;
}

public String getContent() {
return content;
}
}

如上所见,Spring使用了Jackson JSON类库自动生成greeting对象的JSON字符串。

创建资源类控制器

在使用Spring构建RESTful web services的时候,使用资源类控制器来处理http请求。资源控制类可以通过@RestController 注解来定义,下面的GreetingController控制器处理来自/greeting 的GET请求并返回一个Greeting类的实例:

src/main/java/hello/GreetingController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package hello;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();

@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}

这个控制类简单明了,但内部做了大量的处理,我们一步一步来分析下。

@RequestMapping注解确保来自/greeting的HTTP请求能映射到greeting()方法上。

上述例子中并没有指定 GET/PUT/POST 等HTTP操作类型,因为注解@RequestMapping 默认映射所有的HTTP 操作类型。使用@RequestMapping(method=GET) 方式来限定类型只能是GET。

使用@RequestParam可将请求参数name的值绑定到greeting 方法的参数上。该参数不是必须的,如果请求中不包含该参数,将使用默认的World

实现的方法体重创建并返回一个新的Greeting对象,其中并且id实现自增,content根据请求name来构造返回。

一个传统的MVC控制器和RESTful web service控制器关键区别在于对HTTP请求响应内容的创建方式。RESTful web service不是依靠视图技术来包装greeting对象并传递到HTML上,而是直接将Greeting数据内容转换成一个JSON对象直接响应到HTTP response中。

使用Spring4+才有的@RestController注解方式,可直接将返回内容放在HTTP response中,而不是原来的返回到一个HTML页面的方式,它轻易的将请求和响应处理融合在了一起。

Greeting对象必须转换成JSON字符串,有了Spring的消息转换支持,你不需要手动进行转换。转换类MappingJackson2HttpMessageConverter将自动进行这一操作。

运行程序

虽然可以将本服务打成一个WAR包并部署到应用服务器,但更简单的办法是创建一个独立的程序。将所有程序打包成一个可运行的JAR包文件,使用main()方式作为入库执行。使用Spring中嵌入的Tomcat servlet容器替代部原有方式作为运行时环境。

src/main/java/hello/Application.java
1
2
3
4
5
6
7
8
9
10
11
12
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@SpringBootApplication是一个很简洁的注解,和下面注解实现的功能一致。

  • @Configuration用于定义一个加入到Spring上下文的注解。
  • @EnableAutoConfiguration告诉Spring添加基于classpath设置的bean。
    正常情况下需要为一个Spring MVC程序设置@EnableWebMvc 来告诉Spring需要加入classpath,但使用@EnableAutoConfiguration Spring将自动加入。这如同之前配置的DispatcherServlet行为一样。
  • @ComponentScan 让Spring在hello包下寻找组件,配置和服务类下找到HelloController类。
    main方法中使用SpringApplication.run() 方法来启动程序。注意该程序没有任何XML配置文件,也没有web.xml文件。该程序只有Java代码不需要其他任何的设置。

将程序编译成JAR包并执行:

java -jar build/libs/gs-rest-service-0.1.0.jar

测试服务

运行JAR包后,直接访问地址http://localhost:8080/greeting将得到如下反馈:

1
{"id":1,"content":"Hello, World!"}

name作为请求参数传入,将得到如下反馈:

1
{"id":2,"content":"Hello, User!"}

Hadoop 2.6.0 集群部署说明

环境准备

软件环境

OS: Ubuntu 14.04 LTS
Java: jdk1.7.0_79
Hadoop: hadoop-2.6.0

系统环境

192.168.0.19 Master
192.168.0.25 Slave1
192.168.0.26 Slave2

安装系统时最好新建名为hadoop的用户以便区分

修改hostname

选定你的机器中一台作为Master,其余的分别为Slave1Slave2,以此类推

1
sudo nano /etc/hostname

在弹出的编辑器中修改好主机名后按Ctrl+X

修改hosts

在每台机器的hosts中加入系统环境中的IP地址以便访问

1
sudo nano /etc/hosts

在弹出的编辑窗口中加入下面配置

1
2
3
192.168.0.19 Master
192.168.0.25 Slave1
192.168.0.26 Slave2

这里需要把hosts中原来的127.0.1.1 ubuntu这一行去掉
完成后ctrl+x并在提示中输入y回车

到此建议重启下所有机器使得上面的配置生效
重启完成后在Master机器上ping一下Slave1Slave2看是否正常

更新apt

新装的ubuntu最好在每台机器上执行下apt更新,否则可能会导致部分软件无法安装

1
sudo apt-get update

安装SSH,配置为免密码登陆

在每台机器上安装ssh server

1
sudo apt-get install openssh-server

安装后,可以使用ssh命令登陆本机:

1
ssh localhost

会提示选择yes/no,选择yes,随后输入本机密码进入ssh
这样证明安装成功,先退出ssh

1
exit    # 退出ssh

Master节点上执行下面命令

1
2
3
cd ~/.ssh           # 如果没有该目录,先执行一次ssh localhost
ssh-keygen -t rsa # 一直按回车就可以,生成的密钥保存为.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 加入授权

完成后可以使用ssh Master验证一下,这时登陆ssh不再需要密码了,同样记得退出

1
exit    # 退出ssh

Master公匙传输到Slave1节点,过程中会要求输入Slave1hadoop用户的密码

1
scp ~/.ssh/id_rsa.pub hadoop@Slave1:/home/hadoop/

Slave1节点上将ssh公匙保存到相应位置

1
cat ~/id_rsa.pub >> ~/.ssh/authorized_keys

Slave2Slave1,也需要将Master公匙传到Slave2并保存到相应位置
这时Masterssh Slave1Slave2就不再需要密码了

1
ssh Slave1

拷贝安装文件

Master机器上用户根目录下新建一个叫workspace的文件夹

1
sudo mkdir workspace

将下载好的hadoop-2.6.0.tar.gzjdk-7u79-linux-x64.tar.gz拷贝至该文件夹

安装JDK

使用命令行进入workspace文件夹并解压jdk安装包

1
2
cd workspace                                # 进入workspace文件夹
sudo tar -zxvf jdk-7u79-linux-x64.tar.gz # 回车后输入用户密码开始解压

解压后可能权限会有问题,保险起见对jdk目录重新赋权

1
sudo chmod -R 777 jdk1.7.0_79

安装Hadoop

使用命令行进入workspace文件夹并解压hadoop安装包

1
2
cd workspace                                # 进入workspace文件夹
sudo tar -zxvf hadoop-2.6.0.tar.gz # 回车后输入用户密码开始解压

解压后可能权限会有问题,保险起见对jdk目录重新赋权

1
sudo chmod -R 777 hadoop-2.6.0

完成后将Master上的workspace拷贝至Slave1Slave2

1
2
scp -r ~/workspace hadoop@Slave1:/home/hadoop/
scp -r ~/workspace hadoop@Slave2:/home/hadoop/

设置环境变量

在所有机器上进行下面操作

1
sudo nano /etc/profile

在打开的文本文件最后面加入

1
2
export JAVA_HOME=/home/hadoop/workspace/jdk1.7.0_79
export PATH=$PATH:$JAVA_HOME/bin

完成后ctrl+x并在提示中输入y回车,然后重启所有机器

配置Hadoop

首先进入hadoop配置文件目录

1
/home/hadoop/workspace/hadoop-2.6.0/etc/hadoop

修改slaves文件

salves文件中的localhost删除并加入所有Slave的主机名

1
2
Slave1
Slave2

配置JDK路径

在很多时候虽然配置了JAVA_HOME,但启动dfs的时候还是会报找不到JAVA_HOME的错,保险起见这里直接设置JDK安装路径,打开hadoop-env.sh找到

1
export JAVA_HOME=${JAVA_HOME}

将其修改为JAVA_HOME的绝对路径

1
export JAVA_HOME=/home/hadoop/workspace/jdk1.7.0_79

修改core-site.xml

configuration节点中加入下面内容

1
2
3
4
5
6
7
8
9
10
<property>
<name>fs.defaultFS</name>
<value>hdfs://Master:9000</value>
<description>Namenode RPC交互端口</description>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/workspace/hadoop-2.6.0/tmp</value>
<description>hadoop文件系统依赖的基础配置</description>
</property>

修改hdfs-site.xml

configuration节点中加入下面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>Master:50090</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/home/hadoop/workspace/hadoop-2.6.0/tmp/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/home/hadoop/workspace/hadoop-2.6.0/tmp/dfs/data</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>

修改mapred-site.xml

默认情况该文件并不存在,但有一个叫mapred-site.xml.template的文件,可以拷贝一个出来然后将文件名修改成mapred-site.xml,同样在configuration节点中加入下面内容

1
2
3
4
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>

修改yarn-site.xml

configuration节点中加入下面内容

1
2
3
4
5
6
7
8
<property>
<name>yarn.resourcemanager.hostname</name>
<value>Master</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>

到此最进本的配置就已经完成,然后将这些配置复制到其他Slave节点

1
2
3
4
scp -r ~/workspace/hadoop-2.6.0/etc hadoop@Slave1:/home/hadoop/workspace/hadoop-2.6.0
scp -r ~/workspace/hadoop-2.6.0/etc hadoop@Slave2:/home/hadoop/workspace/hadoop-2.6.0
scp -r ~/workspace/hadoop-2.6.0/tmp hadoop@Slave1:/home/hadoop/workspace/hadoop-2.6.0
scp -r ~/workspace/hadoop-2.6.0/tmp hadoop@Slave1:/home/hadoop/workspace/hadoop-2.6.0

启动Hadoop

第一次启动需要先格式化namenode

1
2
cd ~/workspace/hadoop-2.6.0         #进入hadoop安装目录
bin/hdfs namenode -format #格式化namenode

格式化完成后启动hadoop

1
2
sbin/start-dfs.sh
sbin/start-yarn.sh

启动成功后通过jps命令查看进程

1
2
3
4
hadoop@Master:~/workspace/hadoop-2.6.0$ jps
5285 Jps
5171 SecondaryNameNode
4958 NameNode

通过bin/hdfs dfsadmin -report查看DataNode是否都正常启动

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
hadoop@Master:~/workspace/hadoop-2.6.0$ bin/hdfs dfsadmin -report
Configured Capacity: 132898217984 (123.77 GB)
Present Capacity: 116339261440 (108.35 GB)
DFS Remaining: 116339212288 (108.35 GB)
DFS Used: 49152 (48 KB)
DFS Used%: 0.00%
Under replicated blocks: 0
Blocks with corrupt replicas: 0
Missing blocks: 0

-------------------------------------------------
Live datanodes (2):

Name: 192.168.0.25:50010 (Slave1)

Hostname: Slave1
Decommission Status : Normal
Configured Capacity: 66449108992 (61.89 GB)
DFS Used: 24576 (24 KB)
Non DFS Used: 8309112832 (7.74 GB)
DFS Remaining: 58139971584 (54.15 GB)
DFS Used%: 0.00%
DFS Remaining%: 87.50%
Configured Cache Capacity: 0 (0 B)
Cache Used: 0 (0 B)
Cache Remaining: 0 (0 B)
Cache Used%: 100.00%
Cache Remaining%: 0.00%
Xceivers: 1
Last contact: Mon Jul 06 08:30:19 PDT 2015


Name: 192.168.0.26:50010 (Slave2)

Hostname: Slave2
Decommission Status : Normal
Configured Capacity: 66449108992 (61.89 GB)
DFS Used: 24576 (24 KB)
Non DFS Used: 8249843712 (7.68 GB)
DFS Remaining: 58199240704 (54.20 GB)
DFS Used%: 0.00%
DFS Remaining%: 87.58%
Configured Cache Capacity: 0 (0 B)
Cache Used: 0 (0 B)
Cache Remaining: 0 (0 B)
Cache Used%: 100.00%
Cache Remaining%: 0.00%
Xceivers: 1
Last contact: Mon Jul 06 08:30:19 PDT 2015

也可通过WEB查看各种信息

1
2
http://Master:8088/     #Hadoop主页默认端口为8088,任务进度也可以在此查看
http://Master:50070/ #查看Namenode及Datanode信息

最后停止服务

1
2
sbin/stop-dfs.sh
sbin/stop-yarn.sh

到这里基本配置完成,这是最基本最小的集群配置方式,仅仅限于让服务”跑起来”了,要用于实际生产环境中还会涉及到很多很多的配置项,各种调优等参数等问题
To be bontinued …