【原創 轉載請注明出處】
本文是學習了dubbo之后自己手動寫的,比較通俗,很多都是自己學習之后的理解,寫的過程中沒有參考任何文章。
另外dubbo也有官方文檔,但是比較官方,也可以多看看dubbo的官方中文文檔。
代碼示例連接:dubbodemo
一、dubbo的相關概念
dubbo是阿里的一個分布式服務開源框架,它的設計理念就是把一個大而全的項目模塊化,每個模塊都是一個獨立的項目。
為什么要把大項目拆分成多個小項目呢?
因為隨着項目越做越大,代碼、功能越來越多,導致代碼的復用性就會降低,項目變得龐大臃腫,維護起來比較麻煩,改一個功能所有的代碼都需要重新打包發布,還可能會影響其他模塊;對於開發者來說所有的開發者都在同一個項目里開發代碼,雖然有版本管理軟件(SVN,Git等),但即使這樣也會使開發者開發時遇到許多問題。
所以就誕生了分布式,分布式的原則就是將項目拆分若干個小項目,實現模塊化,每個項目只關注自己的功能(假如按功能划分模塊),需要其他模塊的數據時就去調用它,分工更加明確。dubbo就是一個基於spring的分布式框架,可以和spring無縫整合。
那么模塊之間該如何調用呢?
我們稱服務提供者為服務生產者,服務調用者為服務消費者,他們兩個是如何通信呢?消費者如何找到生產者呢?顯然是通過網絡,通過網絡就必須要遵循一定的協議,約定,也就時需要有一個第三者或叫中間人、中介,稱為注冊中心,他來定義通訊的協議、規則。服務生產者和服務消費者二者必須都來遵循這個規則。
服務生產者在啟動程序時把服務發布到注冊中心,告訴注冊中心他叫啥,他提供的服務的類型,他的IP和端口就行了,而服務消費者在啟動程序時也去連接注冊中心,告訴注冊中心他叫啥,告訴注冊中心他想要什么類型的服務。對於服務提供方和服務消費方來說,他們還有可能兼具這兩種角色,即既需要提供服務,有需要消費服務。
具體怎么實現調用呢?在項目里的表現如何?
模塊之間要想調用,提供服務方需要創建服務接口,打成jar包,發布這個jar包,服務提供者面向接口編程,服務消費者調用服務時也用這個服務接口的jar包,創建服務接口的實例。
為什么要創建服務接口並發布成jar包?因為服務的提供方和服務的調用方都要用到這個接口。服務消費者要使用這個服務,需要獲得服務的實例(只關心服務的類型)來調用服務提供的方法,而服務的提供者也只需要實現接口就可以了。
有了協議為什么還用dubbo?
那我們直接遵循協議,去和注冊中心打交道不就行了嗎,為什么還產生了dubbo呢?
畢竟有需求就有市場,歸根結底還是因為我們懶。因為所有的人都要遵循這個協議,怎么連接網絡,怎么傳參數,格式如何等這些工作都是重復性的,是所有項目共性的,每次都寫不就很麻煩嗎,所以dubbo來幫你實現,來幫你完成這些共性的、繁瑣的工作,你只需要關注業務本身就行了,那些麻煩的協議dubbo來幫你遵循,所以就產生了dubbo,並且可能他做的這些工作比你自己寫起來更高效比你寫的代碼好。
服務的管理
當項目越來越大,模塊、服務越來越多,一個項目可能會調用很多服務,或者服務之間相互調用,這個時候你可能就不知道你這個服務調用了哪些服務或都被誰調用了,性能如何等,並且管理起來比較麻煩,別擔心,有服務治理中心可以幫你解決這些問題。它是dubbo的一個后台監控系統,叫dubbo-admin,可以下載他的war包運行在web容器里就可以用了。
二、使用dubbo框架來完成一個小demo
我們用idea+spring+maven+zookeeper+JDK 8,來搭建dubbo項目
前提是你安裝好zookeeper,idea安裝maven插件(也可以用其他IDE,過程類似)。
先說一下搭建的整體思路,是在idea里創建一個maven project,然后在這個project里創建三個model,分別是定義api接口的(將來發布到maven倉庫),實現api的(服務提供者)以及服務消費者。
1、創建project
在idea里【file】-【new】-【project】
然后按下圖選擇
填寫完相關信息后一路Next就好了,然后在這個project上右鍵【new】-【model】,創建為student-api,然后一路next后項目結構就是這樣的
2、編寫api接口
創建完project和第一個model之后,在student-api編寫我們的接口。我們創建一些實體類和接口。
具體代碼:

