Dubbo 3 于 Spring MVC 下使用注解配置

Posted sp42a

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo 3 于 Spring MVC 下使用注解配置相关的知识,希望对你有一定的参考价值。

入门

Dubbo 是做 RPC 的,基于 Socket + 高性能协议,肯定比 HTTP 调用快多。我当期架构逐渐向分布式靠近,——其实也不是最赶什么微服务的潮流,只是觉得写好的代码,如果不独立,都是依附在某个某个项目中(即“单体”)的话,则很不稳定,有点变化的不好维护。还是独立部署运行比较稳定可用。另外一点好处是,微服务其实可以减少代码重复!你想想看,各个模块哦都独立运行了,变成一个个项目,就是把这模块高度抽象提炼,最大限度复用当期的服务或者组件。

项目是 Spring MVC 5,没有也没必要使用 Spring Boot,但使用注解下配置几乎是一样的。网上很多教程都是说 Dubbo v2.X 于 Spring MVC 下 xml 配置的,本文完全抛弃 xml,采用 Java 注解来配置。

直连方式

同时本文使用最简单的直连方式,无须配置什么 ZooKeeper 注册中心。直连方式是配置 url 指向提供者,将绕过注册中心,支持多个 url。


顺便科普一下,注册中心是什么:

就像发送http请求一样,消费端只要知道服务提供方的 ip 地址和端口号,就可以发起远程 RPC 调用,但如果服务提供方服务器迁移或者增加节点,又或者某个节点故障维修,那会导致消费端不可用,所以需要一个注册中心来统一管理。消费端从注册中心拿到正常提供服务的所有服务提供者,服务提供者向注册中心注册,并保持心跳,当某个提供者不可用时,将提供者剔除,并通知所有消费者,或者消费者定时询问注册中心。Monitor 暂时不讨论。

用法

还记得 Java RMI 吗?定义接口,序列化数据,远程请求,很神奇,就像在本地调用一样。当前 Dubbo 就是 RMI 加强版。跟 RMI 一样,客户端(消费端)只需要写接口,不用写实现,即可执行返回结果,实现的逻辑由服务端提供并返回。

定义一个简单的 GreetingService 接口,里面只有一个简单的方法 sayHello 向调用者问好。这个接口无论消费端还是服务端都需要的,你可以把它放在一个公共的项目/包。

public interface GreetingService 
	String sayHello(String name);

服务端:服务实现

实现 GreetingService 接口,并通过 @DubboService 来标注其为 Dubbo 的一个服务。

import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class AnnotatedGreetingService implements GreetingService 
	public String sayHello(String name) 
		System.out.println("::::::::::::" + name);
		return "hello, " + name;
	

注意 AnnotatedGreetingService 实现了接口 GreetingService

配置服务端

像普通 Bean 那样配置 Dubbo 的相关配置。注意 scanBasePackages 要配置到能扫描到上述的业务类 AnnotatedGreetingService

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableDubbo(scanBasePackages = "com.ajaxjs")
public class ProviderConfiguration 
	@Bean // #2
	public ApplicationConfig applicationConfig() 
		ApplicationConfig app = new ApplicationConfig();
		app.setName("dubbo-annotation-provider");

		return app;
	

	@Bean // #1
	public ProviderConfig providerConfig() 
		ProviderConfig provider = new ProviderConfig();
		provider.setTimeout(1000);

		return provider;
	

	@Bean // #3
	public RegistryConfig registryConfig() 
		RegistryConfig registry = new RegistryConfig();
		registry.setAddress("N/A"); // 直连

		return registry;
	

	@Bean // #4
	public ProtocolConfig protocolConfig() 
		ProtocolConfig protocol = new ProtocolConfig();
		protocol.setName("dubbo");
		protocol.setPort(20880);

		return protocol;
	

至此服务端的配置就 OK 了。运行 Spring 就会启动 Dubbo 服务,运行 Tomcat 也是会。或者你写个单测也行,不需要调用什么,Spring 注入 Dubbo bean 时候就启动了。单测例子如下。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@ContextConfiguration(locations =  "classpath*:applicationContext.xml" )
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestRpcServer 

	@Test
	public void test() throws Exception 
		System.out.println("Dubbo Server Running!");
//		Thread.sleep(100000);
		System.in.read(); // #4
	

成功运行日志显示如下。

配置消费端

与服务端的类似。

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableDubbo(scanBasePackages = "com.ajaxjs.cms")
public class DubboConsumerConfiguration 
	@Bean
	public ApplicationConfig applicationConfig() 
		ApplicationConfig app = new ApplicationConfig();
		app.setName("dubbo-annotation-consumer");

		return app;
	

	@Bean 
	public ConsumerConfig consumerConfig() 
		ConsumerConfig con = new ConsumerConfig();
		con.setTimeout(3000);

		return con;
	

	/**
	 * 注册中心配置
	 * 
	 * @return
	 */
	@Bean
	public RegistryConfig registryConfig() 
		RegistryConfig registry = new RegistryConfig();
		registry.setAddress("N/A"); // 直连

		return registry;
	

