Skip to content

MethodEventSubscribe

刘召 edited this page Jul 21, 2020 · 2 revisions

功能介绍

该功能用于异步订阅方法的调用事件

订阅方法调用的实现方式较多,常用方式为动态代理,在方法调用前后做处理,但是这种方式对于第三方jar包中的类做代理是有局限性的(类实例直接在jar包中创建出来,无法代理),而该文档介绍的使用方式可以不局限于业务代码的代理 依赖于javaagent实现,通过字节增强实现方法调用订阅,之所以为订阅而不是拦截,主要是考虑该功能主要应用于监控,而非业务逻辑,即便是去掉探针接入,也不应该影响到业务,所以在使用场景上需要用户考虑是否适用

优势

  • 使用注解或者一行配置即可订阅方法调用;
  • 订阅的方法调用事件,不局限于业务类方法,第三方jar包中的类方法一样可以订阅;
  • 支持批量订阅;

接入方式

接入指南

配置

配置key 类型 默认值 说明
eventSubscribe.check.minSize Integer 10 订阅事件检测死循环的最小调用栈大小,避免因代码错误导致死循环同时也降低检测性能损耗,如果死循环时的调用栈长度小于该值,则需要人为调小该值,默认为10
eventSubscribe.callAsync Boolean true 事件是否为异步订阅,默认为true,即异步调用,如果为false,则事件源与订阅逻辑处于同一个线程,同步调用

用法

订阅方法

如果未配置忽略被订阅方法参数,则订阅方法必须包含被订阅方法的每个参数,且位置一一对应,订阅方法参数可在最后增加三个参数,分别为:被订阅类实例、被订阅方法返回值、被订阅方法抛出的异常,这三个参数的位置可不限先后(根据类型注入),且个数是可选的

注解方式(推荐)

  1. 【必须】在订阅方法上添加@Subscribe,如果希望订阅多个方法可添加多个@Subscribe;
  2. 【可选】如果订阅时忽略被订阅方法的参数,则在订阅方法上增加@IgnoreParams;
  3. 【可选】如果订阅方法的类实例希望自动创建,则在类上增加@AutoInstance,如果自己管理类实例(spring),则无需添加该注解;
  4. 【可选】在服务入口类上增加注解@Register({订阅类.class}),保证订阅类加载早于被订阅类,如果可确定订阅类早于被订阅类加载,则无需增加该注解;

步骤一

Subscribe注解配置说明(源码中也有对应注释),如果某个注解方法不配置,则不过滤该方法

方法名 说明 注意事项 配置参考
interfaces 被订阅类匹配的接口,任何一个匹配到(包含多级接口)则匹配成功 interfaces、parent、className方法必须配置至少一个(不能全为默认值) @Subscribe(interfaces = {"xxxx", "xxxx"}, .....)
parent 被订阅类匹配的父类(多级父类时,任何一个匹配到,则匹配成功) interfaces、parent、className方法必须配置至少一个(不能全为默认值) @Subscribe(parent= "xxxx", .....)
className 被订阅类的类名 interfaces、parent、className方法必须配置至少一个(不能全为默认值) @Subscribe(className= "xxxx", .....)
methodName 被订阅类匹配的方法名 @Subscribe(methodName= "xxxx", .....)
isMethodNameRegex methodName值是否正则表达式 @Subscribe(methodName= "execute.*", isMethodNameRegex=true, .....)
argumentNumber 被订阅方法的参数个数 @Subscribe(argumentNumber= 3, .....)
argumentType 被订阅方法的对应位置的参数类型,如果不指定某个位置的参数类型,可在对应位置使用空字符串,如:argumentsType={"java.lang.Integer", "", "java.lang.String"} @Subscribe(argumentType= {"java.lang.Integer", "", "java.lang.String"}, .....)

使用参考

被订阅类

package cn.xxx.test;

public class TestService {
    public String execute1(String cmd) {
		// 业务逻辑....
        return "success";
	}
    public String execute2(String cmd) {
		// 业务逻辑....
        return "success";
	}
    public void method(Integer num, String type) {
		// 业务逻辑....
	}
}

订阅类

package cn.xxx.test;

@AutoInstance
public class TestSubscribe {
    
    @IgnoreParams
	@Subscribe(className="cn.xxx.test.TestService", methodName = "execute.*", isMethodNameRegex = true)
	@Subscribe(className="cn.xxx.test.TestService", methodName = "method")
    public void deal(TestService service, String ret, Throwable t) {
		// 异常逻辑处理....
	}
}

服务入口类

package cn.xxx.test;

@Register({TestSubscribe.class})
public class Application {
    public static void main(String[] args) {
		// 启动
	}
}

配置方式

在配置文件中增加相应配置,针对上面的示例,不使用注解的等价配置为

eventSubscribe.group.ignoreParams.serviceSubscribe=.*cn\\.xxx\\.test\\.TestService\\.execute.* > cn.xxx.test.TestSubscribe.deal\\(.*
eventSubscribe.group.ignoreParams.serviceSubscribe2=.*cn\\.xxx\\.test\\.TestService\\.method\\(.* > cn.xxx.test.TestSubscribe.deal\\(.*

事件订阅配置,格式为:eventSubscribe.group.xxx=source classMethodRegular > subscribe className.methodRegular

key说明:xxx为自定义字符串,代表事件分组,如果包含.ignoreParams.则不强制订阅方法参数必须包含被订阅方法参数,被订阅方法中的参数也不会传递到订阅方法的参数中,如果key中不包含.ignoreParams.,则subscribe className.methodRegular的方法参数必须包含source classMethodRegular的方法参数且位置一致

value说明:source classMethodRegular为被订阅类方法正则,subscribe className.methodRegular为订阅类全称及方法正则,其中方法正则中必须包含一个参数起始(# 订阅方法参数后面可多出3种类型的参数(ignoreParams的也一样):被订阅类实例、被订阅方法返回值、被订阅方法抛出的异常,参数的位置放在最后,这个三个参数的顺序不限先后

配置支持一个方法被多个注册者监听,一个监听者也可以监听多个方法,多对多的关系

注意:在订阅类中不能再调用被订阅类的订阅方法,否则会导致死循环(已做了运行限制,但有些用法可能失效);事件的通知是异步的,如果消费慢于生产可能导致事件丢弃;在一个分组内的事件通知为单线程调用;

为什么要设置不同分组,因为在一个组内被订阅、订阅配置都可能匹配多个方法,而且订阅与被订阅的注册时间不分先后,所以为了绑定注册与被注册关系,需要中间的桥梁,即时间组,事件传递为:多个被订阅方法 -> 组事件 ->多个订阅方法

使用场景

  • 监控所有mysql客户端包请求异常、mongodb客户端包请求异常

注意事项

  • 如果出现无法订阅事件,可能是被订阅类过早的加载了,如配置了订阅JDK中的类,很有可能导致无法订阅;