| :index-group: Proxy Beans |
| :jbake-type: page |
| :jbake-status: status=published |
| = Proxy dinâmico para acessar o MBean |
| |
| == Exemplo |
| |
| Acessar o MBean é algo simples por meio da API JMX, mas geralmente é |
| técnico e não muito interessante. |
| |
| Este exemplo simplifica este trabalho simplesmente fazendo isso genericamente em um proxy. |
| |
| Portanto, do lado do usuário, você simplesmente declara uma interface para acessar seus |
| MBeans. |
| |
| Nota: A implementação de exemplo usa um MBeanServer local, mas aprimorando |
| a API de exemplo, é fácil imaginar uma conexão remota com |
| usuário / senha, se necessário. |
| |
| == API ObjectName (anotação) |
| |
| Simplesmente uma anotação para obter o objeto |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.Target; |
| |
| import static java.lang.annotation.ElementType.TYPE; |
| import static java.lang.annotation.ElementType.METHOD; |
| import static java.lang.annotation.RetentionPolicy.RUNTIME; |
| |
| @Target({TYPE, METHOD}) |
| @Retention(RUNTIME) |
| public @interface ObjectName { |
| String value(); |
| |
| // apenas para uso remoto |
| String url() default ""; |
| String user() default ""; |
| String password() default ""; |
| } |
| ---- |
| |
| == DynamicMBean Handler (a implementação de proxy) |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean; |
| |
| import javax.annotation.PreDestroy; |
| import javax.management.Attribute; |
| import javax.management.MBeanAttributeInfo; |
| import javax.management.MBeanInfo; |
| import javax.management.MBeanServer; |
| import javax.management.MBeanServerConnection; |
| import javax.management.ObjectName; |
| import javax.management.remote.JMXConnector; |
| import javax.management.remote.JMXConnectorFactory; |
| import javax.management.remote.JMXServiceURL; |
| import java.io.IOException; |
| import java.lang.management.ManagementFactory; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * Precisa de um método @PreDestroy para desconectar o host remoto quando usado no modo remoto. |
| */ |
| public class DynamicMBeanHandler implements InvocationHandler { |
| private final Map<Method, ConnectionInfo> infos = new ConcurrentHashMap<Method, ConnectionInfo>(); |
| |
| @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| final String methodName = method.getName(); |
| if (method.getDeclaringClass().equals(Object.class) && "toString".equals(methodName)) { |
| return getClass().getSimpleName() + " Proxy"; |
| } |
| if (method.getAnnotation(PreDestroy.class) != null) { |
| return destroy(); |
| } |
| |
| final ConnectionInfo info = getConnectionInfo(method); |
| final MBeanInfo infos = info.getMBeanInfo(); |
| if (methodName.startsWith("set") && methodName.length() > 3 && args != null && args.length == 1 |
| && (Void.TYPE.equals(method.getReturnType()) || Void.class.equals(method.getReturnType()))) { |
| final String attributeName = attributeName(infos, methodName, method.getParameterTypes()[0]); |
| info.setAttribute(new Attribute(attributeName, args[0])); |
| return null; |
| } else if (methodName.startsWith("get") && (args == null || args.length == 0) && methodName.length() > 3) { |
| final String attributeName = attributeName(infos, methodName, method.getReturnType()); |
| return info.getAttribute(attributeName); |
| } |
| // Operação |
| return info.invoke(methodName, args, getSignature(method)); |
| } |
| |
| public Object destroy() { |
| for (ConnectionInfo info : infos.values()) { |
| info.clean(); |
| } |
| infos.clear(); |
| return null; |
| } |
| |
| private String[] getSignature(Method method) { |
| String[] args = new String[method.getParameterTypes().length]; |
| for (int i = 0; i < method.getParameterTypes().length; i++) { |
| args[i] = method.getParameterTypes()[i].getName(); |
| } |
| return args; // note: null geralmente deve funcionar... |
| } |
| |
| private String attributeName(MBeanInfo infos, String methodName, Class<?> type) { |
| String found = null; |
| String foundBackUp = null; // sem verificar o tipo |
| final String attributeName = methodName.substring(3, methodName.length()); |
| final String lowerName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4, methodName.length()); |
| |
| for (MBeanAttributeInfo attribute : infos.getAttributes()) { |
| final String name = attribute.getName(); |
| if (attributeName.equals(name)) { |
| foundBackUp = attributeName; |
| if (attribute.getType().equals(type.getName())) { |
| found = name; |
| } |
| } else if (found == null && ((lowerName.equals(name) && !attributeName.equals(name)) |
| || lowerName.equalsIgnoreCase(name))) { |
| foundBackUp = name; |
| if (attribute.getType().equals(type.getName())) { |
| found = name; |
| } |
| } |
| } |
| |
| if (found == null && foundBackUp == null) { |
| throw new UnsupportedOperationException("cannot find attribute " + attributeName); |
| } |
| |
| if (found != null) { |
| return found; |
| } |
| return foundBackUp; |
| } |
| |
| private synchronized ConnectionInfo getConnectionInfo(Method method) throws Exception { |
| if (!infos.containsKey(method)) { |
| synchronized (infos) { |
| if (!infos.containsKey(method)) { // verifica novamente se há sincronização |
| org.superbiz.dynamic.mbean.ObjectName on = method.getAnnotation(org.superbiz.dynamic.mbean.ObjectName.class); |
| if (on == null) { |
| Class<?> current = method.getDeclaringClass(); |
| do { |
| on = method.getDeclaringClass().getAnnotation(org.superbiz.dynamic.mbean.ObjectName.class); |
| current = current.getSuperclass(); |
| } while (on == null && current != null); |
| if (on == null) { |
| throw new UnsupportedOperationException("classe ou método deve definir o objectName a ser usado para invocação: " + method.toGenericString()); |
| } |
| } |
| final ConnectionInfo info; |
| if (on.url().isEmpty()) { |
| info = new LocalConnectionInfo(); |
| ((LocalConnectionInfo) info).server = ManagementFactory.getPlatformMBeanServer(); //poderia usar um id ... |
| } else { |
| info = new RemoteConnectionInfo(); |
| final Map<String, String[]> environment = new HashMap<String, String[]>(); |
| if (!on.user().isEmpty()) { |
| environment.put(JMXConnector.CREDENTIALS, new String[]{ on.user(), on.password() }); |
| } |
| // ((RemoteConnectionInfo) info).connector = JMXConnectorFactory.newJMXConnector(new JMXServiceURL(on.url()), environment); |
| ((RemoteConnectionInfo) info).connector = JMXConnectorFactory.connect(new JMXServiceURL(on.url()), environment); |
| |
| } |
| info.objectName = new ObjectName(on.value()); |
| |
| infos.put(method, info); |
| } |
| } |
| } |
| return infos.get(method); |
| } |
| |
| private abstract static class ConnectionInfo { |
| protected ObjectName objectName; |
| |
| public abstract void setAttribute(Attribute attribute) throws Exception; |
| public abstract Object getAttribute(String attribute) throws Exception; |
| public abstract Object invoke(String operationName, Object params[], String signature[]) throws Exception; |
| public abstract MBeanInfo getMBeanInfo() throws Exception; |
| public abstract void clean(); |
| } |
| |
| private static class LocalConnectionInfo extends ConnectionInfo { |
| private MBeanServer server; |
| |
| @Override public void setAttribute(Attribute attribute) throws Exception { |
| server.setAttribute(objectName, attribute); |
| } |
| |
| @Override public Object getAttribute(String attribute) throws Exception { |
| return server.getAttribute(objectName, attribute); |
| } |
| |
| @Override |
| public Object invoke(String operationName, Object[] params, String[] signature) throws Exception { |
| return server.invoke(objectName, operationName, params, signature); |
| } |
| |
| @Override public MBeanInfo getMBeanInfo() throws Exception { |
| return server.getMBeanInfo(objectName); |
| } |
| |
| @Override public void clean() { |
| // no-op |
| } |
| } |
| |
| private static class RemoteConnectionInfo extends ConnectionInfo { |
| private JMXConnector connector; |
| private MBeanServerConnection connection; |
| |
| private void before() throws IOException { |
| connection = connector.getMBeanServerConnection(); |
| } |
| |
| private void after() throws IOException { |
| // no-op |
| } |
| |
| @Override public void setAttribute(Attribute attribute) throws Exception { |
| before(); |
| connection.setAttribute(objectName, attribute); |
| after(); |
| } |
| |
| @Override public Object getAttribute(String attribute) throws Exception { |
| before(); |
| try { |
| return connection.getAttribute(objectName, attribute); |
| } finally { |
| after(); |
| } |
| } |
| |
| @Override |
| public Object invoke(String operationName, Object[] params, String[] signature) throws Exception { |
| before(); |
| try { |
| return connection.invoke(objectName, operationName, params, signature); |
| } finally { |
| after(); |
| } |
| } |
| |
| @Override public MBeanInfo getMBeanInfo() throws Exception { |
| before(); |
| try { |
| return connection.getMBeanInfo(objectName); |
| } finally { |
| after(); |
| } |
| } |
| |
| @Override public void clean() { |
| try { |
| connector.close(); |
| } catch (IOException e) { |
| // no-op |
| } |
| } |
| } |
| } |
| ---- |
| |
| == Proxies dinâmicos |
| |
| === DynamicMBeanClient (o cliente JMX dinâmico) |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean; |
| |
| import org.apache.openejb.api.Proxy; |
| import org.superbiz.dynamic.mbean.DynamicMBeanHandler; |
| import org.superbiz.dynamic.mbean.ObjectName; |
| |
| import javax.ejb.Singleton; |
| |
| /** |
| * @author rmannibucau |
| */ |
| @Singleton |
| @Proxy(DynamicMBeanHandler.class) |
| @ObjectName(DynamicMBeanClient.OBJECT_NAME) |
| public interface DynamicMBeanClient { |
| static final String OBJECT_NAME = "test:group=DynamicMBeanClientTest"; |
| |
| int getCounter(); |
| void setCounter(int i); |
| int length(String aString); |
| } |
| ---- |
| |
| === DynamicRemoteMBeanClient (o Cliente JMX Dinâmico Remoto) |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean; |
| |
| import org.apache.openejb.api.Proxy; |
| |
| import javax.annotation.PreDestroy; |
| import javax.ejb.Singleton; |
| |
| |
| @Singleton |
| @Proxy(DynamicMBeanHandler.class) |
| @ObjectName(value = DynamicRemoteMBeanClient.OBJECT_NAME, url = "service:jmx:rmi:///jndi/rmi://localhost:8243/jmxrmi") |
| public interface DynamicRemoteMBeanClient { |
| static final String OBJECT_NAME = "test:group=DynamicMBeanClientTest"; |
| |
| int getCounter(); |
| void setCounter(int i); |
| int length(String aString); |
| |
| @PreDestroy void clean(); |
| } |
| ---- |
| |
| == O MBean usado para o teste |
| |
| === SimpleMBean |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean.simple; |
| |
| public interface SimpleMBean { |
| int length(String s); |
| |
| int getCounter(); |
| void setCounter(int c); |
| } |
| ---- |
| |
| == Simple |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean.simple; |
| |
| public class Simple implements SimpleMBean { |
| private int counter = 0; |
| |
| @Override public int length(String s) { |
| if (s == null) { |
| return 0; |
| } |
| return s.length(); |
| } |
| |
| @Override public int getCounter() { |
| return counter; |
| } |
| |
| @Override public void setCounter(int c) { |
| counter = c; |
| } |
| } |
| ---- |
| |
| == DynamicMBeanClientTest (O teste) |
| |
| [source,java] |
| ---- |
| package org.superbiz.dynamic.mbean; |
| |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.superbiz.dynamic.mbean.simple.Simple; |
| |
| import javax.ejb.EJB; |
| import javax.ejb.embeddable.EJBContainer; |
| import javax.management.Attribute; |
| import javax.management.ObjectName; |
| import java.lang.management.ManagementFactory; |
| |
| import static junit.framework.Assert.assertEquals; |
| |
| public class DynamicMBeanClientTest { |
| private static ObjectName objectName; |
| private static EJBContainer container; |
| |
| @EJB private DynamicMBeanClient localClient; |
| @EJB private DynamicRemoteMBeanClient remoteClient; |
| |
| @BeforeClass public static void start() { |
| container = EJBContainer.createEJBContainer(); |
| } |
| |
| @Before public void injectAndRegisterMBean() throws Exception { |
| container.getContext().bind("inject", this); |
| objectName = new ObjectName(DynamicMBeanClient.OBJECT_NAME); |
| ManagementFactory.getPlatformMBeanServer().registerMBean(new Simple(), objectName); |
| } |
| |
| @After public void unregisterMBean() throws Exception { |
| if (objectName != null) { |
| ManagementFactory.getPlatformMBeanServer().unregisterMBean(objectName); |
| } |
| } |
| |
| @Test public void localGet() throws Exception { |
| assertEquals(0, localClient.getCounter()); |
| ManagementFactory.getPlatformMBeanServer().setAttribute(objectName, new Attribute("Counter", 5)); |
| assertEquals(5, localClient.getCounter()); |
| } |
| |
| @Test public void localSet() throws Exception { |
| assertEquals(0, ((Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(objectName, "Counter")).intValue()); |
| localClient.setCounter(8); |
| assertEquals(8, ((Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(objectName, "Counter")).intValue()); |
| } |
| |
| @Test public void localOperation() { |
| assertEquals(7, localClient.length("openejb")); |
| } |
| |
| @Test public void remoteGet() throws Exception { |
| assertEquals(0, remoteClient.getCounter()); |
| ManagementFactory.getPlatformMBeanServer().setAttribute(objectName, new Attribute("Counter", 5)); |
| assertEquals(5, remoteClient.getCounter()); |
| } |
| |
| @Test public void remoteSet() throws Exception { |
| assertEquals(0, ((Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(objectName, "Counter")).intValue()); |
| remoteClient.setCounter(8); |
| assertEquals(8, ((Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(objectName, "Counter")).intValue()); |
| } |
| |
| @Test public void remoteOperation() { |
| assertEquals(7, remoteClient.length("openejb")); |
| } |
| |
| @AfterClass public static void close() { |
| if (container != null) { |
| container.close(); |
| } |
| } |
| } |
| ---- |