Dubbo学习笔记 八:Redis共享session


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

目录:

经过前几章的集群配置,现如今整个项目流程如下:

1) 访问本机80端口,Nginx反向代理至Tomcat集群中,负载均衡策略为权重比,共有两个节点。

2) 其中一个dubbo-client接收请求,通过注册中心发现服务,注册中心使用ZooKeeper集群实现,共有三个节点。

3) 发现并调用服务dubbo-server,该服务共有两个节点。

目前存在的问题:

1) Tomcat集群没有Session共享策略。

2) dubbo-server没有做数据持久化。

因此,在虚拟机端部署一个Redis,解决session共享问题。同时为了方便,不再部署MySQL,直接使用Redis作为数据存储,项目整体架构图如下:
dubbo-testdubbo-test

1. 部署Redis:

1) 在Redis官网下载稳定版Redis。

2) 解压并编译:

1
2
3
tar -vxzf redis-3.2.9.tar.gz
cd redis-3.2.9
make

3) 打开redis.conf,寻找对应配置项并作如下修改:

1
2
3
bind 192.168.16.152 127.0.0.1
daemonize yes
requirepass 123456

其意思分别是支持远程连接、后台运行和使用密码。

4) 键入如下命令启动Redis:

1
./src/redis-server redis.conf

并使用客户端进入调试:

1
./src/redis-cli -a 123456

2. Redis数据持久化:

1) 打开dubbo-parent下的build.gradle,添加spring-data-redis和jedis的依赖:

1
2
compile 'org.springframework.data:spring-data-redis:1.8.4.RELEASE'
compile 'redis.clients:jedis:2.9.0'

2) 在dubbo-server的META-INF目录下创建db文件夹,并创建配置文件db.properties,编写如下配置:

1
2
3
4
5
6
7
8
redis.host=127.0.0.1
redis.port=6379
redis.pass=123456

redis.maxIdle=5
redis.maxTotal=30
redis.maxWait=1000
redis.testOnBorrow=true

3) 打开dubbo-server.xml,增加如下配置:

1
<context:property-placeholder location="classpath:META-INF/db/db.properties"/>

即加载db.properties的配置。

4) 增加configuration子包,并创建Redis配置类RedisConfiguration:

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfiguration {

@Value("${redis.host}")
private String host;

@Value("${redis.port}")
private int port;

@Value("${redis.pass}")
private String pass;

@Value("${redis.maxIdle}")
private int maxIdle;

@Value("${redis.maxTotal}")
private int maxTotal;

@Value("${redis.maxWait}")
private int maxWait;

@Value("${redis.testOnBorrow}")
private boolean testOnBorrow;

@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setTestOnBorrow(testOnBorrow);

return jedisPoolConfig;
}

@Bean
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();

redisConnectionFactory.setHostName(host);
redisConnectionFactory.setPort(port);
redisConnectionFactory.setPassword(pass);
redisConnectionFactory.setPoolConfig(jedisPoolConfig);

return redisConnectionFactory;
}

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());

return redisTemplate;
}

}

5) 在dao.impl包中创建基于Redis的实现类BookDaoRedisImpl:

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

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

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

@Repository
public class BookDaoRedisImpl implements BookDao {

@Resource
private RedisTemplate<String, Book> redisTemplate;

@Override
public Book findById(int id) {
return redisTemplate.opsForValue().get("book." + id);
}

@Override
public List<Book> findAll() {
return redisTemplate.opsForValue().multiGet(redisTemplate.keys("book.*"));
}

@Override
public void save(Book book) {
int id = redisTemplate.keys("book.*").size() + 1;

book.setId(id);

redisTemplate.opsForValue().set("book." + id, book);
}

}

6) 打开BookServiceImpl,为BookDao注入新实现类:

1
2
@Resource(name = "bookDaoRedisImpl")
private BookDao bookDao;

3. Redis共享Session:

1) 打开dubbo-client下的build.gradle,添加spring-session-data-redis的依赖:

1
compile 'org.springframework.session:spring-session-data-redis:1.3.1.RELEASE'

2) 在dubbo-client的config目录下创建db文件夹和配置文件db.properties,配置同dubbo-server。

3) 打开spring-common.xml,增加如下配置:

1
2
3
<context:property-placeholder location="classpath:config/db/db.properties"/>

<import resource="spring-redis.xml"/>