消费端服务定义

这个就是你要调用的定义,不需要写实现,就是简单的接口。

import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;

import com.ajaxjs.rpc.GreetingService;

@Component
public class GreetingServiceConsumer 
	@DubboReference(url = "dubbo://127.0.0.1:20880")
    private GreetingService greetingService;

    public String doSayHello(String name) 
        return greetingService.sayHello(name);
    

比较重要的是 @DubboReference 注解,这里指定 Dubbo 服务端地址。开始我就是没配置这里,搞了很久。

循例写单测:

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.ajaxjs.cms.GreetingServiceConsumer;

@ContextConfiguration(locations =  "classpath*:applicationContext.xml" )
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestRpcConsumer 
	@Autowired
	private GreetingServiceConsumer greetingServiceConsumer;

	@Test
	public void test() 
		String hello = greetingServiceConsumer.doSayHello("World!"); // #4
		System.out.println("result: " + hello); // #5
		assertNotNull(hello);
	

使用技巧

  • 单测时候不想加载 Dubbo,加快速度,把注解 @EnableDubbo 注释掉就可以。

使用 @DubboReference 注解有个问题,就是写死了配置,不能可配置化。对此我们可以抛弃注解方式,改用以下 API 的方式:

@Value("$Dubbo.server")
private String dubboServer;

/**
 * 配置要使用的 RPC 服务
 * 
 * @return RPC 服务,使用 get() 返回业务对象
 */
@Bean
public ReferenceConfig<IDataDictService> ReferenceConfig() 
	ReferenceConfig<IDataDictService> ref = new ReferenceConfig<>();
	ref.setUrl(dubboServer);
	ref.setInterface(IDataDictService.class);

	return ref;

使用业务类的话,变成:

@Autowired
ReferenceConfig<IDataDictService> ref;

@Test
public void test2() 
	IDataDictService service = ref.get();
	List<DataDict> dataDict = service.getDataDict(2L);
	assertNotNull(dataDict);
	System.out.println(dataDict.get(0));

点对点直连要注意:

// 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>(); 
// 如果点对点直连,可以用reference.setUrl()指定目标地址,设置url后将绕过注册中心,
// 其中,协议对应provider.setProtocol()的值,端口对应provider.setPort()的值,
// 路径对应service.setPath()的值,如果未设置path,缺省path为接口名
reference.setUrl("dubbo://10.20.130.230:20880/com.xxx.DemoService"); 

参见《Dubbo 的几种启动方式》、《Dubbo 直连代码示例》。

注册中心

注册中心作用,还是这位老铁说得好:

开篇首先想思考一个问题,没有注册中心 Dubbo 还能玩下去吗? 当然可以,只要知道服务提供者地址相关信息,消费者配置之后就可以调用。如果只有几个服务,这么玩当然没问题。但是生产服务动辄成千上百,如果每个服务都需要手写配置信息,想象一下是多么麻烦。

好吧,如果上面的问题都不是事的话,试想一下如果一个服务提供者在运行过程中宕机,消费者怎么办?消费者不知情,所以它还会不断把请求发往服务提供者,然后不断失败。这个时候唯一的办法就是修改服务地址信息,然后重启服务。可以看到如果没有注册中心,分布式环境中服务查找发现将会非常麻烦,一切需要手工配置,无法完成自动化。所以这里就需要一个第三者,协调服务提供者与消费者之间关系,这就是注册中心。

注册中心的选型:

  • Nacos 一开始的默认选择,但发现依然太重 100多M
  • Consul,也是一百多 M 的 Go 程序,一站式的,太重
  • Redis,开始觉得它是轻量级的,但鲜见案例,放弃了
  • etcd,十几 M 的 Go 程序,听说也占资源,但相对较好,就选它了

暂时还是不搞注册中心。参见《几个你不知道的dubbo注册中心细节》dubbo-registry-etcd3

小结

Dubbo 很轻,Jar 包只有 3m 多,铁定用它了~

参见:

以上是关于Dubbo 3 于 Spring MVC 下使用注解配置的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo 3 于 Spring MVC 下使用注解配置

spring+spring mvc+mybatis+mysql+dubbo整合开发任务流程后台管理系统

Spring MVC 3 有视图组件吗?

BAT:Java架构师必备的学习流程图(Spring/TCP/JVM/Spring MVC/JDBC/Spring Clound/Dubbo)

如何使用Dubbo服务和集成Spring

基于dubbo的SOA项目改造