检测Java Web应用程序而无需修改其源代码

与其他系统进行交互时,大多数Java Web应用程序都使用标准Java接口。 使用接口javax.servlet.Servlet来实现基于HTTP的服务,例如网页或REST服务器。 使用JDBC接口java.sql.Statementjava.sql.Connection实现数据库交互。 这些标准几乎是通用的,与基础框架(Spring或Java EE)和Servlet容器(Tomcat,Wildfly等)无关。

本文介绍如何实现Java代理,该Java代理使用Bytecode操作来挂接到这些接口,并收集有关HTTP和数据库调用的频率和持续时间的度量。 演示代码可在https://github.com/fstab/promagent上找到 ,该代码是为Prometheus监视系统检测Java Web应用程序的代理。 但是,本文不是Prometheus特有的,它着重于Java代理,字节码操作和类加载器等基础技术。

1. Java代理

Java代理是可以附加到JVM以便操作Java字节码的Java程序。 例如,可以使用Java代理来修改接口javax.servlet.Servlet所有实现,以获取有关HTTP调用的数量和持续时间的统计信息。

Java代理以JAR文件的形式提供。 常规Java程序使用main()方法作为应用程序的入口点,而Java代理具有premain()方法,该方法将在应用程序的main()方法之前调用:

Java代理概述