在dubbo-server中使用Java Config的方式配置Redis,在dubbo-client则使用xml的方式配置。

4) 创建spring-redis.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxTotal" value="${redis.maxTotal}"/>
<property name="maxWaitMillis" value="${redis.maxWait}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>

<bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.pass}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>

<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">

<property name="maxInactiveIntervalInSeconds" value="60"/>
</bean>

</beans>

这里将Session过期时间定为一分钟。

5) 打开web.xml,增加如下配置:

1
2
3
4
5
6
7
8
9
10
<!-- Session -->
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

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

注意该配置的位置要在所有filter之前。

6) 打开BookController,修改getInfo方法:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/info", method = RequestMethod.GET)
public String getInfo(HttpServletRequest request) {
Object sessionInfo = request.getSession().getAttribute("info");

if (sessionInfo == null) {
request.getSession().setAttribute("info", "Session : " + request.getLocalAddr());
return "No Session<br/>" + request.getLocalAddr();
}

return sessionInfo + "<br/>" + request.getLocalAddr();
}

4. 项目测试:

1) 按照上一章的方式分别部署ZooKeeper、dubbo-server、dubbo-client、Nginx。

注意部署本机端时要修改db.properties的host为192.168.16.152。

2) 测试dubbo-server的数据持久化。

添加一条数据:

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

在Redis的客户端中查看:

1
keys *

显示如下:

1
book.1

连续访问http://192.168.16.1/dubbo-client/book ,固定得到如下结果:

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

至此Redis数据持久化测试成功。

3) 测试dubbo-client的Session共享策略。

访问http://192.168.16.1/dubbo-client/book/info ,显示如下:

1
No Session
192.168.16.1

连续访问,结果如下:

1
Session : 192.168.16.1
192.168.16.1


1
Session : 192.168.16.1
192.168.16.152

等待一分钟,关闭本机端的Tomcat,再次访问,显示如下:

1
No Session
192.168.16.152

连续访问,结果如下:

1
Session : 192.168.16.152
192.168.16.152

至此Redis共享Session测试成功。


作者 [@FateSolo]
2017 年 06月 06日

Dubbo学习笔记 七:Nginx + Tomcat集群


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

目录:

前两章分别对注册中心ZooKeeper和服务提供者dubbo-server做了集群测试,在本章将用Nginx + Tomcat对服务消费者dubbo-client进行集群。

1. 集群配置总览:

本机ip:192.168.16.1,部署Nginx反向代理服务器、服务消费者dubbo-client、服务提供者dubbo-server。

虚拟机ip:192.168.16.152,部署三个ZooKeeper节点、服务消费者dubbo-client、服务提供者dubbo-server。

2. dubbo-client打包:

1) 在BookController类中增加一个方法:

1
2
3
4
@RequestMapping(value = "/info", method = RequestMethod.GET)
public String getInfo(HttpServletRequest request) {
return request.getLocalAddr();
}

2) 在dubbo-client目录下,键入如下命令:

1
gradle build

在dubbo-client/build/libs目录下已经生成了war包。

3. 项目部署:

1) 确认dubbo-server和dubbo-client配置文件里注册中心地址的正确性。

虚拟机端:

1
<dubbo:registry address="zookeeper://127.0.0.1:2181?backup=127.0.0.1:2182,127.0.0.1:2183"/>

本机端:

1
<dubbo:registry address="zookeeper://192.168.16.152:2181?backup=192.168.16.152:2182,192.168.16.152:2183"/>

2) 部署ZooKeeper集群:

1
2
3
./zookeeper-1/bin/zkServer.sh start
./zookeeper-2/bin/zkServer.sh start
./zookeeper-3/bin/zkServer.sh start

3) 部署本机和虚拟机的dubbo-server:

1
java -jar dubbo-server-1.0-SNAPSHOT.jar

4) 部署本机和虚拟机的dubbo-client,将项目放在Tomcat的webapps下,随后启动两个Tomcat即可。

5) 查看dubbo-admin,此时dubbo-server和dubbo-client都已经有了来自本机和虚拟机的服务提供者和服务消费者。

4. 部署Nginx:

1) 打开nginx目录下的conf/nginx.conf,在http中增加如下配置:

1
2
3
4
upstream dubbo-client {
server 192.168.16.1:8080 weight=2;
server 192.168.16.152:8080 weight=1;
}