public interface StudentService{ ResultVO<Student> getStudentByName(String name); }

public class Student implements Serializable{ private String name; private String sex; private int age; public Student() { } public Student(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

public class ResultVO<T> implements Serializable{ private T data; private String msg; private String status; public ResultVO() { } public ResultVO(T data, String msg, String status) { this.data = data; this.msg = msg; this.status = status; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } }
3、api打包
我們在第2步寫了一個簡單的接口,和一些實體類,這一步我們需要將api整個項目打包,安裝到本地maven倉庫。因為服務提供者和消費者都依賴這個api項目。具體操作如下
選中student-api右鍵【maven】-【install -e】
這一步操作的前提是,你的IDE安裝了maven插件,如果沒安裝,右鍵是沒有【maven】選項的,具體怎么安裝這里不講。
install -e是執行maven的命令,此操作會將項目打包並安裝到maven本地倉庫,-e是會在idea控制台打日志。
執行命令后過一會查看控制台
就是成功了,安裝目錄頁告訴我們了。我們去對應目錄驗證下,已經有了。樣我們就可以像以往引用別人的依賴那樣引用我們自己的api了
4、創建服務提供者
這個時候我們就可以創建服務提供者了,同樣像創建student-api那樣創建服務提供者命名為student-server
只不過,在選擇時我們將它創建為webapp,因為這是一個spring web項目
5、引入依賴
在project(父項目,即最外層的那個)的pom.xml里引入dubbo和zkClient的依賴,因為服務提供者和消費者都需要這個依賴,所以寫在父項目里。

<!--spring web--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.13.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> </dependency> <!--引入dubbo--> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.4</version> </dependency> <!--引入zookeeper--> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> <exclusions> <exclusion> <artifactId>junit</artifactId> <groupId>junit</groupId> </exclusion> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--json數據轉換--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
引入student-api的依賴
<dependency> <groupId>com.dubbodemo.me</groupId> <artifactId>student-api</artifactId> <version>1.0</version> </dependency>
6、實現服務
在student-server項目里實現StudentService接口

@Service public class StudentServiceImpl implements StudentService{ @Override public ResultVO<Student> getStudentByName(String name) { Student student = new Student(name,"m",24); ResultVO<Student> studentResultVO = new ResultVO<Student>(student,"success","1"); return studentResultVO; } }
7、創建消費者
服務提供者已經寫好,接下來我們寫服務消費者。和student-server創建方式一樣,只不過命名為student-client
在這個項目里就不用引入dubbo的依賴了,因為我們已經把依賴放到父項目里了,各個model都能引用
此時項目都創建完了,看看結構
然后我們在student-client里調用服務,我們寫一個控制層StudentController.java

@Controller public class StudentController{ Logger logger = Logger.getLogger(StudentController.class); @Resource private StudentService studentService; @ResponseBody @RequestMapping("/getData") public ResultVO<Student> getData(String name){ ResultVO<Student> studentByName = studentService.getStudentByName(name); Student student = studentByName.getData(); logger.info("+++++++++返回碼是:"+studentByName.getStatus()+" name:"+student.getName()+" resultSet:"+student.getSex()); return studentByName; } }
至此,我們的代碼已經全部寫完了,接下來就是把服務提供者和服務消費者都啟動,來驗證一下。
8、配置
你會發現直到我們把代碼都寫完了,還沒用到有關dubbo的東西呢,沒錯,dubbo的關鍵是配置。
首先需要明確的是,student-server和student-client會以web項目啟動,而student-client還要用到Spring MVC。
我們先來配置student-server,在resources下創建ApplicationContext.xml為什么是ApplicationContext.xml?因為服務提供者只用到了spring,現在是提供服務沒有mvc)

<?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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--開啟注解--> <context:annotation-config/> <!--掃描注解--> <context:component-scan base-package="com.dubbodemo.me.service"/> <!--dubbo可以和spring無縫整合--> <!--配置目的:找到注冊中心,告訴注冊中心,是誰在向它注冊,IP是多少,提供服務的端口是多少--> <!--1、配置別名,目的是在后台好區分到底是誰, name 可以隨便寫,最好語義化--> <dubbo:application name="student-server"/> <!--2、注冊服務, zookeeper 注冊中心; address 注冊中心的地址;protocol 注冊中心的協議--> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/> <!--3、告訴注冊中心我要提供的服務 interface 代表發布的服務的類型 ref代表要發布的是哪個服務(具體的實現) timeout超時時間--> <dubbo:service interface="com.dubbodemo.me.api.StudentService" ref="studentServiceImpl" timeout="6000"/> <!--4、配置服務的端口,因為消費者必須通過IP+端口才能訪問我的服務,我們在注冊中心注冊時注冊中心就已經知道我們的IP了, 所以現在只需要告訴他端口 端口可以隨便寫,前提不可被其他程序占用。一個dubbo被發布時必須獨占一個端口--> <dubbo:protocol name="dubbo" port="12003"/> </beans>
服務提供者配置完了,我們再來配置消費者,在student-client的resources下創建spring-mvc.xml(為什么是spring-mvc.xml?因為服務消費者我們用的是spring mvc,到時候訪問controller驗證服務調用)

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--開啟注解--> <context:annotation-config/> <!--掃描路徑--> <context:component-scan base-package="com.dubbodemo.me.controller"/> <!--1、配置別名,目的是在后台好區分到底是誰, name 可以隨便寫,最好語義化--> <dubbo:application name="student-client"/> <!--2找到注冊中心--> <dubbo:registry address="127.0.0.1:2181" protocol="zookeeper" /> <!--3告訴注冊中心你要什么 id可以隨便寫--> <dubbo:reference interface="com.dubbodemo.me.api.StudentService" id="studentServiceImpl"/> <!--因為我是消費者,不提供服務,所以不用配置端口 當我也是服務提供者時需要配置--> <!--json轉換 我們將實體類直接返回 它會幫我們轉成json--> <mvc:annotation-driven /> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd" /> </bean> </property> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> </beans>
student-server的web.xml配置:讀取spring配置文件,加載spring

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- <async-supported>true</async-supported> --> <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> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:ApplicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
student-client的web.xml配置: 讀取spring配置文件,加載spring mvc模塊

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- <async-supported>true</async-supported> --> <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> <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:spring-mvc.xml</param-value> </init-param> <!--啟動項目時就創建這個servlet對象,這個參數必須在init-param之后出現--> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
驗證之前,我們先啟動zookeeper
然后再依次啟動student-server項目和student-client項目
啟動完成后,訪問student-client的接口localhost:9001/getData?name=tom
控制台日志
調用成功了。本教程源碼已經上傳到GitHub上了,傳送門
題外話:為什么兩個web項目的web.xml配置不一樣?
student-client里的controller是交給SpringMVC來處理,並不是spring容器。若交給spring容器來處理,代表項目一啟動就要創建出來controller,這個時候可能還沒有鏈接網絡(沒連zookeeper)可能還拿不到遠程對象,就會注入失敗,所以不能交給spring容器來處理。