Repository: skiptests/astraea
Pull request #131 (2021-11-29 16:17:15)

Make MBeanClient be interface
在測試#123的時候發現沒有抽象化蠻不方便的，因此給`MBeanClient`抽出介面


Patch:
diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java
index 26ac4cd1..3e96fcaf 100644
--- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java
+++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java
@@ -162,10 +162,10 @@ public class MetricExplorer {
         .collect(Collectors.toList());
   }
 
-  public static void main(String[] args) throws Exception {
+  public static void main(String[] args) {
     var arguments = ArgumentUtil.parseArgument(new Argument(), args);
 
-    try (MBeanClient mBeanClient = new MBeanClient(arguments.jmxServer)) {
+    try (var mBeanClient = MBeanClient.of(arguments.jmxServer)) {
       execute(mBeanClient, arguments);
     }
   }
diff --git a/app/src/main/java/org/astraea/metrics/jmx/MBeanClient.java b/app/src/main/java/org/astraea/metrics/jmx/MBeanClient.java
index 4dbd1b1f..77b2fc8c 100644
--- a/app/src/main/java/org/astraea/metrics/jmx/MBeanClient.java
+++ b/app/src/main/java/org/astraea/metrics/jmx/MBeanClient.java
@@ -1,11 +1,10 @@
 package org.astraea.metrics.jmx;
 
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.*;
-import java.util.stream.Collectors;
-import javax.management.*;
-import javax.management.remote.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import javax.management.ObjectName;
+import javax.management.remote.JMXServiceURL;
 
 /**
  * A MBeanClient used to retrieve mbean value from remote Jmx server.
@@ -19,51 +18,22 @@ import javax.management.remote.*;
  *   System.out.println(bean.getAttributes());
  * }</pre>
  */
-public class MBeanClient implements AutoCloseable {
+public interface MBeanClient extends AutoCloseable {
 
-  private final JMXServiceURL jmxServiceURL;
-  private final JMXConnector jmxConnector;
-  private final MBeanServerConnection mBeanServerConnection;
-  private boolean isClosed;
-
-  public MBeanClient(JMXServiceURL jmxServiceURL) throws IOException {
-    this.jmxServiceURL = Objects.requireNonNull(jmxServiceURL);
-    this.jmxConnector = JMXConnectorFactory.connect(jmxServiceURL);
-    this.isClosed = false;
-    this.mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+  static MBeanClient of(JMXServiceURL url) {
+    return new MBeanClientImpl(url);
   }
 
   /**
    * Fetch all attributes of target mbean.
    *
-   * <p>Note that when exception is raised during the attribute fetching process, the exactly
+   * <p>Note that when exception is raised during the attribute fetching process, the exact
    * exception will be placed into the attribute field.
    *
    * @param beanQuery the non-pattern BeanQuery
    * @return A {@link BeanObject} contain all attributes if target resolved successfully.
    */
-  public BeanObject queryBean(BeanQuery beanQuery) {
-    ensureConnected();
-    try {
-      // ask for MBeanInfo
-      MBeanInfo mBeanInfo = mBeanServerConnection.getMBeanInfo(beanQuery.objectName());
-
-      // create a list builder all available attributes name
-      List<String> attributeName =
-          Arrays.stream(mBeanInfo.getAttributes())
-              .map(MBeanFeatureInfo::getName)
-              .collect(Collectors.toList());
-
-      // query the result
-      return queryBean(beanQuery, attributeName);
-    } catch (ReflectionException | IntrospectionException e) {
-      throw new RuntimeException(e);
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    } catch (InstanceNotFoundException e) {
-      throw new NoSuchElementException(e.getMessage());
-    }
-  }
+  BeanObject queryBean(BeanQuery beanQuery);
 
   /**
    * Fetch given attributes of target mbean
@@ -75,65 +45,7 @@ public class MBeanClient implements AutoCloseable {
    * @param attributeNameCollection a list of attribute you want to retrieve
    * @return A {@link BeanObject} contain given specific attributes if target resolved successfully.
    */
-  public BeanObject queryBean(BeanQuery beanQuery, Collection<String> attributeNameCollection) {
-    ensureConnected();
-    try {
-
-      // fetch attribute value from mbean server
-      String[] attributeNameArray = attributeNameCollection.toArray(new String[0]);
-      List<Attribute> attributeList =
-          mBeanServerConnection.getAttributes(beanQuery.objectName(), attributeNameArray).asList();
-
-      // collect attribute name & value into a map
-      Map<String, Object> attributes = new HashMap<>();
-      for (Attribute attribute : attributeList) {
-        attributes.put(attribute.getName(), attribute.getValue());
-      }
-
-      // according to the javadoc of MBeanServerConnection#getAttributes, the API will
-      // ignore any
-      // error occurring during the fetch process (for example, attribute not exists). Below code
-      // check for such condition and try to figure out what exactly the error is. put it into
-      // attributes return result.
-      Set<String> notResolvedAttributes =
-          Arrays.stream(attributeNameArray)
-              .filter(str -> !attributes.containsKey(str))
-              .collect(Collectors.toSet());
-      for (String attributeName : notResolvedAttributes) {
-        attributes.put(attributeName, fetchAttributeObjectOrException(beanQuery, attributeName));
-      }
-
-      // collect result, and build a new BeanObject as return result
-      return new BeanObject(beanQuery.domainName(), beanQuery.properties(), attributes);
-
-    } catch (ReflectionException e) {
-      throw new RuntimeException(e);
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    } catch (InstanceNotFoundException e) {
-      throw new NoSuchElementException(e.getMessage());
-    }
-  }
-
-  private Object fetchAttributeObjectOrException(BeanQuery beanQuery, String attributeName) {
-    // It is possible to trigger some unexpected runtime exception during the following call.
-    // For example, on my machine when I try to get attribute "BootClassPath" from
-    // "java.lang:type=Runtime".
-    // I will get a {@link java.lang.UnsupportedOperationException} indicates that "Boot class path
-    // mechanism is not supported". Those attribute actually exists, but I cannot retrieve those
-    // attribute value. Doing so I get that error.
-    //
-    // Instead of blinding that attribute from the library user, I decided to put the
-    // exception
-    // into their result.
-    try {
-      return mBeanServerConnection.getAttribute(beanQuery.objectName(), attributeName);
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    } catch (Exception e) {
-      return e;
-    }
-  }
+  BeanObject queryBean(BeanQuery beanQuery, Collection<String> attributeNameCollection);
 
   /**
    * Query mBeans by pattern.
@@ -147,19 +59,7 @@ public class MBeanClient implements AutoCloseable {
    * @param beanQuery the pattern to query
    * @return A {@link Set} of {@link BeanObject}, all BeanObject has its own attributes resolved.
    */
-  public Collection<BeanObject> queryBeans(BeanQuery beanQuery) {
-    ensureConnected();
-    try {
-      return mBeanServerConnection.queryMBeans(beanQuery.objectName(), null).stream()
-          .map(ObjectInstance::getObjectName)
-          .map(BeanQuery::fromObjectName)
-          .map(this::queryBean)
-          .collect(Collectors.toSet());
-
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
+  Collection<BeanObject> queryBeans(BeanQuery beanQuery);
 
   /**
    * Returns the list of domains in which any MBean is currently registered.
@@ -168,31 +68,14 @@ public class MBeanClient implements AutoCloseable {
    *
    * @return a {@link List} of domain name {@link String}
    */
-  public List<String> listDomains() {
-    try {
-      return Arrays.asList(mBeanServerConnection.getDomains());
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
+  List<String> listDomains();
 
-  /**
-   * The JMX URL of current MBeanClient instance
-   *
-   * @return the origin JMX URL used to initiate MBeanClient
-   */
-  public JMXServiceURL getAddress() {
-    return jmxServiceURL;
-  }
+  /** @return the host address of jmx server */
+  String host();
 
-  private void ensureConnected() {
-    if (isClosed) throw new IllegalStateException("MBean client is closed");
-  }
+  /** @return the port listened by jmx server */
+  int port();
 
   @Override
-  public void close() throws Exception {
-    ensureConnected();
-    this.isClosed = true;
-    this.jmxConnector.close();
-  }
+  void close();
 }
diff --git a/app/src/main/java/org/astraea/metrics/jmx/MBeanClientImpl.java b/app/src/main/java/org/astraea/metrics/jmx/MBeanClientImpl.java
new file mode 100644
index 00000000..ac088fc1
--- /dev/null
+++ b/app/src/main/java/org/astraea/metrics/jmx/MBeanClientImpl.java
@@ -0,0 +1,168 @@
+package org.astraea.metrics.jmx;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.*;
+import java.util.stream.Collectors;
+import javax.management.*;
+import javax.management.remote.*;
+import org.astraea.Utils;
+
+class MBeanClientImpl implements MBeanClient {
+
+  private final JMXServiceURL jmxServiceURL;
+  private final JMXConnector jmxConnector;
+  private final MBeanServerConnection mBeanServerConnection;
+  private boolean isClosed;
+
+  MBeanClientImpl(JMXServiceURL jmxServiceURL) {
+    try {
+      this.jmxServiceURL = Objects.requireNonNull(jmxServiceURL);
+      this.jmxConnector = JMXConnectorFactory.connect(jmxServiceURL);
+      this.isClosed = false;
+      this.mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  @Override
+  public BeanObject queryBean(BeanQuery beanQuery) {
+    ensureConnected();
+    try {
+      // ask for MBeanInfo
+      MBeanInfo mBeanInfo = mBeanServerConnection.getMBeanInfo(beanQuery.objectName());
+
+      // create a list builder all available attributes name
+      List<String> attributeName =
+          Arrays.stream(mBeanInfo.getAttributes())
+              .map(MBeanFeatureInfo::getName)
+              .collect(Collectors.toList());
+
+      // query the result
+      return queryBean(beanQuery, attributeName);
+    } catch (ReflectionException | IntrospectionException e) {
+      throw new RuntimeException(e);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    } catch (InstanceNotFoundException e) {
+      throw new NoSuchElementException(e.getMessage());
+    }
+  }
+
+  @Override
+  public BeanObject queryBean(BeanQuery beanQuery, Collection<String> attributeNameCollection) {
+    ensureConnected();
+    try {
+
+      // fetch attribute value from mbean server
+      String[] attributeNameArray = attributeNameCollection.toArray(new String[0]);
+      List<Attribute> attributeList =
+          mBeanServerConnection.getAttributes(beanQuery.objectName(), attributeNameArray).asList();
+
+      // collect attribute name & value into a map
+      Map<String, Object> attributes = new HashMap<>();
+      for (Attribute attribute : attributeList) {
+        attributes.put(attribute.getName(), attribute.getValue());
+      }
+
+      // according to the javadoc of MBeanServerConnection#getAttributes, the API will
+      // ignore any
+      // error occurring during the fetch process (for example, attribute not exists). Below code
+      // check for such condition and try to figure out what exactly the error is. put it into
+      // attributes return result.
+      Set<String> notResolvedAttributes =
+          Arrays.stream(attributeNameArray)
+              .filter(str -> !attributes.containsKey(str))
+              .collect(Collectors.toSet());
+      for (String attributeName : notResolvedAttributes) {
+        attributes.put(attributeName, fetchAttributeObjectOrException(beanQuery, attributeName));
+      }
+
+      // collect result, and build a new BeanObject as return result
+      return new BeanObject(beanQuery.domainName(), beanQuery.properties(), attributes);
+
+    } catch (ReflectionException e) {
+      throw new RuntimeException(e);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    } catch (InstanceNotFoundException e) {
+      throw new NoSuchElementException(e.getMessage());
+    }
+  }
+
+  private Object fetchAttributeObjectOrException(BeanQuery beanQuery, String attributeName) {
+    // It is possible to trigger some unexpected runtime exception during the following call.
+    // For example, on my machine when I try to get attribute "BootClassPath" from
+    // "java.lang:type=Runtime".
+    // I will get a {@link java.lang.UnsupportedOperationException} indicates that "Boot class path
+    // mechanism is not supported". Those attribute actually exists, but I cannot retrieve those
+    // attribute value. Doing so I get that error.
+    //
+    // Instead of blinding that attribute from the library user, I decided to put the
+    // exception
+    // into their result.
+    try {
+      return mBeanServerConnection.getAttribute(beanQuery.objectName(), attributeName);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    } catch (Exception e) {
+      return e;
+    }
+  }
+
+  @Override
+  public Collection<BeanObject> queryBeans(BeanQuery beanQuery) {
+    ensureConnected();
+    try {
+      return mBeanServerConnection.queryMBeans(beanQuery.objectName(), null).stream()
+          .map(ObjectInstance::getObjectName)
+          .map(BeanQuery::fromObjectName)
+          .map(this::queryBean)
+          .collect(Collectors.toSet());
+
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  @Override
+  public List<String> listDomains() {
+    try {
+      return Arrays.asList(mBeanServerConnection.getDomains());
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  /**
+   * The JMX URL of current MBeanClient instance
+   *
+   * @return the origin JMX URL used to initiate MBeanClient
+   */
+  public JMXServiceURL getAddress() {
+    return jmxServiceURL;
+  }
+
+  @Override
+  public String host() {
+    return jmxServiceURL.getHost();
+  }
+
+  @Override
+  public int port() {
+    return jmxServiceURL.getPort();
+  }
+
+  private void ensureConnected() {
+    if (isClosed) throw new IllegalStateException("MBean client is closed");
+  }
+
+  @Override
+  public void close() {
+    if (!isClosed) {
+      isClosed = true;
+      Utils.close(jmxConnector);
+    }
+  }
+}
diff --git a/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeLoadClient.java b/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeLoadClient.java
index 009d5229..5afe5c2c 100644
--- a/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeLoadClient.java
+++ b/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeLoadClient.java
@@ -5,8 +5,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.concurrent.TimeUnit;
+import org.astraea.Utils;
 import org.astraea.concurrent.ThreadPool;
-import org.astraea.metrics.jmx.MBeanClient;
 
 public class NodeLoadClient implements ThreadPool.Executor {
 
@@ -42,12 +42,7 @@ public class NodeLoadClient implements ThreadPool.Executor {
   public void close() {
     for (NodeMetadata nodeMetadata : nodeMetadataCollection) {
       NodeMetrics nodeMetrics = nodeMetadata.getNodeMetrics();
-      MBeanClient mBeanClient = nodeMetrics.getKafkaMetricClient();
-      try {
-        mBeanClient.close();
-      } catch (Exception e) {
-        e.printStackTrace();
-      }
+      Utils.close(nodeMetrics.getKafkaMetricClient());
     }
   }
 
diff --git a/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeMetrics.java b/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeMetrics.java
index 92c857cd..df3c1e3b 100644
--- a/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeMetrics.java
+++ b/app/src/main/java/org/astraea/partitioner/nodeLoadMetric/NodeMetrics.java
@@ -28,7 +28,7 @@ public class NodeMetrics {
     if (Pattern.compile("^service:").matcher(address).find())
       serviceURL = new JMXServiceURL(address);
     else serviceURL = new JMXServiceURL(createJmxUrl(address));
-    mBeanClient = new MBeanClient(serviceURL);
+    mBeanClient = MBeanClient.of(serviceURL);
     metricsValues = new HashMap();
   }
 


Test patch:
diff --git a/app/src/test/java/org/astraea/metrics/jmx/MBeanClientTest.java b/app/src/test/java/org/astraea/metrics/jmx/MBeanClientTest.java
index 3628d685..eab5cdff 100644
--- a/app/src/test/java/org/astraea/metrics/jmx/MBeanClientTest.java
+++ b/app/src/test/java/org/astraea/metrics/jmx/MBeanClientTest.java
@@ -17,7 +17,7 @@ class MBeanClientTest {
 
   private MBeanServer mBeanServer;
   private JMXConnectorServer jmxServer;
-  private Map<ObjectName, Object> registeredBeans = new HashMap<>();
+  private final Map<ObjectName, Object> registeredBeans = new HashMap<>();
 
   private void register(ObjectName name, Object mBean) {
     registeredBeans.put(name, mBean);
@@ -60,13 +60,13 @@ class MBeanClientTest {
   }
 
   @Test
-  void testFetchAttributes() throws Exception {
+  void testFetchAttributes() {
     // arrange
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery beanQuery = BeanQuery.builder("java.lang").property("type", "Memory").build();
 
       // act
-      BeanObject beanObject = sut.queryBean(beanQuery);
+      BeanObject beanObject = client.queryBean(beanQuery);
 
       // assert
       assertTrue(beanObject.getProperties().containsKey("type"));
@@ -76,9 +76,9 @@ class MBeanClientTest {
   }
 
   @Test
-  void testFetchMbeanWithMultipleProperties() throws Exception {
+  void testFetchMbeanWithMultipleProperties() {
     // arrange
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery query1 =
           BeanQuery.builder(
                   "java.lang", Map.of("type", "MemoryManager", "name", "CodeCacheManager"))
@@ -90,8 +90,8 @@ class MBeanClientTest {
               .build();
 
       // act
-      BeanObject beanObject1 = sut.queryBean(query1);
-      BeanObject beanObject2 = sut.queryBean(query2);
+      BeanObject beanObject1 = client.queryBean(query1);
+      BeanObject beanObject2 = client.queryBean(query2);
 
       // assert
       assertTrue(beanObject1.getProperties().containsKey("type"));
@@ -111,14 +111,14 @@ class MBeanClientTest {
   }
 
   @Test
-  void testFetchSelectedAttributes() throws Exception {
+  void testFetchSelectedAttributes() {
     // arrange
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery beanQuery = BeanQuery.builder("java.lang").property("type", "Memory").build();
       List<String> selectedAttribute = List.of("HeapMemoryUsage");
 
       // act
-      BeanObject beanObject = sut.queryBean(beanQuery, selectedAttribute);
+      BeanObject beanObject = client.queryBean(beanQuery, selectedAttribute);
 
       // assert
       assertTrue(beanObject.getProperties().containsKey("type"));
@@ -128,13 +128,13 @@ class MBeanClientTest {
   }
 
   @Test
-  void testQueryBeans() throws Exception {
+  void testQueryBeans() {
     // arrange 1 query beans
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery beanQuery = BeanQuery.builder("java.lang").property("type", "C*").build();
 
       // act 1
-      Collection<BeanObject> beanObjects = sut.queryBeans(beanQuery);
+      Collection<BeanObject> beanObjects = client.queryBeans(beanQuery);
 
       // assert 1
       assertEquals(2, beanObjects.size());
@@ -165,13 +165,13 @@ class MBeanClientTest {
   }
 
   @Test
-  void testQueryNonExistsBeans() throws Exception {
+  void testQueryNonExistsBeans() {
     // arrange
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery beanQuery = BeanQuery.builder("java.lang").property("type", "Something").build();
 
       // act
-      Collection<BeanObject> beanObjects = sut.queryBeans(beanQuery);
+      Collection<BeanObject> beanObjects = client.queryBeans(beanQuery);
 
       // assert
       assertEquals(0, beanObjects.size());
@@ -179,53 +179,54 @@ class MBeanClientTest {
   }
 
   @Test
-  void testFetchNonExistsBeans() throws Exception {
+  void testFetchNonExistsBeans() {
     // arrange
-    try (MBeanClient sut = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery beanQuery = BeanQuery.builder("java.lang").property("type", "Something").build();
 
       // act assert
-      assertThrows(NoSuchElementException.class, () -> sut.queryBean(beanQuery));
+      assertThrows(NoSuchElementException.class, () -> client.queryBean(beanQuery));
       assertThrows(
-          NoSuchElementException.class, () -> sut.queryBean(beanQuery, Collections.emptyList()));
+          NoSuchElementException.class, () -> client.queryBean(beanQuery, Collections.emptyList()));
     }
   }
 
   @Test
-  void testUseClosedClientWillThrowError() throws Exception {
+  void testUseClosedClientWillThrowError() {
     // arrange
-    MBeanClient sut = new MBeanClient(jmxServer.getAddress());
+    var client = MBeanClient.of(jmxServer.getAddress());
     BeanQuery query = BeanQuery.builder("java.lang").property("type", "Memory").build();
 
     // act
-    sut.close();
+    client.close();
 
     // assert
-    assertThrows(IllegalStateException.class, () -> sut.queryBean(query));
-    assertThrows(IllegalStateException.class, () -> sut.queryBean(query, Collections.emptyList()));
-    assertThrows(IllegalStateException.class, () -> sut.queryBeans(query));
+    assertThrows(IllegalStateException.class, () -> client.queryBean(query));
+    assertThrows(
+        IllegalStateException.class, () -> client.queryBean(query, Collections.emptyList()));
+    assertThrows(IllegalStateException.class, () -> client.queryBeans(query));
   }
 
   @Test
-  void testCloseOnceMoreWillThrowError() throws Exception {
+  void testCloseOnceMore() {
     // arrange
-    MBeanClient sut = new MBeanClient(jmxServer.getAddress());
+    var client = MBeanClient.of(jmxServer.getAddress());
 
     // act
-    sut.close();
+    client.close();
 
     // assert
-    assertThrows(IllegalStateException.class, sut::close);
-    assertThrows(IllegalStateException.class, sut::close);
-    assertThrows(IllegalStateException.class, sut::close);
-    assertThrows(IllegalStateException.class, sut::close);
-    assertThrows(IllegalStateException.class, sut::close);
+    client.close();
+    client.close();
+    client.close();
+    client.close();
+    client.close();
   }
 
   @Test
-  void testGetAllMBeans() throws Exception {
+  void testGetAllMBeans() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       Collection<BeanObject> beanObjects = client.queryBeans(BeanQuery.all());
@@ -237,9 +238,9 @@ class MBeanClientTest {
   }
 
   @Test
-  void testGetAllMBeansUnderSpecificDomainName() throws Exception {
+  void testGetAllMBeansUnderSpecificDomainName() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       Collection<BeanObject> beanObjects = client.queryBeans(BeanQuery.all("java.lang"));
@@ -251,9 +252,9 @@ class MBeanClientTest {
   }
 
   @Test
-  void testGetAllMBeansUnderSpecificDomainNamePattern() throws Exception {
+  void testGetAllMBeansUnderSpecificDomainNamePattern() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       Collection<BeanObject> beanObjects = client.queryBeans(BeanQuery.all("java.*"));
@@ -265,9 +266,9 @@ class MBeanClientTest {
   }
 
   @Test
-  void testUsePropertyListPattern() throws Exception {
+  void testUsePropertyListPattern() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
       BeanQuery patternQuery =
           BeanQuery.builder("java.lang").property("type", "*").usePropertyListPattern().build();
 
@@ -314,9 +315,9 @@ class MBeanClientTest {
   }
 
   @Test
-  void testListDomains() throws Exception {
+  void testListDomains() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       List<String> domains = client.listDomains();
@@ -328,15 +329,12 @@ class MBeanClientTest {
   }
 
   @Test
-  void testGetAddress() throws Exception {
+  void testHostAndPort() {
     // arrange
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
-      // act
-      JMXServiceURL address = client.getAddress();
-
-      // assert
-      assertEquals(jmxServer.getAddress(), address);
+      assertEquals(jmxServer.getAddress().getHost(), client.host());
+      assertEquals(jmxServer.getAddress().getPort(), client.port());
     }
   }
 
@@ -350,7 +348,7 @@ class MBeanClientTest {
 
     register(objectName0, customMBean0);
 
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       Collection<BeanObject> all =
@@ -376,7 +374,7 @@ class MBeanClientTest {
       register(objectName, mbean);
     }
 
-    try (MBeanClient client = new MBeanClient(jmxServer.getAddress())) {
+    try (var client = MBeanClient.of(jmxServer.getAddress())) {
 
       // act
       Collection<BeanObject> all =
diff --git a/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricsTest.java b/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricsTest.java
index 794dd3ad..a64c9370 100644
--- a/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricsTest.java
+++ b/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricsTest.java
@@ -41,7 +41,7 @@ class KafkaMetricsTest extends RequireBrokerCluster {
     jmxServer = JMXConnectorServerFactory.newJMXConnectorServer(serviceURL, null, mBeanServer);
     jmxServer.start();
 
-    mBeanClient = new MBeanClient(jmxServer.getAddress());
+    mBeanClient = MBeanClient.of(jmxServer.getAddress());
   }
 
   @AfterEach
diff --git a/app/src/test/java/org/astraea/service/RequireJmxServerTest.java b/app/src/test/java/org/astraea/service/RequireJmxServerTest.java
index ca8b69c0..c0dc76e1 100644
--- a/app/src/test/java/org/astraea/service/RequireJmxServerTest.java
+++ b/app/src/test/java/org/astraea/service/RequireJmxServerTest.java
@@ -10,7 +10,7 @@ public class RequireJmxServerTest extends RequireBrokerCluster {
 
   @Test
   void testQueryBeans() throws Exception {
-    try (var client = new MBeanClient(jmxServiceURL())) {
+    try (var client = MBeanClient.of(jmxServiceURL())) {
       var result = client.queryBeans(BeanQuery.all());
       Assertions.assertFalse(result.isEmpty());
     }
@@ -18,7 +18,7 @@ public class RequireJmxServerTest extends RequireBrokerCluster {
 
   @Test
   void testMemory() throws Exception {
-    try (var client = new MBeanClient(jmxServiceURL())) {
+    try (var client = MBeanClient.of(jmxServiceURL())) {
       var memory = KafkaMetrics.Host.jvmMemory(client);
       Assertions.assertNotEquals(0, memory.heapMemoryUsage().getMax());
     }