更改server中的根location:

1
2
3
4
5
location / {
#root html;
#index index.html index.htm;
proxy_pass http://dubbo-client;
}

2) 启动Nginx即可,此时访问本机80端口将被反向代理至两个Tomcat上,负载均衡策略为本机:虚拟机=2:1。

5. 集群测试:

1) 连续访问http://192.168.16.1/dubbo-client/book/info ,将得到如下两个结果:

1
192.168.16.1
192.168.16.152

并且出现的比例固定为2:1。

2) 关掉其中一个Tomcat,再次访问,需要很长时间才请求成功。

问题出在此时其中一个Tomcat宕机后,Nginx仍然将请求转发至该Tomcat,因此持续等待至Nginx默认超时时间后才重新转发至另一台Tomcat。

因此打开nginx.conf,在server的根location中增加如下配置:

1
proxy_connect_timeout 3;

重启Nginx,再次访问,问题解决。


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

Dubbo学习笔记 六:ZooKeeper集群


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

目录:

在本章将测试注册中心的集群功能,即ZooKeeper的集群。

1. 文件配置

因为资源有限,这里只在虚拟机内配置三个ZooKeeper。

1) 将ZooKeeper复制为三份,分别命名为zookeeper-1、zookeeper-2、zookeeper-3。

2) 在三个文件夹内各创建一个data文件夹,并在data文件夹内创建myid文件,myid只包含一个数字,分别是1、2、3。

3) 分别修改三个ZooKeeper的conf/zoo.cfg:

1
2
3
4
5
6
7
8
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/fate/zookeeper-1/data
clientPort=2181
server.1=localhost:2887:3887
server.2=localhost:2888:3888
server.3=localhost:2889:3889

其余两个大致相同,不同点如下:

1
2
3
4
5
dataDir=/home/fate/zookeeper-2/data
clientPort=2182

dataDir=/home/fate/zookeeper-3/data
clientPort=2183

2. 运行测试

1) 键入如下命令启动三个ZooKeeper:

1
2
3
./zookeeper-1/bin/zkServer.sh start
./zookeeper-2/bin/zkServer.sh start
./zookeeper-3/bin/zkServer.sh start

2) 查看jps:

1
2
3
4
5
6
7
6193 Jps
6115 QuorumPeerMain
6085 QuorumPeerMain
3687 RemoteMavenServer
3579 Main
3789 Launcher
6159 QuorumPeerMain

可以看到已经启动了三个ZooKeeper。

3) 打开dubbo-admin的dubbo.properties,修改如下配置:

1
dubbo.registry.address=zookeeper://127.0.0.1:2181?backup=127.0.0.1:2182,127.0.0.1:2183

除此之外,dubbo-server和dubbo-client的dubbo:registry标签也应该修改如下:

1
<dubbo:registry address="zookeeper://127.0.0.1:2181?backup=127.0.0.1:2182,127.0.0.1:2183"/>

4) 启动dubbo-admin,查看系统管理 -> 系统状态,注册中心此时处于正常连接状态。

5) 停止zookeeper-1:

1
./zookeeper-1/bin/zkServer.sh stop

此时注册中心仍然处于正常连接状态,集群成功。

6) 停止zookeeper-2:

1
./zookeeper-2/bin/zkServer.sh stop

此时注册中心报错,处于未连接状态,因为超过一半机器已宕机。

7) 重新启动zookeeper-1和zookeeper-2:

1
2
./zookeeper-1/bin/zkServer.sh start
./zookeeper-2/bin/zkServer.sh start

注册中心状态已经恢复正常。


作者 [@FateSolo]
2017 年 06月 04日

Dubbo学习笔记 五:Dubbo服务集群


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

目录:

在本章将测试Dubbo的服务集群功能。

1. 服务打包

将dubbo-server打成jar包,然后用java -jar命令运行。

1) 打开dubbo-server下的build.gradle,增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
task copyLib(type : Copy) {
from { configurations.compile }
into('lib')
}

jar {
manifest {
attributes 'Main-Class': 'com.fatesolo.dubbo.server.service.impl.BookServiceImpl'
attributes 'Class-Path': new File('lib').list().findAll {
it.endsWith('.jar')
}.collect { "lib/$it" }.join(' ')
}
}

