本文需要实现的是一个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(); } } }
注:原创博客,转载请注明。
原文:https://www.cnblogs.com/Vivianwang/p/9451006.html