Overview
Java's generics is very powerful and easy to use. Its common use is straight forward, for example in the Collections API. In this post I would like to show another use, not so common, of generics. The purpose is to implement a proxy for remote class, e.g. when using RMI (Remote Method Invocation), JMX (Java Management Extensions), etc. This is handy since you usually have similar basic functionality in your proxy - connect, disconnect, etc.
As you can see in the class diagram above, there are 4 participants:
Design
The participants in the design - remote classes in green, local classes in blue (the interface is deployed remotely and locally) |
- MyInterface -
This is the interface of the remote class. This interface must be deployed both remotely and locally. - MyRemoteClass -
This is the implementation of the remote class. It is deployed remotely and is the target for our proxy (in the client-side) - AbstractProxy -
This is an abstract implementation of a proxy. It has some basic common proxy functionality such as connect, disconnect, etc. and it holds the actual proxy object encapsulated inside. This class is deployed locally (in the client-side). - MyProxy -
This is the specific proxy for MyRemoteClass. It implements MyInterface and extends AbstractProxy setting the generic type to be MyInterface.
Implementation
Here is partial and simplified implementation of the classes in the design above.
MyInterface
public interface MyInterface { void doSomething(); }
MyRemoteClass
public class MyRemoteClass implements MyInterface { @Override public void doSomething() { // Do something... } }
AbstractProxy
public abstract class AbstractProxy<T> { private T proxy; protected T getProxy() { return proxy; } public void connect() { // Connect to the remote class - set the encapsulated proxy object } public void disconnect() { // Disconnect from the remote class - reset the encapsulated proxy object } public boolean isConnected() { // Check whether the proxy is connected return true; } }
MyProxy
public class MyProxy extends AbstractProxy<MyInterface> implements MyInterface { @Override public void doSomething() { // Delegate the call to the proxy getProxy().doSomething(); } }
Use Case Example
Here is a more realistic example of how I used this pattern (with some simplification and modification) for implementing an abstract proxy for JMX agents.
Background
(If you are not interested in the "why", you can skip to the Implementation part)A few years ago, as part of a distributed system I was working on (school project...), I wanted to implement agents that will be deployed on the servers and listen for requests. This agents needed to answer 2 main requirements:
- Remote method invocation -
The ability to connect from one machine to another and trigger actions on the remote server. - Monitor the agents on the remote server -
The ability to connect from a PC to the agent on the remote server and interact with it for monitoring, configuration, perform "manual" actions, etc.
In the beginning I chose Java RMI for the task. It's quite easy to use (in development), and once you run the server you're OK. But then, if you want to monitor it from a remote PC you need to develop a dedicated client UI for that. Therefore I looked for another technology that will give me similar capabilities (of Remote Method Invocation) using an API, but will also answer my other requirement with an existing UI (command line or graphical).
The answer I found was JMX (Java Management Extensions). Although this is not exactly the purpose of JMX, it was close enough and it answers my requirements. The UI for monitoring is JConsole (part of the SDK). It enables you to connect to a Java process (local or remote) and monitor its threads, memory, loaded classes, registered MBeans and more. In the MBeans section you can view & update the attributes each MBean exposes and invoke its methods. This is a simple generic UI which was quite enough for the requirements.
After implementing the MBeans I needed to implement the clients which will enable me to programmatically connect to the remote agents. When I started I noticed there is some basic code that I repeats itself in all clients, just with small differences. These are good candidates for refactoring.
Implementation
public abstract class AbstractJmxClient<MBeanInterface> {
private MBeanInterface proxy = null;
private JMXConnector conn = null;
private MBeanServerConnection mbsc = null;
// JMX properties which are used to connect to the registered MBean
private String jmxHost = null;
private String jmxDomain = null;
private String jmxKeyName = null;
private String jmxKeyValue = null;
private int jmxPort = -1;
// Constructor getting all the JMX properties...
// Getters for all the JMX properties...
protected MBeanInterface getProxy() {
return _proxy;
}
/**
* Connect to the remote JMX agent
*/
@SuppressWarnings("unchecked")
public void connect() throws Exception
{
String urlSuffix = null;
try {
urlSuffix = this.jmxHost + ":" + this.jmxPort + "/jmxrmi";
ObjectName oName = new ObjectName(this.jmxDomain, this.jmxKeyName, this.jmxKeyValue);
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + urlSuffix);
if (this.conn == null) {
this.conn = JMXConnectorFactory.connect(url, null);
}
if (this.mbsc == null) {
this.mbsc = this.conn.getMBeanServerConnection();
}
Type type = getClass().getGenericSuperclass();
Class<MBeanInterface> mbiClass = null;
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
mbiClass = (Class<MBeanInterface>) paramType.getActualTypeArguments()[0];
}
this.proxy = JMX.newMBeanProxy(this.mbsc, oName, mbiClass);
} catch (RuntimeException e) {
String errMsg = String.format(
"Could not connect to the JMX agent: [%s:%s=%s] at %s due to the following error: %s",
this.jmxDomain, this.jmxKeyName, this.jmxKeyValue, urlSuffix, e.getMessage());
throw new Exception(errMsg, e);
}
}
/**
* Disconnect from the remote JMX agent
*/
public void disconnect() {
this.proxy = null;
this.mbsc = null;
if (this.conn != null) {
try {
this.conn.close();
} catch (IOException e) {
// Write to the trace
}
this.conn = null;
}
}
/**
* Check whether the client is connected to the remote JMX agent
*
* @return true
if the client is connected
*/
public boolean isConnected() {
if (this.proxy == null || this.conn == null) {
return false;
}
try {
this.conn.getConnectionId();
return true;
} catch (IOException e) {
return false;
}
}
}
No comments:
Post a Comment