copyLib负责将dubbo-server的所有依赖拷贝至lib文件夹下,attributes ‘Main-Class’指定jar包运行入口,attributes ‘Class-Path’指定jar包的所有依赖,即lib文件夹下的所有jar包。

2) 打开dubbo-server.xml,修改服务配置如下:

1
2
<dubbo:service interface="com.fatesolo.dubbo.api.service.BookService"
ref="bookServiceImpl" protocol="dubbo,rest"/>

即让该服务同时使用dubbo和rest协议。

3) 进入到dubbo-server目录下,键入如下命令:

1
gradle copyLib

在dubbo-server文件夹下已经生成了lib文件夹。

4) 继续键入如下命令:

1
gradle build

在dubbo-server/build/libs文件夹下已经生成了dubbo-server-1.0-SNAPSHOT.jar。

2. 服务部署

因为资源有限,这里只在虚拟机和本机同时运行两个服务。

1) 将lib文件夹和jar包放至同一个目录,键入如下命令:

1
java -jar dubbo-server-1.0-SNAPSHOT.jar

即可在虚拟机运行第一个服务。

2) 将lib文件夹和jar包拷贝至本机,修改jar包内的dubbo-server.xml,更改注册中心地址:

1
<dubbo:registry address="zookeeper://192.168.16.151:2181"/>

也就是虚拟机的ip地址,随后可在本机运行此服务。

3. 集群测试

1) 在dubbo-admin中,可以看到com.fatesolo.dubbo.api.service.BookService服务此时拥有四个提供者:
dubbo-server-providerdubbo-server-provider

分别是本机和虚拟机的7110端口和20880端口。

2) 运行dubbo-client。

3) 使用dubbo-client添加一条数据:

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

4) 连续访问http://localhost:8080/dubbo-client/book ,将随机得到如下结果:

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

或者

1
[]

会出现这种情况,是因为Dubbo的负载均衡策略缺省为random随机调用。因此添加数据被随机添加到了其中一个服务中,访问同样是随机访问其中一个服务。


作者 [@FateSolo]
2017 年 06月 03日

Dubbo学习笔记 四:支持RESTful Remoting


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

目录:

本章将对dubbo-server进行优化,包括为dubbo协议替换高效二进制序列化实现Kryo和增加RESTful协议(均是当当网维护的Dubbox项目所增加的功能)。

1. 替换序列化实现:

dubbo协议采用单一长连接和NIO异步通讯,是Dubbo框架所推荐的协议,其缺省采用Hessian序列化方式。

Kryo是Dubbox中引入的两个高效序列化实现中的一个(另一个是FST),其性能显著优于Hessian。

1) 打开dubbo-parent下的build.gradle,添加Kryo的依赖:

1
2
compile 'com.esotericsoftware.kryo:kryo:2.24.0'
compile 'de.javakaffee:kryo-serializers:0.41'

2) 在dubbo-api中添加子包serialization,并创建SerializationOptimizerImpl类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.fatesolo.dubbo.api.serialization;

import com.alibaba.dubbo.common.serialize.support.SerializationOptimizer;
import com.fatesolo.dubbo.api.bean.Book;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class SerializationOptimizerImpl implements SerializationOptimizer {

@Override
public Collection<Class> getSerializableClasses() {
List<Class> classes = new LinkedList<>();

classes.add(Book.class);

return classes;
}

}

dubbo-api此时没有Dubbo的依赖,因此无法找到SerializationOptimizer接口

3) 再次打开dubbo-parent下的build.gradle,将对Dubbo 2.8.4的依赖由configure移至subprojects下,令dubbo-api拥有对Dubbo框架的依赖。

4) 打开dubbo-server.xml,更改dubbo:protocol标签如下:

1
2
<dubbo:protocol name="dubbo" port="20880" serialization="kryo"
optimizer="com.fatesolo.dubbo.api.serialization.SerializationOptimizerImpl"/>

至此序列化实现已经切换至Kryo,此后所有需要序列化的类都应该在SerializationOptimizerImpl中添加。

2. 增加RESTful协议:

目前dubbo-server采用了缺省的Dubbo协议,即TCP协议(Netty) + 二进制序列化(Kryo)。

在Dubbox中增加了对RESTful的支持,即Http协议 + Json,由于此协议能同时支持REST服务和RPC调用,故称为RESTful Remoting。

1) 打开dubbo-parent下的build.gradle,增加subprojects下的依赖:

