Dubbo是支持多種協議的,這里我會 演示 dubbo(默認)、hessian、rest 這三種協議。文章代碼貼的比較多,代碼已經上傳到GitHub,見文末。
假如我有這樣一個場景:
OrderService 接口有兩個實現類,其中一個 OrderServiceImpl 獲取的數據較小,我想通過dubbo協議調用;而另外一個 OrderServiceImpl2 獲取的數據較大,我想通過 hessian協議調用,或者我想直接通過Http調用provider的接口。
這要如何配置呢?
下面來研究一下這幾種協議的使用。
TODO:可以使用jmh壓測不同協議傳輸不同的數據量,進行性能對比
項目結構:
父類pom,引入協議需要的依賴:
<modules>
<module>dubbo-samples-xml-api</module>
<module>dubbo-samples-xml-provider</module>
<module>dubbo-samples-xml-consumer</module>
</modules>
<description>Demo project for Spring</description>
<properties>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<dubbo.version>2.7.13</dubbo.version>
<!--<dubbo.version>3.0.2.1</dubbo.version>-->
<spring.version>4.3.16.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<tomcat.version>8.0.53</tomcat.version>
<validation-api.version>1.1.0.Final</validation-api.version>
<hibernate-validator.version>4.2.0.Final</hibernate-validator.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!--Tomcat內嵌包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
1、新建api項目
新建dubbo-samples-xml-api項目
pom.xml:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
定義接口:
OrderRESTService:
public interface OrderRESTService {
Order getOrderInfo(Long id);
}
OrderRESTService2,一個標准的 JAX-RS rest服務:
@Consumes({MediaType.APPLICATION_JSON})
@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
@Path("order2")
public interface OrderRESTService2 {
@GET
@Path("{id : \\d+}")
Order getOrderInfo(@PathParam("id") Long id);
}
OrderService:普通接口
public interface OrderService {
List<Order> getOrderInfo(long orderId);
}
Order:實體類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private static final long serialVersionUID = -3757842094691885448L;
Long orderId;
String orderName;
}
2、創建provider
創建一個dubbo-samples-xml-provider 項目。
pom.xml:
<dependencies>
<dependency>
<groupId>com.dubbo</groupId>
<artifactId>dubbo-samples-xml-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!--dubbo支持rest-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<!--dubbo使用hessian-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-hessian</artifactId>
</dependency>
</dependencies>
創建四個實現類:
其中 OrderServiceImpl、OrderServiceImpl2 均實現 OrderService 接口,具體代碼如下:
OrderServiceImpl:
@Service("orderServiceImpl")
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public List<Order> getOrderInfo(long orderId) {
log.info("OrderServiceImpl方法");
log.info("request from consumer: {}", RpcContext.getContext().getRemoteAddress());
log.info("protocol: {}",RpcContext.getContext().getProtocol());
log.info("response from provider: {}",RpcContext.getContext().getLocalAddress());
List<Order> list = new ArrayList<>();
Order order1 = new Order();
order1.setOrderId(199L);
order1.setOrderName("MacBook Pro 13");
Order order2 = new Order();
order2.setOrderId(200L);
order2.setOrderName("RTX 2060");
list.add(order1);
list.add(order2);
return list;
}
}
OrderServiceImpl2:
/**
* @author 醋酸菌HaC | WebSite📶 : https://rain.baimuxym.cn
* @site
* @date 2021/11/17
* @Description 這是個復雜大對象,用於測試傳輸大包
*/
@Service("orderServiceImpl2")
@Slf4j
public class OrderServiceImpl2 implements OrderService {
@Override
public List<Order> getOrderInfo(long orderId) {
log.info("OrderServiceImpl2方法");
log.info("request from consumer: {}", RpcContext.getContext().getRemoteAddress());
log.info("protocol: {}",RpcContext.getContext().getProtocol());
log.info("response from provider: {}",RpcContext.getContext().getLocalAddress());
List<Order> list = new ArrayList<>();
for (int i = 10; i <= 20; i++) {
Order order = new Order();
order.setOrderId((long) i);
order.setOrderName("MacBook Pro " + i);
list.add(order);
}
return list;
}
}
OrderRESTServiceImpl:
/**
* @author 醋酸菌HaC | WebSite📶 : https://rain.baimuxym.cn
* @site
* @date 2021/11/17
* @Description 這里在實現類加上 JAX-RS 的注解,表示提供REST服務,類似於 springMVC 的 @RestController、@RequestMapping
*/
@Service("orderRESTServiceImpl")
@Path("order")
@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
@Slf4j
public class OrderRESTServiceImpl implements OrderRESTService {
@Override
@GET
@Path("{id : \\d+}")
public Order getOrderInfo(@PathParam("id") Long id /*@Context HttpServletRequest request 這種方法也可以獲取到上下文*/) {
log.info("這是在實現類上聲明的rest");
log.info("request from consumer: {}",RpcContext.getContext().getRemoteAddress());
log.info("protocol: {}",RpcContext.getContext().getProtocol());
log.info("response from provider: {}",RpcContext.getContext().getLocalAddress());
return new Order(id, "MacBook Air" + id);
}
}
OrderRESTServiceImpl 在實現類上使用 JAX-RS 注解,類似於 spring-mvc 的 @RestController、@RequestMapping
OrderRESTServiceImpl2:
/**
* @author 醋酸菌HaC | WebSite📶 : https://rain.baimuxym.cn
* @site
* @date 2021/12/21
* @Description OrderRESTService2 接口使用 JAX-RS 注解
*/
@Service("orderRESTServiceImpl2")
@Slf4j
public class OrderRESTServiceImpl2 implements OrderRESTService2 {
@Override
public Order getOrderInfo(Long id) {
log.info("這是在接口上聲明的rest");
log.info("request from consumer: {}", RpcContext.getContext().getRemoteAddress());
log.info("protocol: {}",RpcContext.getContext().getProtocol());
log.info("response from provider: {}",RpcContext.getContext().getLocalAddress());
return new Order(id, "MacBook Pro");
}
}
創建dubbo-provider.xml,聲明dubbo的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>
<context:component-scan base-package="com.dubbo.impl"/>
<dubbo:application name="dubbo-samples-provider-xml"/>
<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:${zookeeper.port:2181}"/>
<dubbo:protocol name="dubbo" port="20883"/>
<!--hessian需要借助servlet容器,這里使用內部tomcat-->
<dubbo:protocol name="hessian" port="8888" server="tomcat" />
<!--rest使用外部tomcat,端口需要和外部tomcat一致-->
<!--<dubbo:protocol name="rest" port="7777" contextpath="services" server="servlet"/>-->
<!--rest使用內部tomcat-->
<dubbo:protocol name="rest" port="7777" threads="500" contextpath="services" server="tomcat" accepts="500"/>
<!--這個接口僅支持dubbo協議-->
<dubbo:service interface="com.dubbo.api.OrderService"
ref="orderServiceImpl" protocol="dubbo" group="one"/>
<!--這個接口僅支持hessian協議-->
<dubbo:service interface="com.dubbo.api.OrderService"
ref="orderServiceImpl2" protocol="hessian" group="two"/>
<!--這個是rest協議,實現類使用 JAX-RS-->
<dubbo:service interface="com.dubbo.api.OrderRESTService"
ref="orderRESTServiceImpl" protocol="rest"/>
<!--這個是rest協議,接口使用 JAX-RS-->
<dubbo:service interface="com.dubbo.api.OrderRESTService2"
ref="orderRESTServiceImpl2" protocol="rest" />
</beans>
這里聲明了三種協議,協議注意不同的端口,server="tomcat"
表示使用的是內部tomcat。
Provider啟動類:
public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/dubbo-provider.xml"});
context.start();
System.out.println("provider service start ......");
new CountDownLatch(1).await();
}
}
3、創建consumer
創建dubbo-samples-xml-consumer項目。
pom:
<dependencies>
<dependency>
<groupId>com.dubbo</groupId>
<artifactId>dubbo-samples-xml-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!--dubbo支持rest-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<!--hessian-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-hessian</artifactId>
</dependency>
</dependencies>
dubbo-consumer.xml 配置類:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-samples-consumer-xml"/>
<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:${zookeeper.port:2181}"/>
<dubbo:reference id="orderServiceImpl" check="false"
interface="com.dubbo.api.OrderService" protocol="dubbo" group="one"/>
<dubbo:reference id="orderServiceImpl2" check="false"
interface="com.dubbo.api.OrderService" protocol="hessian" group="two"/>
<!--rest,接口使用jax-->
<dubbo:reference id="orderRESTService2" check="false"
interface="com.dubbo.api.OrderRESTService2" protocol="rest"/>
</beans>
Consumer啟動類:
/**
* @author 醋酸菌HaC | WebSite📶 : https://rain.baimuxym.cn
* @date 2021/11/22
* @Description consumer啟動類
*/
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/dubbo-consumer.xml"});
context.start();
System.out.println("consumer start.....");
// dubbo
OrderService orderService1 = context.getBean("orderServiceImpl", OrderService.class);
// hessian
OrderService orderService2 = context.getBean("orderServiceImpl2", OrderService.class);
// rest
OrderRESTService2 orderRESTService2 = context.getBean("orderRESTService2", OrderRESTService2.class);
while (true) {
System.in.read();
RpcContext rpcContext = RpcContext.getContext();
System.out.println("SUCCESS: got order list " + orderService1.getOrderInfo(1L));
System.out.println("SUCCESS: got order list" + orderService2.getOrderInfo(1L));
System.out.println("SUCCESS: got order " + orderRESTService2.getOrderInfo(1L));
// rest
String port = "7777";
getOrder("http://localhost:" + port + "/services/order/2");
}
}
/**
* 走http調用
* @param url
*/
private static void getOrder(String url) {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(url);
Response response = target.request().get();
try {
if (response.getStatus() != 200) {
throw new RuntimeException("Failed with HTTP error code : " + response.getStatus());
}
System.out.println("SUCCESS: got result: " + response.readEntity(Order.class));
} finally {
response.close();
client.close();
}
}
}
4、測試
執行 provider 測試類,打開dubbo-admin 項目(dubbo官方的可視化面板,需要自行搭建),可以看到有四個提供接口:
點擊某個接口,可以看到詳細的信息:
啟動consumer測試類,可以看到日志:
consumer日志:
SUCCESS: got order list [Order(orderId=199, orderName=MacBook Pro 13), Order(orderId=200, orderName=RTX 2060)]
SUCCESS: got order list[Order(orderId=10, orderName=MacBook Pro 10), Order(orderId=11, orderName=MacBook Pro 11), Order(orderId=12, orderName=MacBook Pro 12), Order(orderId=13, orderName=MacBook Pro 13), Order(orderId=14, orderName=MacBook Pro 14), Order(orderId=15, orderName=MacBook Pro 15), Order(orderId=16, orderName=MacBook Pro 16), Order(orderId=17, orderName=MacBook Pro 17), Order(orderId=18, orderName=MacBook Pro 18), Order(orderId=19, orderName=MacBook Pro 19), Order(orderId=20, orderName=MacBook Pro 20)]
SUCCESS: got order Order(orderId=1, orderName=MacBook Pro)
SUCCESS: got result: Order(orderId=2, orderName=MacBook Air2)
provider日志:
# 這里是dubbo協議調用
2021-12-23 15:29.33 [DubboServerHandler-172.16.44.48:20883-thread-23] -- [ INFO] - impl.OrderServiceImpl: OrderServiceImpl方法
2021-12-23 15:29.33 [DubboServerHandler-172.16.44.48:20883-thread-23] -- [ INFO] - impl.OrderServiceImpl: request from consumer: /172.16.44.48:54311
2021-12-23 15:29.33 [DubboServerHandler-172.16.44.48:20883-thread-23] -- [ INFO] - impl.OrderServiceImpl: protocol: null
2021-12-23 15:29.33 [DubboServerHandler-172.16.44.48:20883-thread-23] -- [ INFO] - impl.OrderServiceImpl: response from provider: 172.16.44.48:20883
# 這里是hessian協議調用
2021-12-23 15:29.33 [http-nio-8888-exec-6] -- [ INFO] - impl.OrderServiceImpl2: OrderServiceImpl2方法
2021-12-23 15:29.33 [http-nio-8888-exec-6] -- [ INFO] - impl.OrderServiceImpl2: request from consumer: 172.16.44.48:54314
2021-12-23 15:29.33 [http-nio-8888-exec-6] -- [ INFO] - impl.OrderServiceImpl2: protocol: dubbo
2021-12-23 15:29.33 [http-nio-8888-exec-6] -- [ INFO] - impl.OrderServiceImpl2: response from provider: 172.16.44.48:8888
# 這里是rest協議調用
2021-12-23 15:29.33 [http-nio-7777-exec-1] -- [ INFO] - rest.OrderRESTServiceImpl2: 這是在接口上聲明的rest
2021-12-23 15:29.33 [http-nio-7777-exec-1] -- [ INFO] - rest.OrderRESTServiceImpl2: request from consumer: 172.16.44.48:54315
2021-12-23 15:29.33 [http-nio-7777-exec-1] -- [ INFO] - rest.OrderRESTServiceImpl2: protocol: dubbo
2021-12-23 15:29.33 [http-nio-7777-exec-1] -- [ INFO] - rest.OrderRESTServiceImpl2: response from provider: 172.16.44.48:7777
# 這里是http協議調用
2021-12-23 15:29.33 [http-nio-7777-exec-2] -- [ INFO] - rest.OrderRESTServiceImpl: 這是在實現類上聲明的rest
2021-12-23 15:29.33 [http-nio-7777-exec-2] -- [ INFO] - rest.OrderRESTServiceImpl: request from consumer: 127.0.0.1:54316
2021-12-23 15:29.33 [http-nio-7777-exec-2] -- [ INFO] - rest.OrderRESTServiceImpl: protocol: dubbo
2021-12-23 15:29.33 [http-nio-7777-exec-2] -- [ INFO] - rest.OrderRESTServiceImpl: response from provider: 172.16.44.48:7777
在dubbo-admin可以看到有三個消費者(rest、hessian、dubbo 各一個協議的消費者,第四個是http直連的):
5、其他
5.1、rest
dubbo支持rest(dubbo集成了JAX-RS)在使用上,這里和spring有異曲同工之妙,簡單使用幾個JAX-RS注解就可以使用了,如:
@Path("users")
public class UserServiceImpl implements UserService {
@POST
@Path("register")
@Consumes({MediaType.APPLICATION_JSON})
public void registerUser(User user) {
// save the user...
}
}
-
@Path("users"):指定訪問UserService的URL相對路徑是/users,即http://localhost:8080/users
-
@Path("register"):指定訪問registerUser()方法的URL相對路徑是/register,再結合上一個@Path為UserService指定的路徑,則調用UserService.register()的完整路徑為http://localhost:8080/users/register
-
@POST:指定訪問registerUser()用HTTP POST方法
-
@Consumes({MediaType.APPLICATION_JSON}):指定registerUser()接收JSON格式的數據。REST框架會自動將JSON數據反序列化為User對象
然后在配置中聲明需要暴露的服務接口即可:
<dubbo:service interface="com.dubbo.api.OrderRESTService2"
ref="orderRESTServiceImpl2" protocol="rest" />
5.2、踩坑:
使用 rest 協議的時候,啟動consumer 發現報錯:
You must use at least one, but no more than one http method annotation on: public abstract
原因是 實現類使用 @Path
、@Consumes
、@Produces
注解:
@Service("orderRESTServiceImpl")
@Path("provider")
@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
public class OrderRESTServiceImpl implements OrderRESTService {
consumer.xml 又配置了:
<dubbo:reference id="orderRESTServiceImpl"
interface="com.dubbo.api.OrderRESTService" protocol="rest"/>
使用了JAX-RS 的注解,本來就提供REST服務,所以就會出現兩個REST,提示報錯。
解決方法:
- 使用JAX-RS的注解到接口上,而不是實現類
- 使用http直連的方法訪問即可,rest協議可以直接使用 http 訪問
那 Annotation放在接口類還是實現類?
在一般應用中, 建議放在實現類,便於維護,又不用污染接口,萬一該接口有多個實現類就....
如果接口和實現類都同時添加了annotation,則實現類的annotation配置會生效,接口上的annotation被直接忽略。
當然你都放在實現類了,在consumer的配置文件,就不要使用 protocol="rest"
這種方式調用了,直接使用http連接就可以了。
5.3、rest、hessian
這兩個協議需要依賴 servlet 容器,當然你也可以使用外部的servlet容器,只需要在你的web.xml
配置:
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/dubbo-provider-rest.xml</param-value>
</context-param>
<!--this listener must be defined before the spring listener-->
<listener>
<listener-class>org.apache.dubbo.remoting.http.servlet.BootstrapListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>
在配置文件中聲明:
<!--使用外部tomcat,端口需要和外部tomcat一致-->
<dubbo:protocol name="rest" port="7777" contextpath="services" server="servlet"/>
contextpath
必須要和 web.xml 配置的 url-pattern
一致
端口也必須要和外部servlet容器一致
未來TODO:
參考: