JavaAgent實現(xiàn)http接口發(fā)布方式淺析
需求
公司運維系統(tǒng)想要監(jiān)控服務(wù)是否正常啟動,這些服務(wù)是k8s部署的,運維人員的要求業(yè)務(wù)服務(wù)提供一個http接口用于監(jiān)控服務(wù)健康監(jiān)測,要求所有的接口請求的URL,參數(shù)等都是相同的,這么做的目的是不需要通過規(guī)范來約束開發(fā)人員去開一個服務(wù)健康監(jiān)測的接口。
使用服務(wù)接口來檢測服務(wù)我覺得相比較監(jiān)控進程啟動,端口監(jiān)聽等方式更準確一些。所以,為了滿足運維同學的要求,起初想到的方案是提供一個jar,專門集成到項目中用于發(fā)布監(jiān)控接口,但是想了一下,這么做需要涉及到服務(wù)的改造,我理想的方式是對應(yīng)用無侵入的方式實現(xiàn)。
初步方案
說到對應(yīng)用無入侵,首先想到的就是javaagent技術(shù),此前使用該技術(shù)實現(xiàn)了無入侵增強程序日志的工具,所以對使用javaagent已經(jīng)沒有問題,此時需要考慮的是如何發(fā)布接口了。
基礎(chǔ)技術(shù) JavaAgent
支持的技術(shù) SpringBoot和DubboX發(fā)布的rest服務(wù)
公司服務(wù)大致分為兩類,一個是使用springboot發(fā)布的Spring MVC rest接口,另一種是基于DubboX發(fā)布的rest接口,因為公司在向服務(wù)網(wǎng)格轉(zhuǎn),所以按要求是去dubbo化的,沒辦法還是有其他小組由于一些其他原因沒有或者說短期內(nèi)不想進行服務(wù)改造的項目,這些項目比較老,不是springboot的,是使用spring+DubboX發(fā)布的rest服務(wù)。所以這個agent要至少能支持這兩種技術(shù)。
支持SpringBoot
想要支持SpringBoot很簡單,因為SpringBoot支持自動裝配,所以,我要寫一個spring.factories來進行自動裝配。
支持DubboX
業(yè)務(wù)系統(tǒng)是傳統(tǒng)spring+DubboX實現(xiàn)的,并不支持自動裝配,這是個問題點,還有個問題點就是如何也發(fā)布一個DubboX的rest接口,這兩個問題實際上就需要對SpringBean生命周期和Dubbo接口發(fā)布的流程有一定的了解了,這個一會兒再說。
技術(shù)實現(xiàn)
pom文件依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.6.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.6.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.3.6.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.8.4</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
實現(xiàn)一個JavaAgent
實現(xiàn)一個JavaAgent很容易,以下三步就可以了,這里不細說了。
定義JavaAgent入口
public class PreAgent {
public static void premain(String args, Instrumentation inst) {
System.out.println("輸入?yún)?shù):" + args);
// 通過參數(shù)控制,發(fā)布的接口是DubboX還是SpringMVC
Args.EXPORT_DUBBOX = args;
}
}
Maven打包配置
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<promoteTransitiveDependencies>false</promoteTransitiveDependencies>
<createDependencyReducedPom>true</createDependencyReducedPom>
<minimizeJar>false</minimizeJar>
<createSourcesJar>true</createSourcesJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>com.ruubypay.agent.PreAgent</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
MANIFEST.MF編寫
注:該文件在resource/META-INF/目錄下
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.ruubypay.agent.PreAgent
支持SpringBoot發(fā)布的Http接口
編寫Controller
接口很簡單就發(fā)布一個get接口,響應(yīng)pong即可。
@RestController
public class PingServiceController {
@GetMapping(value = "/agentServer/ping")
public String ping() {
return "pong";
}
}
創(chuàng)建spring.factories
通過這個配置文件可以實現(xiàn)SpringBoot自動裝配,這里不細說SpringBoot自動裝配的原理了,該文件的配置內(nèi)容就是要自動裝配的Bean的全路徑,代碼如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruubypay.config.WebConfiguration
WebConfiguration配置類
這個配置配置類很簡單,@Configuration聲明這是個配置類,@ComponentScan掃描包。
@Configuration
@ComponentScan(value = "com.ruubypay")
public class WebConfiguration {
}
支持DubboX發(fā)布的rest接口
定義API
使用的是DubboX發(fā)布rest接口需要javax.ws.rs包的注解,@Produces({ContentType.APPLICATION_JSON_UTF_8})聲明序列化方式,@Pathrest接口的路徑,@GET聲明為get接口。
@Produces({ContentType.APPLICATION_JSON_UTF_8})
@Path("/agentServer")
public interface IPingService {
/**
* ping接口
* @return
*/
@GET
@Path("/ping")
String ping();
}
編寫API實現(xiàn)類
@Component("IPingService")
public class IPingServiceImpl implements IPingService {
@Override
public String ping() {
return "pong";
}
}
實現(xiàn)發(fā)布Dubbo接口
如何實現(xiàn)發(fā)布接口是實現(xiàn)的難點;首先程序并不支持自動裝配了,我們就要考慮如何獲取到Spring上下文,如果能夠注冊Bean到Spring容器中,如何觸發(fā)發(fā)布Dubbo接口等問題。
Spring上下文獲取及注冊Bean到Spring容器中
觸發(fā)Bean注冊,獲取Spring上下文我們通過Spring的Aware接口可以實現(xiàn),我這里使用的是ApplicationContextAware;注冊Bean到Spring容器中可以使用BeanDefinition先創(chuàng)建Bean然后使用DefaultListableBeanFactory的registerBeanDefinition將BeanDefinition注冊到Spring上下文中。
@Component
public class AgentAware implements ApplicationContextAware {
private static final String DUBBOX = "1";
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
// 如果不是DubboX,不用發(fā)布接口
if (DUBBOX.equals(Args.EXPORT_DUBBOX)) {
// 注冊配置Bean WebConfiguration
webConfiguration();
// 發(fā)布DubboX接口
exportDubboxService();
}
}
public void webConfiguration() {
System.out.println("創(chuàng)建WebConfiguration的bean");
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
// 創(chuàng)建WebConfiguration的bean
BeanDefinition webConfigurationBeanDefinition = new RootBeanDefinition(WebConfiguration.class);
// 注冊到集合beanFactory中
System.out.println("注冊到集合beanFactory中");
listableBeanFactory.registerBeanDefinition(WebConfiguration.class.getName(), webConfigurationBeanDefinition);
}
}
發(fā)布Dubbo接口
通過ApplicationContextAware我們已經(jīng)能夠獲取Spring上下文了,也就是說應(yīng)用程序的Dubbo注冊中心,發(fā)布接口協(xié)議,Dubbo Application等配置都已經(jīng)存在Spring容器中了,我們只要拿過來使用即可,拿過來使用沒問題,我們接下來就需要考慮,如何發(fā)布接口,這需要對Dubbo服務(wù)發(fā)布的流程有一定的了解,這里我不細說了,感興趣的可以自己了解下,或者看我以前發(fā)布的文章;
首先Dubbo接口的Provider端的核心Bean是com.alibaba.dubbo.config.spring.ServiceBean,使用Spring配置文件中的標簽<dubbo:service標簽生成的Bean就是ServiceBean,所以,這里我們只需要創(chuàng)建ServiceBean對象并且初始化對象中的必要數(shù)據(jù),然后調(diào)用ServiceBean#export()方法就可以發(fā)布Dubbo服務(wù)了。
這里需要的對象直接通過依賴查找的方式從Spring容器獲取就可以了 ApplicationConfig,ProtocolConfig,RegistryConfig,IPingService。
public void exportDubboxService() {
try {
System.out.println("開始發(fā)布dubbo接口");
// 獲取ApplicationConfig
ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class);
// 獲取ProtocolConfig
ProtocolConfig protocolConfig = applicationContext.getBean(ProtocolConfig.class);
// 獲取RegistryConfig
RegistryConfig registryConfig = applicationContext.getBean(RegistryConfig.class);
// 獲取IPingService接口
IPingService iPingService = applicationContext.getBean(IPingService.class);
// 創(chuàng)建ServiceBean
ServiceBean<IPingService> serviceBean = new ServiceBean<>();
serviceBean.setApplicationContext(applicationContext);
serviceBean.setInterface("com.ruubypay.api.IPingService");
serviceBean.setApplication(applicationConfig);
serviceBean.setProtocol(protocolConfig);
serviceBean.setRegistry(registryConfig);
serviceBean.setRef(iPingService);
serviceBean.setTimeout(12000);
serviceBean.setVersion("1.0.0");
serviceBean.setOwner("rubby");
// 發(fā)布dubbo接口
serviceBean.export();
System.out.println("dubbo接口發(fā)布完畢");
} catch (Exception e) {
e.printStackTrace();
}
}使用方式
- DubboX: java -javaagent:ruubypay-ping-agent.jar=1 -jar 服務(wù)jar包
- springboot的http接口:java -javaagent:ruubypay-ping-agent.jar -jar 服務(wù)jar包
總結(jié)
這個工具實現(xiàn)起來不復雜,總也就六個類和一個接口,但其實實現(xiàn)其能力所涉及的支持還是比較考驗對框架的理解的,比如Spring生命周期,DubboX發(fā)布接口的流程以及實現(xiàn)一個最簡單的JavaAgent的方式。
到此這篇關(guān)于JavaAgent實現(xiàn)http接口發(fā)布方式淺析的文章就介紹到這了,更多相關(guān)JavaAgent http接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java聯(lián)調(diào)生成測試數(shù)據(jù)工具類方式
這篇文章主要介紹了java聯(lián)調(diào)生成測試數(shù)據(jù)工具類方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
關(guān)于java.lang.NumberFormatException: null的問題及解決
這篇文章主要介紹了關(guān)于java.lang.NumberFormatException: null的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
spring定時器定時任務(wù)到時間未執(zhí)行問題的解決
這篇文章主要介紹了spring定時器定時任務(wù)到時間未執(zhí)行問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java 中 Date 與 Calendar 之間的編輯與轉(zhuǎn)換實例詳解
這篇文章主要介紹了Java 中 Date 與 Calendar 之間的編輯與轉(zhuǎn)換 ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-07-07
詳解spring開發(fā)_JDBC操作MySQL數(shù)據(jù)庫
本篇文章主要介紹了spring開發(fā)_JDBC操作MySQL數(shù)據(jù)庫,具有一定的參考價值,有興趣的可以了解一下。2016-12-12
Java中的clone方法詳解_動力節(jié)點Java學院整理
clone顧名思義就是復制, 在Java語言中, clone方法被對象調(diào)用,所以會復制對象。下面通過本文給大家介紹java中的clone方法,感興趣的朋友一起看看吧2017-06-06
java+vue實現(xiàn)添加單選題、多選題到題庫功能
這篇文章主要為大家詳細介紹了java+vue實現(xiàn)添加單選題、多選題到題庫功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04
AsyncHttpClient IOExceptionFilter異常過濾器
這篇文章主要為大家介紹了AsyncHttpClient IOExceptionFilter異常過濾器代碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
Spring中ClassPathXmlApplicationContext類的使用詳解
這篇文章主要介紹了Spring中ClassPathXmlApplicationContext類的使用詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