1
compile 'org.jboss.resteasy:resteasy-client:3.1.3.Final'

增加configure下的依赖:

1
compile 'org.jboss.resteasy:resteasy-jackson-provider:3.1.3.Final'

2) 打开dubbo-server下的build.gradle,增加如下依赖:

1
2
3
4
5
6
dependencies {
compile 'javax.validation:validation-api:1.0.0.GA'

compile 'org.apache.tomcat.embed:tomcat-embed-core:8.0.11'
compile 'org.apache.tomcat.embed:tomcat-embed-logging-juli:8.0.11'
}

3) 打开dubbo-api下的BookService,更改如下:

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

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

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Path("/book")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface BookService {

@GET
@Path("/{id}")
Book getBookById(@PathParam("id") int id);

@GET
@Path("/")
List<Book> getBooks();

@POST
@Path("/")
Boolean addBook(Book book);

}

这里将addBook方法的返回类型由boolean更改为了Boolean,因此同时也要修改dubbo-server中的BookServiceImpl:

1
2
@Override
public Boolean addBook(Book book) {

4) 打开dubbo-server.xml,增加一个协议:

1
<dubbo:protocol name="rest" port="7110" server="tomcat" contextpath="dubbo-server"/>

更改BookService使用的协议:

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

3. 测试RESTful Remoting:

1) 分别运行dubbo-server和dubbo-client。

2) 使用dubbo-client添加一条数据:

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

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

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

4) 使用dubbo-server添加一条数据:

1
curl -H "Content-Type:application/json" -d "{\"name\":\"Book2\",\"author\":\"Fate\"}" http://localhost:7110/dubbo-server/book

5) 访问http://localhost:7110/dubbo-server/book ,得到如下结果:

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

至此,dubbo-server的RESTful协议已经同时支持REST服务和RPC调用。


作者 [@FateSolo]
2017 年 06月 02日

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日

Dubbo学习笔记 二:使用Gradle构建多module项目


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

目录:

在本章将用Gradle重新构建多module项目Dubbo-Test,以展示和Maven的区别。

1. 项目创建:

1) 创建名为Dubbo-Test的文件夹并进入:

1
mkdir Dubbo-Test && cd Dubbo-Test

2) 创建Gradle Wrapper,但不是必需的:

1
gradle wrapper

2. 创建parent module:

1) 创建build.gradle:

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

group 'com.fatesolo'
version '1.0-SNAPSHOT'

apply plugin: 'java'

repositories {
mavenCentral()
}

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

}

2) 创建settings.gradle:

1
rootProject.name = 'dubbo-parent'

3. 创建其余module:

1) 创建dubbo-api:

1
2
3
mkdir dubbo-api && cd dubbo-api
mkdir -p src/{main,test}/{java,resources}
touch build.gradle

2) 创建dubbo-server:

1
2
3
mkdir dubbo-server && cd dubbo-server
mkdir -p src/{main,test}/{java,resources}
touch build.gradle

并修改build.gradle:

1
2
3
dependencies {
compile project(':dubbo-api')
}

3) 创建dubbo-client:

1
2
3
mkdir dubbo-client && cd dubbo-client
mkdir -p src/{main,test}/{java,resources} src/main/webapp
touch build.gradle

并修改build.gradle:

1
2
3
4
5
apply plugin: 'war'

dependencies {
compile project(':dubbo-server')
}

4) 修改settings.gradle,增加如下代码:

1
include 'dubbo-api', 'dubbo-server', 'dubbo-client'

4. 测试多module项目

1) 在dubbo-api中创建子包com.fatesolo.dubbo.api,创建实例类TestApi:

1
2
3
4
5
6
7
8
9
package com.fatesolo.dubbo.api;

public class TestApi {

public String testAPi() {
return "api";
}

}

2) 在dubbo-server中创建子包com.fatesolo.dubbo.server,创建实例类TestServer:

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

import com.fatesolo.dubbo.api.TestApi;

public class TestServer {

public String testServer() {
TestApi api = new TestApi();
return "server " + api.testAPi();
}

}

3) 在dubbo-client中创建index.jsp:

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.fatesolo.dubbo.server.TestServer" %>
<!DOCTYPE html>
<html>
<body>
<% TestServer server = new TestServer(); %>
<h2><%=server.testServer()%></h2>
</body>
</html>

