
本文需要实现的是一个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