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

java web源代码

与其他系统进行交互时,大多数Java Web应用程序都使用标准Java接口。 诸如Web页面或REST服务器之类的基于HTTP的服务是使用接口javax.servlet.Servlet来实现的。 使用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

java web源代码

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

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

相关文章

反射与泛型 java_Java的反射 和 泛型的一个问题

aluckdog模板方法在编译过程中类型擦除:update(T,UpdateOperations)退化成update(Object,UpdateOperations);update(Query,UpdateOperations)退化成update(Query,UpdateOperations);morphia的update方法不光是一个模板方法,还是一…

迭代器原理图

原理图&#xff1a; Iterator<String> it Collection.iterator();it 是集合Collection的视图&#xff0c;迭代器其实就是视图&#xff08;类似数据库的 view&#xff09;&#xff0c;可以理解为一张单列多行的表&#xff0c;如下图所示&#xff1a; 有人理解成单行多…

neo4j python 算法_图论与图学习(二):图算法

选自towardsdatascience作者&#xff1a;Mal Fabien机器之心编译参与&#xff1a;熊猫图(graph)近来正逐渐变成机器学习的一大核心领域&#xff0c;比如你可以通过预测潜在的连接来理解社交网络的结构、检测欺诈、理解汽车租赁服务的消费者行为或进行实时推荐。近日&#xff0c…

cobol和java_现代化历险:策略+将COBOL转换为Java的示例

cobol和java在Keyhole Software&#xff0c;我们在很大程度上是一家现代化公司。 我们有一些顾问&#xff0c;专门研究将旧的代码迁移到新的&#xff0c;翻新的残旧代码库&#xff0c;并为大多数已经被供应商锁定的企业设计更光明的未来。 作为这些经验的一个有趣的副作用&…

c# mysql sdr_C#结合数据库实现验证识别ID卡内容的方法

本文所述实例为C#结合数据库&#xff0c;来验证所识别的ID卡内容&#xff0c;通过本实例代码&#xff0c;用户可以轻松实现对ID卡会员信息的验证。该实例代码可实现读取数据库&#xff0c;进而逐步实现数据库连接&#xff0c;数据库读取&#xff0c;ID卡读取&#xff0c;ID卡信…

递归算法 流程图_什么是算法?如何学习算法?算法入门的学习路径

什么是算法&#xff1f;有一个很著名的公式 “程序数据结构算法”。曾经跟朋友吃饭的时候我问他什么是算法&#xff0c;他说算法嘛&#xff0c;就是一套方法&#xff0c;需要的时候拿过来&#xff0c;套用就可以&#xff0c;我吐槽他&#xff0c;他说的是小学数学题的算法&…

java webmethod 参数_java详解Spring接收web请求参数的方式

本篇文章给大家带来的内容是java详解Spring接收web请求参数的方式 。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你们有所帮助。1 查询参数请求格式&#xff1a;url?参数1值1&参数2值2...同时适用于GET和POST方式spring处理查询参数的方法又…

envoy重试_具有Envoy代理的微服务模式,第二部分:超时和重试

envoy重试该博客是系列文章的一部分&#xff0c;该系列文章更深入地介绍了Envoy Proxy和Istio.io &#xff0c;以及它如何实现一种更优雅的连接和管理微服务的方式。 跟随我christianposta &#xff0c;紧跟这些博客文章的发布。 什么是Envoy代理 &#xff0c;它如何工作&…

python中字符串格式化的形式_Python中format函数字符串格式化入门

格式化在程序开发中非常常见&#xff0c;大家肯定不陌生&#xff0c;Python中也存在多重格式化方式&#xff0c;format函数就是其中一种。函数原型format(value[, format_spec])参数意义value&#xff1a; 需要被格式化的字符串format_spec&#xff1a; 格式化的格式函数定义与…

Java的超类/基类Object

文章目录简介主要方法equals简介 所有的类都继承了 Object&#xff0c;即 Object 是所有类的父类&#xff0c;所以所有的 Java 类都继承了 Object 的所有方法。 比如你写一个类如下&#xff1a; public class Question { ... }实际上这个类是继承自 Object 的&#xff0c;默…