4) 进入到Dubbo-Test文件夹,键入命令:

1
gradle build

查看各个module,可以看到都已经生成了各自的包,其中dubbo-client-1.0-SNAPSHOT.war包的lib中已经包含了dubbo-api和dubbo-server的jar包。

5) 将dubbo-client-1.0-SNAPSHOT.war部署到Tomcat,运行并访问http://localhost:8080/dubbo-client-1.0-SNAPSHOT ,得到如下结果:

1
server api


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

Dubbo学习笔记 一:使用Maven构建多module项目


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

目录:

对于一个使用了Dubbo的分布式应用,其可以拆分成三个部分:

dubbo-api,服务公共api,包含服务接口、实体等server、client公用的类。
dubbo-server,服务提供者,实现api的服务接口,提供具体服务。
dubbo-client,服务消费者,使用api提供的接口,远程调用server端服务。

其中dubbo-api包是server和client共有的,server实现并暴露服务,client则通过注册中心发现并远程调用服务。

在本章将用Maven构建一个多module项目Dubbo-Test,其包含了以上所述的三个module。

1. 项目创建:

1) 创建名为Dubbo-Test的文件夹并进入即可:

1
mkdir Dubbo-Test && cd Dubbo-Test

2. 创建parent module:

多module项目应该有一个统一的父module,以方便进行管理。

1) 创建pom.xml:

1
2
3
4
5
6
7
8
9
<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>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

3. 创建其余module:

1) 创建dubbo-api:

1
mvn archetype:generate -DgroupId=com.fatesolo.dubbo -DartifactId=dubbo-api -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

2) 创建dubbo-server:

1
mvn archetype:generate -DgroupId=com.fatesolo.dubbo -DartifactId=dubbo-server -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

3) 创建dubbo-client:

1
mvn archetype:generate -DgroupId=com.fatesolo.dubbo -DartifactId=dubbo-client -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

4. 测试多module项目

1) 修改各pom文件,令dubbo-server依赖dubbo-api,dubbo-client依赖dubbo-server。

dubbo-parent:

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
<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>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>dubbo-api</module>
<module>dubbo-server</module>
<module>dubbo-client</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-api</artifactId>
<version>${version}</version>
</dependency>

<dependency>
<groupId>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-server</artifactId>
<version>${version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

dubbo-api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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">

<parent>
<artifactId>dubbo-parent</artifactId>
<groupId>com.fatesolo.dubbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>dubbo-api</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

dubbo-server:

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
<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">

<parent>
<artifactId>dubbo-parent</artifactId>
<groupId>com.fatesolo.dubbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>dubbo-server</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>

<dependency>
<groupId>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-api</artifactId>
</dependency>
</dependencies>
</project>

dubbo-client:

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
<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">

<parent>
<artifactId>dubbo-parent</artifactId>
<groupId>com.fatesolo.dubbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>dubbo-client</artifactId>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>

<dependency>
<groupId>com.fatesolo.dubbo</groupId>
<artifactId>dubbo-server</artifactId>
</dependency>
</dependencies>
</project>

2) 在dubbo-api中创建子包com.fatesolo.dubbo.api,创建实例类TestApi:

1
2
3
4
5
6
7
8
9
package com.fatesolo.dubbo.api;

public class TestApi {

public String testAPi() {
return "api";
}

}

3) 在dubbo-server中创建子包com.fatesolo.dubbo.server,创建实例类TestServer:

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

import com.fatesolo.dubbo.api.TestApi;

public class TestServer {

public String testServer() {
TestApi api = new TestApi();
return "server " + api.testAPi();
}

}

4) 在dubbo-client中修改index.jsp:

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.fatesolo.dubbo.server.TestServer" %>
<!DOCTYPE html>
<html>
<body>
<% TestServer server = new TestServer(); %>
<h2><%=server.testServer()%></h2>
</body>
</html>

5) 进入到Dubbo-Test文件夹,键入命令:

1
mvn package

查看各个module,可以看到都已经生成了各自的包,其中dubbo-client-1.0-SNAPSHOT.war包的lib中已经包含了dubbo-api和dubbo-server的jar包。

6) 将dubbo-client-1.0-SNAPSHOT.war部署到Tomcat,运行并访问http://localhost:8080/dubbo-client-1.0-SNAPSHOT ,得到如下结果:

