首頁 > 軟體

JavaAgent實現http介面釋出方式淺析

2023-03-02 18:01:19

需求

公司運維繫統想要監控服務是否正常啟動,這些服務是k8s部署的,運維人員的要求業務服務提供一個http介面用於監控服務健康監測,要求所有的介面請求的URL,引數等都是相同的,這麼做的目的是不需要通過規範來約束開發人員去開一個服務健康監測的介面。

使用服務介面來檢測服務我覺得相比較監控程序啟動,埠監聽等方式更準確一些。所以,為了滿足運維同學的要求,起初想到的方案是提供一個jar,專門整合到專案中用於釋出監控介面,但是想了一下,這麼做需要涉及到服務的改造,我理想的方式是對應用無侵入的方式實現。

初步方案

說到對應用無入侵,首先想到的就是javaagent技術,此前使用該技術實現了無入侵增強程式紀錄檔的工具,所以對使用javaagent已經沒有問題,此時需要考慮的是如何釋出介面了。

基礎技術 JavaAgent

支援的技術 SpringBoot和DubboX釋出的rest服務

公司服務大致分為兩類,一個是使用springboot釋出的Spring MVC rest介面,另一種是基於DubboX釋出的rest介面,因為公司在向服務網格轉,所以按要求是去dubbo化的,沒辦法還是有其他小組由於一些其他原因沒有或者說短期內不想進行服務改造的專案,這些專案比較老,不是springboot的,是使用spring+DubboX釋出的rest服務。所以這個agent要至少能支援這兩種技術。

支援SpringBoot

想要支援SpringBoot很簡單,因為SpringBoot支援自動裝配,所以,我要寫一個spring.factories來進行自動裝配。

支援DubboX

業務系統是傳統spring+DubboX實現的,並不支援自動裝配,這是個問題點,還有個問題點就是如何也釋出一個DubboX的rest介面,這兩個問題實際上就需要對SpringBean生命週期和Dubbo介面釋出的流程有一定的瞭解了,這個一會兒再說。

技術實現

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>

實現一個JavaAgent

實現一個JavaAgent很容易,以下三步就可以了,這裡不細說了。

定義JavaAgent入口

public class PreAgent {
    public static void premain(String args, Instrumentation inst) {
        System.out.println("輸入引數:" + args);
        // 通過引數控制,釋出的介面是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釋出的Http介面

編寫Controller

介面很簡單就釋出一個get介面,響應pong即可。

@RestController
public class PingServiceController {
    @GetMapping(value = "/agentServer/ping")
    public String ping() {
        return "pong";
    }
}

建立spring.factories

通過這個組態檔可以實現SpringBoot自動裝配,這裡不細說SpringBoot自動裝配的原理了,該檔案的設定內容就是要自動裝配的Bean的全路徑,程式碼如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.ruubypay.config.WebConfiguration

WebConfiguration設定類

這個設定設定類很簡單,@Configuration宣告這是個設定類,@ComponentScan掃描包。

@Configuration
@ComponentScan(value = "com.ruubypay")
public class WebConfiguration {
}

支援DubboX釋出的rest介面

定義API

使用的是DubboX釋出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實現類

@Component("IPingService")
public class IPingServiceImpl implements IPingService {
    @Override
    public String ping() {
        return "pong";
    }
}

實現釋出Dubbo介面

如何實現釋出介面是實現的難點;首先程式並不支援自動裝配了,我們就要考慮如何獲取到Spring上下文,如果能夠註冊BeanSpring容器中,如何觸發釋出Dubbo介面等問題。

Spring上下文獲取及註冊Bean到Spring容器中

觸發Bean註冊,獲取Spring上下文我們通過Spring的Aware介面可以實現,我這裡使用的是ApplicationContextAware;註冊BeanSpring容器中可以使用BeanDefinition先建立Bean然後使用DefaultListableBeanFactoryregisterBeanDefinitionBeanDefinition註冊到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,不用釋出介面
        if (DUBBOX.equals(Args.EXPORT_DUBBOX)) {
            // 註冊設定Bean WebConfiguration
            webConfiguration();
            // 釋出DubboX介面
            exportDubboxService();
        }
    }
    public void webConfiguration() {
        System.out.println("建立WebConfiguration的bean");
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
        // 建立WebConfiguration的bean
        BeanDefinition webConfigurationBeanDefinition = new RootBeanDefinition(WebConfiguration.class);
        // 註冊到集合beanFactory中
        System.out.println("註冊到集合beanFactory中");
        listableBeanFactory.registerBeanDefinition(WebConfiguration.class.getName(), webConfigurationBeanDefinition);
    }
}

釋出Dubbo介面

通過ApplicationContextAware我們已經能夠獲取Spring上下文了,也就是說應用程式的Dubbo註冊中心,釋出介面協定,Dubbo Application等設定都已經存在Spring容器中了,我們只要拿過來使用即可,拿過來使用沒問題,我們接下來就需要考慮,如何釋出介面,這需要對Dubbo服務釋出的流程有一定的瞭解,這裡我不細說了,感興趣的可以自己瞭解下,或者看我以前釋出的文章;

首先Dubbo介面的Provider端的核心Bean是com.alibaba.dubbo.config.spring.ServiceBean,使用Spring組態檔中的標籤<dubbo:service標籤生成的Bean就是ServiceBean,所以,這裡我們只需要建立ServiceBean物件並且初始化物件中的必要資料,然後呼叫ServiceBean#export()方法就可以釋出Dubbo服務了。

這裡需要的物件直接通過依賴查詢的方式從Spring容器獲取就可以了 ApplicationConfig,ProtocolConfig,RegistryConfig,IPingService

    public void exportDubboxService() {
        try {
            System.out.println("開始釋出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);
            // 建立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");
            // 釋出dubbo介面
            serviceBean.export();
            System.out.println("dubbo介面釋出完畢");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用方式

  • DubboX: java -javaagent:ruubypay-ping-agent.jar=1 -jar 服務jar包
  • springboot的http介面:java -javaagent:ruubypay-ping-agent.jar -jar 服務jar包

總結

這個工具實現起來不復雜,總也就六個類和一個介面,但其實實現其能力所涉及的支援還是比較考驗對框架的理解的,比如Spring生命週期,DubboX釋出介面的流程以及實現一個最簡單的JavaAgent的方式。

到此這篇關於JavaAgent實現http介面釋出方式淺析的文章就介紹到這了,更多相關JavaAgent http介面內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com