Dubbo学习笔记 三:搭建Spring + Dubbo框架


项目地址:
https://github.com/FateSolo/Dubbo-Test

目录:

在本章将继续使用上一章的Gradle项目,搭建一个基于Spring + Dubbo框架的分布式服务。

1. 项目导入:

使用Intellij IDEA作为项目IDE,以提高工作效率。

1) 点击Import Project,选择Dubbo-Test文件夹,点击OK。

2) 选择第二项Import Project …,选择Gradle,点击Next。

3) Gradle配置默认即可,点击Finish完成项目导入。

4) 进入Project Structure,我选择Project SDK为JDK 8,并确保Project和各个Module的Language level均跟随Project SDK。

2. 修改build.gradle

1) 将dubbo-server的build.gradle内容清空(对dubbo-api的依赖被移动到了dubbo-parent中)。

2) 将dubbo-client的build.gradle内容做如下改动(不再直接依赖dubbo-server):

1
2
3
4
5
6
apply plugin: 'war'

dependencies {
compile 'org.springframework:spring-webmvc:4.3.7.RELEASE'
compile 'com.google.code.gson:gson:2.7'
}

3) 将dubbo-parent的build.gradle内容做如下改动:

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
subprojects {
group 'com.fatesolo'
version '1.0-SNAPSHOT'

apply plugin: 'java'

repositories {
mavenLocal()
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
}
}

configure(subprojects.findAll {it.name == 'dubbo-server' || it.name == 'dubbo-client'}) {
dependencies {
compile project(':dubbo-api')

compile ('com.alibaba:dubbo:2.8.4') {
exclude group: 'org.springframework'
}

compile 'org.springframework:spring-web:4.3.7.RELEASE'

compile ('com.github.sgroschupf:zkclient:0.1') {
exclude group: 'org.apache.zookeeper'
}

compile 'org.apache.zookeeper:zookeeper:3.4.10'
}
}

增加mavenLocal()是因为Maven中央库中不包含Dubbo 2.8.4版,而在Dubbo学习笔记的序章中我已经使用mvn install手动打包Dubbo 2.8.4版并安装到了本地库中。

configure内的依赖配置只针对dubbo-server和dubbo-client,两个module同时依赖dubbo-api、Dubbo框架、zkClient,并且升级了Spring框架和ZooKeeper的版本。

3. 编写dubbo-api:

1) 删去TestApi.java,增加三个子包bean、service、util。

2) 在bean包中创建Book类:

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
package com.fatesolo.dubbo.api.bean;

import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;

@XmlRootElement
public class Book implements Serializable {

private static final long serialVersionUID = 8981773984309718551L;

private int id;

private String name;

private String author;

public Book() {
}

public Book(int id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}

public int getId() {
return id;
}

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

public String getName() {
return name;
}

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

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}

}

3) 在service包中创建BookService接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.fatesolo.dubbo.api.service;

import com.fatesolo.dubbo.api.bean.Book;

import java.util.List;

public interface BookService {

Book getBookById(int id);

List<Book> getBooks();

boolean addBook(Book book);

}

4) 在util包中创建一个StringUtil工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.fatesolo.dubbo.api.util;

public class StringUtil {

public static boolean isBlank(String str) {
return str == null || "".equals(str.trim());
}

public static boolean isNotBlank(String str) {
return !isBlank(str);
}

}

4. 编写dubbo-server:

1) 删去TestServer.java,增加两个子包dao、service。

2) 在dao包中创建BookDao接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.fatesolo.dubbo.server.dao;

import com.fatesolo.dubbo.api.bean.Book;

import java.util.List;

public interface BookDao {

Book findById(int id);

List<Book> findAll();

void save(Book book);

}

3) 在dao中增加子包impl,创建BookDao的一个简单实现BookDaoSimpleImpl:

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
package com.fatesolo.dubbo.server.dao.impl;

import com.fatesolo.dubbo.api.bean.Book;
import com.fatesolo.dubbo.server.dao.BookDao;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Repository
public class BookDaoSimpleImpl implements BookDao {

private final Map<Integer, Book> map = new ConcurrentHashMap<>();

@Override
public Book findById(int id) {
return map.get(id);
}

@Override
public List<Book> findAll() {
return new ArrayList<>(map.values());
}

@Override
public void save(Book book) {
int id = map.size() + 1;

book.setId(id);
map.put(id, book);
}

}