1
server api


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

Dubbo学习笔记 序


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

目录:

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,但目前已经停止更新。

Dubbox,是当当网基于Dubbo开发的框架,支持REST风格远程调用、基于Kryo和FST的Java高效序列化实现等功能。

1. 架构简介:

简单的讲,Dubbo架构可以将一个复杂项目的核心业务方便的抽取出来,作为独立的服务供其他人调用,主要分为三个角色:

Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。

服务提供者将自己暴露的服务在注册中心注册,服务消费者在注册中心发现服务并调用,具体介绍参阅Dubbo用户指南

2. 安装ZooKeeper:

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务。它是Dubbo所推荐的注册中心,因此首先安装ZooKeeper。

1) 在http://www.apache.org/dist/zookeeper/current/ 下载最新版本的ZooKeeper压缩包并解压。

2) 解压后进入conf文件夹,复制zoo_sample.cfg,改名为zoo.cfg,ZooKeeper运行时默认加载此配置文件。

3) 进入bin文件夹,键入如下命令启动ZooKeeper:

1
./zkServer.sh start

3. 部署dubbo-admin:

dubbo-admin是Dubbo官方提供的服务监控管理端,它是一个独立的运行于Tomcat等web容器的项目,不与服务提供者、服务消费者耦合,通过监控注册中心的方式实现了对服务的管理。

1) 下载源码:

1
git clone git@github.com:dangdangdotcom/dubbox.git

也可以采用别的方式下载源码,甚至可以直接下载网络上已经打成war包的项目,但建议下载最新源码并手动打成war包。

2) 进入dubbox/dubbo-admin/src/main/webapp/WEB-INF文件夹,打开dubbo.properties配置文件,确保注册中心地址指向你的ZooKeeper,例如:

1
dubbo.registry.address=zookeeper://127.0.0.1:2181

3) 退回到dubbox文件夹,键入命令:

1
mvn install -Dmaven.test.skip

请先确保安装好了Maven。

4) 将dubbox/dubbo-admin/target下的war包拷贝至tomcat的webapps下,改名为dubbo-admin.war,启动tomcat即完成了部署。

5) 访问http://localhost:8080/dubbo-admin ,用户名与密码都键入root,便可进入该管理系统,如图:
dubbo-admindubbo-admin


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

JAX-RS初探 三:其他


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

目录:

本章继续对JAX-RS予以补充。

1. 整合Logback日志

关于日志你有多种选择,这里我选择使用Logback来代替此前一直使用的Log4j,关于它们二者的对比可以参阅网上的其他资料。

1) 打开web.xml,添加如下配置:

1
2
3
4
5
6
7
8
9
<!-- Logback -->
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>classpath:config/log/logback.xml</param-value>
</context-param>

<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>

2) 添加Logback依赖:

1
/**
 * Logback
 */
compile 'ch.qos.logback:logback-classic:1.2.1'
compile 'org.logback-extensions:logback-ext-spring:0.1.4'
compile 'org.slf4j:jcl-over-slf4j:1.7.22'

三个依赖依次是Logback(包括Logback本身的实现与slf4j接口)、Logback对Spring的支持、Apache Commons Logging到slf4j的桥接器(因为Spring大量使用了Apache Commons Logging接口,所以需要重定向到slf4j接口)

3) 在resources/config/log目录下创建Logback日志的配置文件logback.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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<description>Logback 日志配置</description>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${JSH-Test.root}/WEB-INF/logs/%d{yyyy-MM}/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>500</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern>
</encoder>
</appender>

<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>

</configuration>

2. 使Jersey支持返回json

在前两章的示例中,我们使用@Consumes和@Produces限制了Content-Type和Accept的类型仅支持Application/xml格式,然后在实体类Book上增加了@XmlRootElement注解,此后Jersey框架会使用JAXB为我们进行xml数据与实体的绑定。

RESTful服务通常情况下会使用更为轻便简洁的json格式来传输数据,而Jersey没有默认支持json的转换,因此我们需要自行添加支持(这里选择了Gson做为实现),编写Provider类来完成手动的转换。

1) 添加Gson依赖:

1
/**
 * Druid
 */
compile 'com.alibaba:druid:1.0.28'

2) 建立provider子包,并创建GsonProvider类:

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
package com.fatesolo.jsh.provider;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