网络研讨室_即将举行的网络研讨会:调试生产中Java的5种最佳实践

网络研讨室您的团队是否花费超过10&#xff05;的时间在生产中调试Java&#xff1f; 将新代码部署到生产中是一项艰巨的任务。 在您的本地环境中起作用的东西在生产中的作用并不相同&#xff0c;您可以通过用户来了解。 不理想吧&#xff1f; 生产中的调试是一个关键要素&…

window部署python项目_Django在Window下的部署

转载 : codingsoho.com前言本文主要介绍利用apache去部署Django项目&#xff0c;所有步骤均在本机Window7和阿里云验证通过。配置本例的基本配置如下&#xff1a;工作目录&#xff1a; C:/virtualenv/zakkabag项目名称&#xff1a; zakkabag最终的安装版本如下&#xff0c;后面…

chrome gwt1.7_快速提示:使用Chrome开发工具调试GWT应用程序

chrome gwt1.7调试是软件开发的重要方面。 拥有正确的工具可以节省大量时间和头痛。 在GWT Super Dev模式之前&#xff0c;经典的Dev模式允许使用JVM调试。 开发人员可以在其IDE中设置断点&#xff0c;并使用调试模式来跟踪错误和错误。 现在&#xff0c;在超级开发模式下&…

java事件编程_java基础 ---Swing事件编程

java基础 ---Swing事件编程GUI的设计就剩个菜单组建&#xff0c;这个组件也是想当于容器套容器&#xff0c;在设计方面没有什么难度&#xff0c;主要是一些事件的响应。还有另一种事件的监听方式&#xff0c;也就是适配器监听方法。1、菜单要设计一个菜单那么有三大组件是不可或…

python3鄙视python2_Python3 正在毁灭 Python的原因分析

Python 3毫不费力地成为发生在Python社区里最糟糕的事。我还记得第一次使用Python的时候&#xff0c;我还在花大量时间在C这块上&#xff0c;而Python就像是我的一次开光。我可以打开文本编辑器用几秒钟或者几分钟写出一个可以工作的程序&#xff0c;而不是用几小时或几天。我记…

Java如何加载类的呢?

JVM加载类 首先查看内存中是否存在该类&#xff08;内存中所有类都是以Class的实例对象存在&#xff09;&#xff0c;若不存在则会通过环境变量中的路径值在电脑或者其它设备的硬盘中找到该类&#xff08;即.class文件&#xff09;&#xff0c;然后JVM会将其读取到内存中&…

brew卸载jenv_使用brew,cask和jenv在MacOSX上设置多个Java JRE / JDK

brew卸载jenv昨天在Java9的Jigsaw HackTheTower事件中&#xff0c;我意识到我需要加强我的游戏并改善我现有的机制&#xff0c;以在我的机器上维护几个不同的JDK。 我曾经手动下载jdk&#xff0c;或使用brew cask来安装它们&#xff0c;我会在我的〜/ bash_profile中设置bash …

java过滤器api_springboot集成过滤器

封装自定义接口filter包含两个方法,第一个过滤的路径数组,第二个为过滤器执行的顺序.spring boot 会按照order值的大小&#xff0c;从小到大的顺序来依次过滤。package com.theeternity.common.baseFilter;import javax.servlet.Filter;/*** program: ApiBoot* description: 封…

Servlet 运行原理

文章目录Servlet 如何运行演示 Servlet 运行原理Servlet 如何运行 用户向浏览器地址栏输入&#xff1a;http://ip:port/helloweb/sayHello?namezs 浏览器使用 ip:port(端口号)连接服务器 浏览器将请求数据按照 http 协议打成一个数据包(请求数据包)发送给服务器 请求数据包…

python dicom 器官分割_python+opencv阈值分割

37 #获取像素点的最大值和最小值38 arr_temp np.reshape(img_arr,(lens,))39 max_val max(arr_temp)40 min_val min(arr_temp)41 #图像归一化42 img_arr (img_arr-min_val)/(max_val-min_val)43 #绘制图像并保存44 #保存图片时去掉周围白边45 plt.axis(off)46 fig plt.gcf()47…