场景
通常来说,探针不会引入太重量级的框架,会更多地使用 JDK 原生的接口。然而最近发现,当探针依附在用户应用中时( Spring 应用),有时难免需要使用反射调用用户接口或 Spring 接口,而反射调用需要类实例,使用 Spring 进行依赖注入的框架中,这个实例必须从 Spring Context 中去取。这就造成了一个问题,如何取到 Spring Context 呢,难道一定要在探针中引入 Sping 框架吗?
理论上其实很容易想到——代理,那么具体怎么做呢,不多说,直接看代码。
这里使用的基础代码来自 java探针技术I——如何写一个 java agent
定义 ApplicationContextHolder
首先我们需要一个地方,存放获取到的 Spring Context ,以便在后续的代码中,随时可以取用。
新增 ApplicationContextHolder 类
1 | public class ApplicationContextHolder { |
记住,我们已经没有 Spring 了,一切皆对象
添加代理类
使用提供代理功能的类库,这里使用到的是 bytebuddy ,还有一些其他的如 cglib 。挑自己顺手的就好,目的是在 Spring 启动时,拦截特定的类,获取 Spring Context 。
添加代理类 ContextAdvice
1 | public class ContextAdvice { |
语法就不细说了,可以参考官网。这里是定义在进入特定的方法时,将参数赋值给我们的 ApplicationContextHolder。
这个代理要绑定到哪里呢,根据经验(写过),可以从ApplicationContextAware 下手。
Spring 在启动时会调用所有 ApplicationContextAware 接口,并将 Context 通知到 setApplicationContext方法,其源码如下
1 | ··· |
官方文档中还有一句
ApplicationObjectSupportis a convenience base class for application objects, implementing this interface.
虽然文档中并没有说明这个类具体是从Spring 的哪个版本开始引入的,但是根据一系列谷歌,就是蛮久的啦,应该可以兼容到我读初中时候的 Spring 版本。。
所以,在探针启动时,我们将该代理类绑定到 ApplicationObjectSupport 类的 setApplicationContext 方法上。
修改 StartUp 类
1 | public class StartUp { |
验证
为了方便验证结果,又暗搓搓地使用 spark 暴露了一个 web 接口,这个接口返回 Spring Context 的所有内容。
引入依赖
1 | <dependency> |
在 StartUp 类的末尾添加一行
1 | get("/spring", (req, res) -> ApplicationContextHolder.applicationContextObj); |
好了,打包,编译,运行!
Demo 项目启动完毕之后,访问 http://localhost:4567/spring (spark 默认 4567),可以看到如下返回信息,表示我们成功获取到 Spring Context 啦
1 | org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3d34d211, started on Wed Jul 17 16:09:09 CST 2019 |
啰嗦几句
接下来就是怎么使用的问题了,首先用反射从 Spring Context 中获取 Bean ,再使用反射调用这个 Bean 的方法或者属性。总之,对着 API 盲写代码,没有 Spring ,只有 Object,Class,Method…..是个体力活!