场景
通常来说,探针不会引入太重量级的框架,会更多地使用 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 | ··· |
官方文档中还有一句
ApplicationObjectSupport
is 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
…..是个体力活!