4) 在service中增加子包impl,创建BookService的实现BookServiceImpl:

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
package com.fatesolo.dubbo.server.service.impl;

import com.alibaba.dubbo.container.Main;
import com.fatesolo.dubbo.api.bean.Book;
import com.fatesolo.dubbo.api.service.BookService;
import com.fatesolo.dubbo.api.util.StringUtil;
import com.fatesolo.dubbo.server.dao.BookDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class BookServiceImpl implements BookService {

@Resource(name = "bookDaoSimpleImpl")
private BookDao bookDao;

@Override
public Book getBookById(int id) {
return bookDao.findById(id);
}

@Override
public List<Book> getBooks() {
return bookDao.findAll();
}

@Override
public boolean addBook(Book book) {
if (book == null || book.getId() != 0 || StringUtil.isBlank(book.getName())) {
return false;
}

bookDao.save(book);
return book.getId() != 0;
}

public static void main(String[] args) {
Main.main(args);
}

}

其中main方法用于启动dubbo-server服务,使用了Dubbo提供的Main.main()方式。

5) 在resources/META-INF/spring目录中创建Spring配置文件dubbo-server.xml(因为Dubbo提供的Main.main()启动方式默认加载META-INF/spring/${module-name}.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">


<context:component-scan base-package="com.fatesolo.dubbo.server">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<dubbo:application name="dubbo-server"/>

<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<dubbo:protocol name="dubbo" port="20880"/>

<dubbo:service interface="com.fatesolo.dubbo.api.service.BookService" ref="bookServiceImpl"/>

</beans>

请注意更改ZooKeeper的地址。

5. 编写dubbo-client:

1) 删去index.jsp,增加一个子包controller。

2) 在controller包中创建BookController类:

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
package com.fatesolo.dubbo.client.controller;

import com.fatesolo.dubbo.api.bean.Book;
import com.fatesolo.dubbo.api.service.BookService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping(value = "/book")
public class BookController {

@Resource
private BookService bookService;

@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/xml")
public Book getBookById(@PathVariable("id") int id) {
return bookService.getBookById(id);
}

@RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
public List<Book> getBooks() {
return bookService.getBooks();
}

@RequestMapping(value = "", method = RequestMethod.POST)
public String addBook(Book book) {
return bookService.addBook(book) ? book.toString() : "Failure";
}

}

3) 在resources/config/spring目录中创建Spring配置文件spring-common.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">


<context:component-scan base-package="com.fatesolo.dubbo.client">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<dubbo:application name="dubbo-client"/>

<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<dubbo:reference id="bookService" interface="com.fatesolo.dubbo.api.service.BookService"/>

</beans>

ZooKeeper的地址应与dubbo-server中的一致。

4) 创建SpringMVC配置文件spring-mvc.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">


<context:component-scan base-package="com.fatesolo.dubbo.client" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<mvc:annotation-driven/>

</beans>

5) 创建web.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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

version="3.0">


<!-- Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring/spring-common.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Spring MVC -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring/spring-mvc.xml</param-value>
</init-param>

<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>

<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

6. 项目运行:

1) 进入ZooKeeper的bin文件夹下,键入运行命令:

1
./zkServer.sh start

2) 使用IDE运行dubbo-server中BookServiceImpl的main方法,看到如下提示:

1
Dubbo service server started!

3) 使用Tomcat运行dubbo-client。

4) 使用curl工具添加一条数据:

1
curl -d "name=Book1&author=Fate" http://localhost:8080/dubbo-client/book

得到如下返回:

1
Book{id=0, name='Book1', author='Fate'}

5) 访问http://localhost:8080/dubbo-client/book/1 ,得到如下结果:

1
2
3
4
5
<book>
<author>Fate</author>
<id>1</id>
<name>Book1</name>
</book>

6) 访问http://localhost:8080/dubbo-client/book ,得到如下结果:

1
[{"id":1,"name":"Book1","author":"Fate"}]

7) 访问dubbo-admin管理端,可以看到服务提供者和服务消费者中已经分别显示了dubbo-server和dubbo-client。


作者 [@FateSolo]
2017 年 05月 31日