一种对应用进行无侵入化埋点的实现方法及系统
技术领域
本发明涉及软件开发技术领域,具体来讲是一种对应用进行无侵入化埋点的实现方法及系统。
背景技术
自从Java引入了Annotation(注解)这一特性后,Annotation就成为了大型软件框架API(Application Programming Interface,应用程序编程接口)中的重要组成部分。这类的大型软件框架包括Spring、Hibernate。其显著的特点是通过在应用程序中添加简单的几行注解代码就能在应用程序中实现相当复杂的逻辑。这种声明式编程方式在软件开发人员特别是Java开发人员间得到了广泛的使用。
但是,实际应用中很少有软件开发人员选择在自己编写的框架或者是中间件中提供基于注解的API。最主要的原因是因为很难去实现。因此,为了在项目中引入埋点逻辑,目前开发人员必须基于Spring boot框架构建一个项目工程,作为需要添加埋点逻辑的应用的父工程。这样就存在很多缺陷,如潜在的jar的冲突、要求应用必须使用了Spring框架等。
因此,如何设计一种新的埋点方法可以避免上述缺陷,是本领域技术人员亟待解决的问题。
发明内容
本发明的目的是为了克服上述背景技术的不足,提供一种对应用进行无侵入化埋点的实现方法及系统,能在不要求应用是Spring应用且必须依赖另外一个工程作为父工程的情况下,对应用进行无侵入的埋点,满足了实际使用需求。
为达到以上目的,本发明提供一种对应用进行无侵入化埋点的实现方法,该方法包括以下步骤:
A、定义构造函数拦截器,该构造函数拦截器用于在被拦截的类的构造函数后织入埋点逻辑;为该构造函数拦截器创建拦截方法,构造函数拦截器的拦截方法的实现功能为:当被拦截的类被实例化时,该拦截方法将链式执行预设的拦截链中的埋点逻辑,该预设的拦截链包括多个依次调用的埋点逻辑;
定义方法拦截器,该方法拦截器用于在被拦截的方法的执行前、后织入埋点逻辑;为该方法拦截器创建拦截方法,方法拦截器的拦截方法的实现功能为:当对象调用被拦截的方法时,该拦截方法将在被拦截的方法执行之前和之后均链式执行预设的拦截链中的埋点逻辑;
B、为埋点应用定义基于java agent的启动类,该埋点应用为实现埋点的应用;并为该启动类创建启动类实现方法,该启动类实现方法具有两个参数:一个是通过javaagent选项提供的启动参数,一个是Java虚拟机监控接口的实例,且该实现方法的功能为:将构造函数拦截器的拦截方法、方法拦截器的拦截方法注册到Java虚拟机监控接口的实例上,使得Java虚拟机在加载各种类之前会将构造函数拦截器和方法拦截器的拦截方法织入到被拦截的类中,然后加载被织入后的类;
C、将具有上述启动类的埋点应用添加到需要进行埋点的应用中,并将其设置为应用启动时运行,结束。
本发明同时还提供一种对应用进行无侵入化埋点的实现系统,该实现系统包括拦截器定义模块、启动类定义模块和埋点应用添加模块;
所述拦截器定义模块用于:定义构造函数拦截器,该构造函数拦截器用于在被拦截的类的构造函数后织入埋点逻辑;为该构造函数拦截器创建拦截方法,构造函数拦截器的拦截方法的实现功能为:当被拦截的类被实例化时,该拦截方法将链式执行预设的拦截链中的埋点逻辑,该预设的拦截链包括多个依次调用的埋点逻辑;
定义方法拦截器,该方法拦截器用于在被拦截的方法的执行前、后织入埋点逻辑;为该方法拦截器创建拦截方法,方法拦截器的拦截方法的实现功能为:当对象调用被拦截的方法时,该拦截方法将在被拦截的方法执行之前和之后均链式执行预设的拦截链中的埋点逻辑;
所述启动类定义模块用于:为埋点应用定义基于java agent的启动类,该埋点应用为实现埋点的应用;并为该启动类创建启动类实现方法,该启动类实现方法具有两个参数:一个是通过java agent选项提供的启动参数,一个是Java虚拟机监控接口的实例,且该启动类实现方法的功能为:将构造函数拦截器的拦截方法、方法拦截器的拦截方法注册到Java虚拟机监控接口的实例上,使得Java虚拟机在加载各种类之前会将构造函数拦截器和方法拦截器的拦截方法织入到被拦截的类中,然后加载被织入后的类;
所述埋点应用添加模块用于:将具有上述启动类的埋点应用添加到需要进行埋点的应用中,并将其设置为应用启动时运行。
本发明的有益效果在于:
(1)本发明中,为埋点应用定义了一个基于java agent的启动类,该启动类的实现方法中应用了两个拦截器:构造函数拦截器和方法拦截器,该构造函数拦截器用于在被拦截的类的构造函数后织入埋点逻辑,该方法拦截器用于在被拦截的方法的执行前、后织入埋点逻辑。通过上述两个拦截器可在应用中实现对构造函数的拦截和方法的拦截,并实现埋点逻辑的织入。
与现有技术的Spring boot的埋点方法相比,本发明基于java agent的埋点方案避免了无用的第三方jar的引入,基本无第三方jar的依赖,从而避免了因Spring boot埋点方案的实现中依赖的jar,而容易影响到应用的依赖的jar,造成潜在的jar的冲突。与此同时,本发明基于java agent的埋点方案不要求应用必须是Spring应用,因此应用可以是使用其他IoC(Inversion of Control控制反转)框架的应用,如Guice,也可也是不使用任何框架的应用。埋点应用不影响应用的应用框架的选型,从而使得该方案能够适用更广泛的java项目,进而达到无侵入化埋点的目的。
(2)本发明中,对于需要添加埋点逻辑的应用只需要在其启动脚本中添加埋点应用的jar文件对应的启动参数即可,当应用不需要添加埋点逻辑时只需要将该启动参数移除即可,不但操作简单而且埋点灵活、应用对埋点无感知。
(3)本发明中是利用过滤器Filter的实现类对应单一的埋点逻辑。因此,新的埋点逻辑只需要实现Filter接口并添加实例到拦截链中即可,使得埋点逻辑易扩展。
附图说明
图1为本发明实施例中对应用进行无侵入化埋点的实现方法的流程图;
图2为本发明实施例中被拦截的类实例化时代码执行流程示意图;
图3为本发明实施例中当对象调用被拦截类的方法时代码执行流程示意图;
图4为本发明实施例中对应用进行无侵入化埋点的实现方法的结构框图。
具体实施方式
下面结合附图及具体实施例对本发明作进一步的详细描述。
本发明旨在设计一种实现无侵入化埋点的方案,所谓无侵入化是指:应用在使用第三方的框架或者组件的时候,不需要继承框架或组件提供的实体类实现框架提供的接口,或是继承第三方框架或者组件的项目工程,即应用在失去所依赖的第三方框架或组件的时候,能够正常运行;这时,我们称第三方框架或者组件对应用是无侵入的。
基于上述设计目的,参见图1所示,本发明实施例提供一种对应用进行无侵入化埋点的实现方法,该实现方法包括以下步骤:
S1、定义构造函数拦截器,该构造函数拦截器用于在被拦截的类的构造函数后织入埋点逻辑;为该构造函数拦截器创建拦截方法,构造函数拦截器的拦截方法的实现功能为:当被拦截的类被实例化时,该拦截方法将链式执行(一个接一个执行)预设的拦截链中的埋点逻辑,该预设的拦截链包括多个依次调用的埋点逻辑。
具体来说,本实施例中,构造函数拦截器命名为ConstructorInterceptor,为该构造函数拦截器创建的拦截方法命名为intercept,且该拦截方法intercept定义如下:
public void intercept(@This Object thiz,@Origin Class<?>clazz);
其中,参数thiz是被拦截的类的实例,clazz是被拦截类的Class实例。该拦截方法对被拦截的类的构造函数进行拦截,用于在被拦截的类的构造方法后织入多个埋点逻辑。
S2、定义方法拦截器,该方法拦截器用于在被拦截的方法的执行前、后织入埋点逻辑;为该方法拦截器创建拦截方法,方法拦截器的拦截方法的实现功能为:当对象调用被拦截的方法时,该拦截方法将在被拦截的方法执行之前和之后均链式执行(一个接一个执行)预设的拦截链中的埋点逻辑。
具体来说,本实施例中,方法拦截器命名为MethodInterceptor,为该方法拦截器创建的拦截方法命名为intercept,且该拦截方法定义如下:
public Object intercept(@SuperCall Callable<Object>zuper,@This Objectthiz,@Origin Method method);
其中,参数zuper是被拦截的方法调用,thiz时被拦截类的方法实例,method是被拦截的方法的实例。该拦截方法对被拦截的普通方法进行拦截,用于在被拦截的普通方法的执行前、后织入多个埋点逻辑。
可以理解的是,本实施例中,构造函数拦截器、方法拦截器织入的埋点逻辑包括但不限于:性能监测埋点逻辑(为软件开发人员提供应用性能调优的数据来源)、日志埋点逻辑(方便软件开发人员对问题的追踪)。通过将过滤器Filter设计为功能单一的埋点逻辑来实现每个埋点逻辑。多个埋点逻辑依次调用的实现流程包括:在每个埋点逻辑中封装一个用于调用下一个链对象的方法doFilter,该doFilter方法具有两个参数:一个是拦截器(包括构造函数拦截器和方法拦截器)收集到的信息参数,一个是拦截链实例;在每个埋点逻辑(Filter)实例的doFilter方法中调用拦截链实例的doFilter方法(如chain.doFilter)继续下一个埋点逻辑(Filter)实例的doFilter方法的调用。
具体来说,Filter接口定义如下:
public interface Filter{void doFilter(Request req,FilterChainchain);}其中,Request定义了拦截器收集到的信息,如被拦截的类的Class实例,被拦截类的实例等等。chain是拦截链实例。
另外,需要强调是,实际操作时步骤S1和步骤S2可同时执行或根据实际情况进行先后顺序的调整,本实施例仅为一具体实例,并不是对其先后顺序的限制。
S3、为埋点应用定义基于java agent的启动类,该埋点应用为实现埋点的应用;并为该启动类创建启动类实现方法,该启动类实现方法具有两个参数:一个是通过javaagent选项提供的启动参数(args),一个是Java虚拟机监控接口的实例(inst);且该启动类实现方法的功能为:将构造函数拦截器(ConstructorInterceptor)拦截方法(intercept)和方法拦截器(MethodInterceptor)的拦截方法(intercept)注册到所述Java虚拟机监控接口的实例(inst)上,使得Java虚拟机在加载各种类之前会将构造函数拦截器和方法拦截器的拦截方法织入到被拦截的类中,然后加载修改后(即被织入后)的类。
本实施例中,该启动类命名为Launcher,为该启动类创建的启动类实现方法命名为premain,且启动类实现方法premain定义如下:
public static void premain(String args,Instrumentation inst);
该启动类实现方法premain运行在main函数之前,与main函数运行在同一个JVM(Java Virtual Machine,Java虚拟机)中。其中,第一个参数args是通过java agent选项提供的启动参数;第二个参数inst是一个Java虚拟机监控接口java.lang.Instrumentation的实例,它由JVM自动传入,该Java虚拟机监控接口提供了类定义的转换和操作。
另外,可以理解的是,将构造函数拦截器(ConstructorInterceptor)的拦截方法(intercept)和方法拦截器(MethodInterceptor)的拦截方法(intercept)注册到所述Java虚拟机监控接口的实例(inst)上时有多种方式,如ASM(ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能;ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为)、Javassist(Javassist是一个开源的分析、编辑和创建Java字节码的类库)、Byte buddy(字节码操作工具)等等。
S4、将具有上述启动类的埋点应用添加到需要进行埋点的应用中,并将其设置为应用启动时运行,结束。
实际操作中,步骤S4具体可包括以下流程:
S401、在埋点应用中添加MANIFEST.MF文件,并将其manifest属性中的Premain-Class项设置为上述启动类的名称,即Launcher。
S402、将当前埋点应用编译并打包成jar文件,如profiler.jar。
S403、在需要进行埋点的应用的启动脚本中添加该jar文件的运行命令。举例来说,可通过以下代码实现:
java-javaagent:$PROFILER_PATH/profiler.jar
-jar app.jar
其中,$PROFILER_PATH是profiler.jar的全路径,app表示需要进行埋点的应用。
另外,如果需要进行埋点的应用是war(Web application Archive,Web应用程序包)应用,则需要在tomcat或jetty(jetty和tomcat为目前全球范围内最著名的两款开源的webserver/servlet容器)的启动脚本中添加-javaagent:profiler.jar。例如tomcat启动脚本的添加:CATALINA_OPTS="$CATALINA_OPTS
-javaagent:$PROFILER_PATH/profiler.jar"。
可以理解的是,利用本实施例的方法对需要进行埋点的应用添加上述埋点应用后,该应用代码实际执行的情况将如下:
a)以名称为Bean的类为例,如2所示,当Bean类被实例化的时候,实际上执行的代码是:1)JVM执行Bean的构造函数;2)执行构造函数拦截器(ConstructorInterceptor),构造函数拦截器的拦截方法(intercept)将链式执行埋点逻辑1(Filter1)至埋点逻辑N(FilterN)。
b)以名称为bean的类的方法为例,如图3所示,当对象调用bean类的普通方法时,实际上执行的代码是:1)在执行bean类的普通方法之前,执行方法拦截器(MethodInterceptor),方法拦截器的拦截方法(intercept)将链式执行埋点逻辑1(Filter1)至埋点逻辑N(FilterN);2)执行bean类的普通方法(bean.method);3)在执行bean类的普通方法之后,执行方法拦截器(MethodInterceptor),方法拦截器的拦截方法(intercept)将链式执行埋点逻辑1(Filter1)至埋点逻辑N(FilterN)。
参见图4所示,本发明实施例提供一种对应用进行无侵入化埋点的实现系统,该实现系统包括拦截器定义模块、启动类定义模块和埋点应用添加模块。
其中,拦截器定义模块用于:定义构造函数拦截器,该构造函数拦截器用于在被拦截的类的构造函数后织入埋点逻辑;为该构造函数拦截器创建拦截方法,构造函数拦截器的拦截方法的实现功能为:当被拦截的类被实例化时,该拦截方法将链式执行预设的拦截链中的埋点逻辑,该预设的拦截链包括多个依次调用的埋点逻辑;
定义方法拦截器,该方法拦截器用于在被拦截的方法的执行前、后织入埋点逻辑;为该方法拦截器创建拦截方法,方法拦截器的拦截方法的实现功能为:当对象调用被拦截的方法时,该拦截方法将在被拦截的方法执行之前和之后均链式执行预设的拦截链中的埋点逻辑。
启动类定义模块用于:为埋点应用定义基于java agent的启动类,该埋点应用为实现埋点的应用;并为该启动类创建启动类实现方法,该启动类实现方法具有两个参数:一个是通过java agent选项提供的启动参数,一个是Java虚拟机监控接口的实例,且该启动类实现方法的功能为:将构造函数拦截器的拦截方法、方法拦截器的拦截方法注册到Java虚拟机监控接口的实例上,使得Java虚拟机在加载各种类之前会将构造函数拦截器和方法拦截器的拦截方法织入到被拦截的类中,然后加载被织入后的类。
埋点应用添加模块用于:将具有上述启动类的埋点应用添加到需要进行埋点的应用中,并将其设置为应用启动时运行。具体来说,其实际操作流程为:在埋点应用中添加MANIFEST.MF文件,并将其manifest属性中的Premain-Class项设置为上述启动类的名称;将当前埋点应用编译并打包成jar文件;在需要进行埋点的应用的启动脚本中添加该jar文件的运行命令。
需要说明的是:上述实施例提供的实现系统在进行操作时,仅以上述各功能模块的划分进行举例说明,实际应用中,可以根据需要而将上述功能分配由不同的功能模块完成,即将系统的内部结构划分成不同的功能模块,以完成以上描述的全部或者部分功能。
本发明不局限于上述实施方式,对于本技术领域的普通技术人员来说,在不脱离本发明原理的前提下,还可以做出若干改进和润饰,这些改进和润饰也视为本发明的保护范围之内。
本说明书中未作详细描述的内容属于本领域专业技术人员公知的现有技术。