[Dubbo開發]Dubbo日誌插件實現(未打包)
本文需要實現的是一個Dubbo的日誌插件,日誌插件的原理如上圖所示。
一、原理
簡單的Dubbo生產者和消費者實現服務調用的原理為:
①生產者在註冊中心上註冊服務;
②消費者在註冊中心上訂閱服務;
③一旦建立了訂閱,消費者和生產者將進行點對點的通信;
此時會產生一個問題:如果作為第三方需要對服務的調用過程進行日誌記錄(有實際生產需求),那麽將失去對調用服務的控制。
於是,在Dubbo簡單生產者和消費者的基礎上,增加一個日誌服務器(本質上也是一個Dubbo生產者),並使用Dubbo攔截器實現日誌的記錄功能,實現的原理為:
④消費者向生產者發送request請求之前,先由攔截器(filter)向日誌服務器發送一條日誌,將請求的信息,諸如接口、方法名、參數等信息記錄到日誌服務器上;
⑤消費者對生產者發送request請求;
⑥生產者對消費者的請求進行響應(Response),當然前提是網絡連接暢通,生產者服務可以正常被調用;
⑦在消費者收到響應之後,由攔截器(filter)再向日誌服務器發送一條日誌,將返回的信息,諸如返回值等信息記錄到日誌服務器上。
以上就是以Dubbo攔截器方式實現的日誌插件的原理。
二、實現
接下來是具體的實現過程:
消費者端的文件目錄結構
FilterDesc.java
package com.mohrss.service; /* * Class: FilterDesc * Function:攔截對象 */ public class FilterDesc { private String interfaceName ;//接口名 private String methodName ;//方法名 private Object[] args ;//參數 public FilterDesc(){ } public String getInterfaceName() { return interfaceName; } public void setInterfaceName(String interfaceName) { this.interfaceName = interfaceName; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } }
LogFilter.java
package com.mohrss.service; import com.alibaba.dubbo.rpc.*; import com.alibaba.dubbo.rpc.service.GenericService; import com.alibaba.fastjson.JSON; /* * Class:LogFilter * Function:日誌攔截器 */ public class LogFilter implements Filter { private static final FilterDesc filterReq = new FilterDesc(); private static final FilterDesc filterRsp = new FilterDesc(); private LogService ls = null; public LogFilter(){ ls = (LogService)SpringContextUtil.getApplicationContext().getBean("testLogService"); } public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try{ //request部分 filterReq.setInterfaceName(invocation.getInvoker().getInterface().getName()); filterReq.setMethodName(invocation.getMethodName()); filterReq.setArgs(invocation.getArguments()); ls.printLog("Dubbo請求數據" + JSON.toJSONString(filterReq)); Result result = invoker.invoke(invocation); if(GenericService.class != invoker.getInterface() && result.hasException()){ ls.printLog("Dubbo執行異常"+result.getException().toString()); }else{ ls.printLog("Dubbo執行成功"); filterRsp.setInterfaceName(invocation.getMethodName()); filterRsp.setMethodName(invocation.getMethodName()); filterRsp.setArgs(new Object[]{result.getValue()}); ls.printLog("Dubbo返回數據" + JSON.toJSONString(filterRsp)); } return result; } catch(RuntimeException e){ ls.printLog("Dubbo未知異常" + RpcContext.getContext().getRemoteHost() + ".service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage()); throw e; } } }
LogService.java(由日誌服務器提供的接口文件,實際中應該是打包成jar包)
package com.mohrss.service; public interface LogService { public void printLog(String log); }
ProviderService.java(由生產者提供的接口文件,實際中應該是打包成jar包)
package com.mohrss.service; public interface ProviderService { public void sayHello(); public int calc(int a, int b); }
SpringContextUtil.java
package com.mohrss.service; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /* * Class: SpringContextUtil * Function:用於獲得當前Spring上下文環境的工具類 */ public class SpringContextUtil implements ApplicationContextAware { // Spring應用上下文環境 private static ApplicationContext context; /** * 實現ApplicationContextAware接口的回調方法。設置上下文環境 * * @param applicationContext */ public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.context = applicationContext; } /** * @return ApplicationContext */ public static ApplicationContext getApplicationContext() { return context; } public static Object getBean(String name) throws BeansException { return context.getBean(name); } }
com.alibaba.dubbo.rpc.filter(攔截器配置文件)
logFilter=com.mohrss.service.LogFilter
dubbo-consumer.xml(消費者自己的dubbo配置文件)
<?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: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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <!-- 消費方應用名,用於計算依賴關系,不是匹配條件,不要與提供方一樣 --> <!-- <dubbo:application name="consumer" /> --> <!-- 使用multicast廣播註冊中心暴露發現服務地址 --> <!-- <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" /> --> <!-- 生成遠程服務代理,可以和本地bean一樣使用testProviderService --> <!-- 對誰攔截,就給誰加filter --> <dubbo:reference id="testProviderService" interface="com.mohrss.service.ProviderService" filter="logFilter" async="true"/> <!-- 引入外部插件的配置文件 --> <import resource="classpath:filter.xml" /> </beans>
dubbo.properties(更加規範的配置文件,可以避免經常修改dubbo-consumer.xml文件,文件名必須叫dubbo.properties,不然會加載不到)
# Consumer application name dubbo.application.name=consumer # Zookeeper registry address dubbo.registry.protocol=zookeeper dubbo.registry.address=127.0.0.1:2181
filter.xml(插件的配置文件,因為是作為插件使用,所以單獨放在filter.xml中,在dubbo-consumer.xml裏用import resource引用)
<?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: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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <bean id="springContextUtil" class="com.mohrss.service.SpringContextUtil" /> <dubbo:reference id="testLogService" interface="com.mohrss.service.LogService" /> </beans>
TestConsumerService.java
package com.mohrss.consumer; import java.io.IOException; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.mohrss.service.ProviderService; /* * Class: TestConsumerService * Function: 程序入口,訪問生產者,被攔截的對象 */ public class TestConsumerService { public static void main(String[] args) { //讀取xml配置文件 ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"dubbo-consumer.xml"}); context.start(); ProviderService testService = (ProviderService) context.getBean("testProviderService"); testService.calc(1, 2); testService.calc(5, 6); testService.sayHello(); testService.calc(7, 8); try { System.in.read(); } catch (IOException e) { e.printStackTrace(); } } }
生產者代碼實現參考之前的博客:https://www.cnblogs.com/Vivianwang/p/9408493.html
日誌服務器文件目錄:
LogService.java
package com.mohrss.service; public interface LogService { public void printLog(String log); }
LogServiceImpl.java
package com.mohrss.service.impl; import org.springframework.stereotype.Service; import com.mohrss.service.LogService; @Service("logService") public class LogServiceImpl implements LogService { public void printLog(String log){ System.out.println(log); } }
dubbo-log.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <!--用註解方式實現bean--> <context:component-scan base-package="com.mohrss.service"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan> <!-- 提供方應用信息,用於計算依賴關系 --> <dubbo:application name="logservice" /> <!-- 使用zookeeper註冊中心暴露服務地址 配置後spring管理--> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> <!-- 用dubbo協議在20881端口暴露日誌服務 --> <dubbo:protocol name="dubbo" port="20881" /> <!-- 聲明需要暴露的服務接口 --> <dubbo:service interface="com.mohrss.service.LogService" ref="logService" /> </beans>
TestLogService.java
package com.mohrss.service; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestLogService { public static void main(String[] args) { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"dubbo-log.xml"}); context.start(); System.out.println("日誌服務已經註冊成功!"); try { System.in.read();//讓此程序一直跑,表示一直提供服務 } catch (Exception e) { e.printStackTrace(); } } }
註:原創博客,轉載請註明。
[Dubbo開發]Dubbo日誌插件實現(未打包)