public class MyAgent {public static void premain(String agentArgs, Instrumentation inst) throws Exception {// ...}
}

虽然可执行的JAR文件有一个MANIFEST.MF文件中指定Main-Class ,代理JAR文件有一个MANIFEST.MF文件中指定的Premain-Class 。 可以使用命令行选项-javaagent:在应用程序启动期间附加代理:

Java代理命令行

java -javaagent:myagent.jar -jar myapp.jar

然后, premain()方法可以调用inst.addTransformer()来注册ClassFileTransformer 。 类文件转换器实现了每当加载Java类时都会调用的transform()方法。 它可以检查和修改任何Java类的字节码,以添加其他功能。

2.字节码操作

有几个可用的库可帮助Java开发人员实现字节码操作。 最低级别的是ASM 。 其他库,例如cglib和javassist提供更高级别的API。 最新,最易于使用的库是Byte Buddy 。 它提供了易于理解的流畅Java API,用于创建ClassFileTransformer并将其注册到Instrumentation

字节伙伴代理示例

package io.promagent;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.Transformer;
import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.named;public class MyAgent {public static void premain(String agentArgs, Instrumentation inst) throws Exception {new AgentBuilder.Default().type(hasSuperType(named("javax.servlet.Servlet"))).transform(new Transformer.ForAdvice().include(MyAgent.class.getClassLoader()).advice(ElementMatchers.named("service"), "io.promagent.MyAdvice")).installOn(inst);}

上面的示例显示了检测所有javax.servlet.Servlet实现的service()方法所需的完整代码。 每当Servlet处理Web请求时,都会调用service()方法。 MyAdvice类定义将注入到Servlet的service()方法中的代码。 这段代码使用@Advice.OnMethodEnter@Advice.OnMethodExit

字节好友建议示例

public class MyAdvice {@Advice.OnMethodEnterpublic static void before(ServletRequest request, ServletResponse response) {System.out.println("before serving the request...");}@Advice.OnMethodExitpublic static void after(ServletRequest request, ServletResponse response) {System.out.println("after serving the request...");}
}

Byte Buddy提供了两种检测方法:建议(如上所示)和拦截器。 区别是微妙的:通过建议, @Advice.OnMethodEnter@Advice.OnMethodExit方法的字节码被复制到被拦截方法的开始和最终块中。 效果与将代码复制并粘贴到要拦截的service()实现中相同。 结果,在完成检测之后,不再使用类MyAdvice 。 截获的service()方法不需要访问MyAdvice类,可以在MyAdvice类不可用的类加载器上下文中执行。

另一方面,拦截器是常规方法调用,它们在被拦截方法的开始和最终块中执行。 这意味着被拦截的方法必须在拦截器可用的上下文中执行。

在以下各节中,我们将看到在应用服务器环境中类的可见性可能受到限制,这就是Promagent使用Advices而不是Interceptor的原因。

3.添加依赖项

为了将上面的示例变成有用的东西,我们需要用代码维护指标并将指标提供给监视系统来替换System.out.println()消息。 例如, Promagent使用Prometheus客户端库来维护和公开Prometheus指标。

JVM自动将使用-javaagent:命令行参数指定的JAR文件添加到应用程序的系统类加载器。 因此,从理论上讲,应该可以创建一个包含代理及其所有依赖项的Uber JAR ,并在-javaagent:命令行参数中使用它。

但是,在应用程序服务器环境中,使所有依赖项在系统类加载器上可用都是有问题的,原因有两个:

  • 某些代理的依赖项可能与应用程序服务器内部使用的库或WAR文件中作为已部署应用程序的一部分提供的库发生冲突。
  • 为了防止冲突,应用程序服务器限制了从系统类加载器对类的访问。 例如,除非使用jboss.modules.system.pkgs系统属性显式公开了受影响的程序包,否则Wildfly模块无法从系统类加载器访问类。 跟踪所有依赖关系并相应地配置模块系统并非易事。

更好的方法是只公开几个Java类,而无需外部依赖系统类加载器,并使用自定义类加载器加载实际的度量实现。 这将潜在的冲突和运行代理所需的配置最小化。

4.从自定义类加载器加载钩子

在Java中实现自定义类加载器很容易,因为我们可以简单地使用java.net.URLClassLoader并使用指向我们的类所在的JAR文件的路径对其进行初始化。 为了使代理易于使用, Promagent作为包含其他JAR文件的JAR文件提供。 内部JAR文件在启动时被复制到临时目录,并且使用临时路径配置了自定义类加载器。 这样,用户将获得一个单一的代理JAR,而该代理在内部区分系统类加载器上的类(这些类直接包含在代理JAR中)和定制类加载器上的类(这些类是从JAR中加载的)临时目录)。

实际的工具在称为hook的类中实现。 该挂钩是从自定义类加载器加载的。 这样,只要自定义类加载器能够提供这些依赖关系,钩子就可以引用它需要的任何依赖关系。 例如, ServletHook如下所示:

自定义钩子类示例

public class ServletHook {public void before(ServletRequest request, ServletResponse response) {// ...}public void after(ServletRequest request, ServletResponse response) {// ...}
}

该钩子看起来与Byte Buddy建议类似。 区别在于Byte Buddy建议仅是几行代码,这些代码具有最小的依赖性,以便从自定义类加载器加载相应的钩子,并通过反射将其委派给钩子的before()after()方法。 字节伙伴建议对检测库没有任何依赖关系,因为实际的检测库仅在自定义类加载器中可见。

但是,在加载钩子时有一个细微的陷阱:参数ServletRequestServletResponse将从已检测的Servlet传递。 这意味着,挂钩中的ServletRequestServletResponse类必须使用与拦截的Servlet相同的类加载器进行加载,否则我们无法将Servlet的参数传递到挂钩的before()after()方法中。

解决方案是使用Thread.currentThread().getContextClassLoader()作为自定义类加载器的父级。 这样,可以从上下文类加载器加载的所有类都将从上下文类加载器加载。 这包括ServletRequestServletResponse 。 只有当前上下文中不可用的类(例如钩子本身及其依赖项)才会从自定义JAR文件中加载。 这意味着我们每个上下文需要一个自定义类加载器,因为每个自定义类加载器都会委托另一个上下文类加载器作为其父代。

5.实施全球指标注册

使用到目前为止描述的实现,可以检测单个Web应用程序。 但是,如果应用程序服务器上有多个部署,则每个工具将具有自己的类加载器。 从不同的类加载器加载指标库时,部署无法共享在该指标库中定义的全局静态变量。 例如,不可能跨多个部署使用Prometheus客户端库随附的全局度量标准注册表。 缺少全局注册表,每个部署都需要独立维护和公开其指标。

解决此问题的一种方法是扩展自定义类加载器,并使其委托将共享度量库加载到另一个共享的自定义类加载器。 但是,JVM还附带有一个内置的全局注册表,我们可以将其用作VM范围的指标存储:JMX平台MBean服务器。 将度量标准注册为MBean具有以下好处:

  • 全局注册表:JMX平台MBean服务器提供了VM范围的注册表,使我们能够维护一组全局指标,以对应用程序服务器上的所有部署进行检测。
  • 监视系统的单个导出器:易于实现一个小型Web应用程序,该应用程序从MBean服务器读取所有度量并将其提供给监视系统。 例如, Promagent包含用于将指标导出到Prometheus服务器的WAR部署。
  • JMX工具:由于所有指标都可以作为MBean使用,因此可以使用任何JMX客户端来了解指标的状态。

JMX平台MBean服务器是Java SE的一部分,可以通过静态方法ManagementFactory.getPlatformMBeanServer()进行访问。 在MBean服务器上注册的Java对象称为MBean。 MBean必须在一个接口中定义其可公开访问的API,按照惯例,该接口的名称类似于Java类,并附加了后缀MBean 。 例如,要将Counter类注册为MBean,该类必须实现一个名为CounterMBean的接口。 每个MBean均可通过唯一的ObjectName寻址。 可以使用MBeanServer.invoke()来调用MBean接口中定义的方法。

6.总结

本文概述了如何在不修改Java Web应用程序源代码的情况下对其进行检测。 它基于Promagent ,该工具使用Prometheus指标对Java应用程序进行检测。 但是,本文重点介绍了诸如Java代理, 字节好友字节码操作库之类的基础技术,在诸如Wildfly之类的应用服务器环境中的类加载以及JMX平台MBean服务器。

最好从Promagent示例代码中总结出一些松散的结局,例如如何避免HTTP请求经过多个Servlet链时重复计数。 有关更多示例,值得研究相关项目,例如inspectIT或stagemonitor 。

翻译自: https://www.javacodegeeks.com/2017/07/instrumenting-java-web-applications-without-modifying-source-code.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349307.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何快速弄懂一个新模型_如何评估创业项目是否靠谱?一个新的模型 | 创创锦囊...

要判断一个创业项目是否靠谱,是否能拥有广阔的市场和巨大的增长潜力,不仅是投资人关心的话题,更是每一个创业者在创业过程中不断思考的问题。投资人关注大趋势、大机会,遵循自上而下的思维模型,在心仪的赛道上寻找合适…

java编译找不到符号 int age=in.nexint()_Java报错找不到符号,小白自学求大佬解决...

import java.util.*;public class guess_1{public static void main(String[] args){Scanner innew Scanner(System.in);System.out.println("--------猜拳游戏--------");System.out.println("请出拳(1.剪刀 2.石头 3.布)");int personin.nextInt();int c…

Java命令行界面(第24部分):MarkUtils-CLI

本系列中有关使用Java解析命令行参数的第一篇文章介绍了Apache Commons CLI库。 这是本系列中介绍的基于Java的命令行解析库中最古老的,而且可能是最常用的之一。 Apache Commons CLI确实显示了它的时代,特别是与一些更现代的基于Java的命令行处理库相比…

view如何接受json_如何将你的 ThinkJS 项目部署到 ZEIT 上

编者按:本文作者奇舞团前端开发工程师李喆明。什么是 ZEITZEIT(https://zeit.co) 是免费的云平台,支持部署静态网站以及 Serverless 函数。Serverless 是近几年比较火的概念,简单去理解就是你只需要去实现具体的业务逻辑,而与最终…

python 小爱音箱集成_python控制小爱音箱自定义设备开关_修仙教程_小爱同学

send send_to_login(ipport,cookie,start_time,end_time)def play(): name info,播放 message_json {"action":"play","media":"app_ios"} path mediaplayer method player_play_operation send.sned_to_cmd(message_json,path,me…

Spring MVC和REST中@RestController和@Controller注释之间的区别

Spring MVC中的RestController注释不过是Controller和ResponseBody注释的组合。 它已添加到Spring 4.0中,以简化在Spring框架中RESTful Web Services的开发。 如果您熟悉REST Web服务,您就会知道Web应用程序与REST API之间的根本区别在于,Web…

java patriciatrie_明明白白以太坊Merkle Patricia Trie

在以太坊数据结构中,Merkle Patricia Trie始终是个绕不过去的坎,世界状态,交易,交易收据等都是以这种树的形式存储在区块链数据库中,并将树root hash保存在区块头里。可以说不弄懂这种树的原理就没有办法真正明白以太坊…

python打开串口失败_python 如何防止串口通信失败?

python 对串口的操作我用的是“线程轮寻”方式。就是打开串口后,启动一个线程来监听串口数据的进入,有数据时,就做数据的处理(也可以发送一个事件,并携带接收到的数据)。我没有用到串口处理太深的东西。客户的原程序不能给你&…

java 调用scala 类_如何使用java类加载器调用带参数的scala函数?

我正在寻找一些将scala jar加载到java类加载器的指导。当我使用java jar文件时,下面的函数对我有效。其中,arr是一个java.net.URL数组,用于我需要加载到类加载器中的所有jar。val classLoader new URLClassLoader(arr, this.getClass().getClassLoader())val clazz classLoad…

python c4.5完整代码_python实现c4.5/Id3自我练习

import numpy as npclass DecisionTree:"""决策树使用方法:- 生成实例: clf DecisionTrees(). 参数mode可选,ID3或C4.5,默认C4.5- 训练,调用fit方法: clf.fit(X,y). X,y均为np.ndarray类型…

jdeveloper_适用于JDeveloper 11gR2的Glassfish插件

jdeveloper众所周知, ADF Essentials是使用Java构建Web应用程序的绝佳框架,它可以自由开发和部署。 您在Glassfish(3.1)服务器上部署ADF Essentials应用程序。 但是,JDeveloper并不带有嵌入式Glassfish服务器&#xff…

php接收get数组数据,来自HTTP的PHP注入GET数据用作PHP数组键值

我想知道在以下场景中是否存在可能的代码注入(或任何其他安全风险,如读取您不应该使用的内存块等等),其中来自HTTP GET的未经过处理的数据用于代码中PHP作为数组的键.这应该将字母转换为字母顺序. a到1,b到2,c到3 …. HTTP GET“字母”变量应该有值字母,但是你可以理解任何东西…

python绘制横向堆积柱状图_Python 堆叠柱状图绘制方法

本文介绍了Python 堆叠柱状图绘制方法,分享给大家,具体如下:>>文件: 堆叠直方图.py>>作者: liu yang>>邮箱: liuyang0001outlook.com>>博客: www.cnblogs.com/liu66blog#!/usr/bin/env python# -*- coding: utf-8 -*…

Spring Cloud教程– Spring Cloud Config Server简介

问题 SpringBoot在通过属性或YAML文件外部化配置属性方面提供了很大的灵活性。 我们还可以使用特定于配置文件的配置文件(例如application.properties , application-dev.properties , application-prod.properties等)分别为每个环…

数字孪生体技术白皮书_基于Flownex的数字孪生体解决方案 系列介绍之二:数据中心应用实例...

致力于数字孪生体技术的研究与发展通过解决方案和工程化应用造福人类来源:数字孪生体实验室原创作者:王永康转载请注明来源和出处导 读《基于Flownex的数字孪生体解决方案》是我们最近完成的系列落地方案之一。该方案适用于热力系统、冷却系统、通风空调…

node php聊天室,最简单的Nodejs聊天室示例

今天群里一个同学找我要一个nodejs聊天室的demo。给他了一个简单的例子,顺便记录下:准备工作(前提是已经装好了nodejs):mkdir nodejs-democd nodejs-demo安装express : npm install express安装socket.io : npm install socket.io安装foreve…

neo4j安装_neo4j 社区版win10 下安装

准备工作:Neo4j下载网址:https://neo4j.com/download-center/#releasesava jdk官网下载:https://www.oracle.com/technetwork/java/javase/downloads/index.html安装 查看是否有用旧版本的java jdk ,如果有请在设置“应用和功能”卸载 旧的ja…

php网站 qq登陆,php写的插件网站接入QQ登录,QQ互联

qq按钮这里的链接是入口,调用你的apiapi_qq.php前端直接链接到此/*** 这个QQ登录简单实用,只要大家看我写的注释会一目了然,请注意看哦。* 带有"todo"这样注释的地方都是要你去改成你自己的逻辑* 这个php怎么进来呢?这是…

spring不自动下载_Spring:自动接线或不自动接线

spring不自动下载自从使用Spring 2.5以来,我从基于XML的应用程序上下文切换到了注释。 尽管我发现那些非常有用且节省大量时间的人,但我始终觉得在灵活性方面我失去了一些东西。 特别是Autowired批注-或标准Inject-在我看来就像新的“新”,增…

php faker 中文,使用faker 生成中文测试数据

https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/zh_CN/Address.php常用的类型都在里面。下面是一个实例。使用了laravel 框架的工厂模式向数据库填充测试数据。$factory->define(App\Models\Customer::class, function ($faker) {$faker Faker\Facto…