@Provider
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class GsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {

private final Gson gson;

public GsonProvider() {
gson = new GsonBuilder().disableHtmlEscaping().create();
}

@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType)
{

return true;
}

@Override
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException
{

try (InputStreamReader reader = new InputStreamReader(entityStream, "UTF-8")) {
return gson.fromJson(reader, type.equals(genericType) ? type : genericType);
}
}

@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType)
{

return true;
}

@Override
public long getSize(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType)
{

return -1;
}

@Override
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException
{

try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, "UTF-8")) {
gson.toJson(object, type.equals(genericType) ? type : genericType, writer);
}
}

}

3) 与资源类一样,若想被Jersey使用,需要在Application类中注册:

1
register(GsonProvider.class);

4) 修改BookResource中的@Consumes和@Produces,增加对json格式的支持:

1
2
3
4
5
@Controller
@Path("/book")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class BookResource {

3. 项目运行:

1) 使用Tomcat 8运行项目,使用curl工具添加一条数据:

1
curl -H "Content-Type:application/json" -H "Accept:application/json" -d "{\"name\":\"Book2\",\"author\":\"Solo\"}" http://localhost:8080/rest/book

得到如下返回:

1
{"code":"SUCCESS"}

2) 使用curl工具获取一条数据:

1
curl -H "Accept:application/json" http://localhost:8080/rest/book/2

得到如下结果:

1
{"id":2,"name":"Book2","author":"Solo"}

3) 使用curl工具获取所有数据:

1
curl -H "Accept:application/json" http://localhost:8080/rest/book

得到如下结果:

1
[{"id":1,"name":"Book","author":"Fate"},{"id":2,"name":"Book2","author":"Solo"}]

至此,Jersey已经支持了对json格式数据的转换。

4. 关于SpringMVC的数据转换

可以看到,当一个方法直接返回实体或包装它的数据结构时,JAX-RS会依据Accept的类型自动完成转换来返回相对应的类型,同理也会依据Content-Type的类型来自动完成请求数据到实体对象的绑定。

在此前使用SpringMVC中,我基本都是直接返回String类型,然后使用Gson的toJson方法手动完成转换,事实上SpringMVC的数据转换同样非常方便:

1) 当我们在spring-mvc.xml增加了如下配置后,SpringMVC会自动注册相应的数据转换器,其中就包括了xml和json的转换器。

1
<mvc:annotation-driven/>

2) 但与Jersey类似,SpringMVC直接使用JAXB完成对xml的转换,而json则需要手动添加实现,这里仍以Gson示例:

1
compile 'com.google.code.gson:gson:2.7'

3) 在实体类上增加注解:

1
@XmlRootElement

4) 在Controller类中,没有专门的@Consumes和@Produces注解,而是在@RequestMapping中完成配置:

1
@RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = "application/xml")

1
@RequestMapping(path = "/name/{name}", method = RequestMethod.GET, produces = "application/json")

produces代表了可以返回的类型,也可以使用{}返回多种类型。

1
2
@RequestMapping(path = "", method = RequestMethod.POST, consumes = "application/json")
public Book addBook(@RequestBody Book book) {

consumes代表支持的请求类型,与produces的语法相同,另外需要在实体前增加@RequestBody注解,以支持json、xml等数据到实体的转换。

至此,当返回类型是实体类或是包装其的数据结构时,SpringMVC会依据Content-Type和Accept自动完成数据的转换,比Jersey方便的一点是,SpringMVC直接支持操作Gson,而不再需要我们手动编写GsonProvider。

5. 关于@Repository和@RepositoryDefinition

在DAO系列的Spring-Data-JPA一章中的BookDaoSpringDataJpaImpl接口中,同时使用了@Repository和@RepositoryDefinition注解,而在本系列的BookRepository接口中则只使用了@RepositoryDefinition注解。

事实上使用@RepositoryDefinition便已完成了bean的注册,无需再使用@Repository注解。

Mybatis框架里声明的接口同样不需要再使用@Repository注解,但在service层注入时,Intellij IDEA会报错(事实上可以完成注入)。

为了看起来统一,DAO系列的六种实现都添加了@Repository注解,而Mybatis和Spring-Data-JPA两种方法增加与否并没有什么太大区别(区别就是上面提到的IDE报错)。


作者 [@FateSolo]
2017 年 03月 22日