Introduzione
La
gestione delle applicazioni è un problema sempre
più sentito a causa anche al crescere della complessità
delle architetture moderne. Nuove tipologie di software
ma soprattutto nuovi modelli architetturali stanno di
giorno in giorno modificando la concezione di pro-gramma:
dai classici software stand alone, client side, si sta
passando a sistemi distribuiti, server side spesso basati
su concetti come quello di container o provider dinamici
di servizi remoti. Quest'ultime due caratteristiche
in particolare hanno introdotto un altro importante
concetto, quello della mutabilità delle applicazioni
e della loro dinamicità: se si pensa ad e-sempio
ad una web application in esecuzione all'interno di
un servlet container, si è abituati a considerare
l'applicazione costituita dalla sola web application,
mentre il container rappre-senta il framework esecutivo
indipendente dalla applicazione sviluppata. In realtà
se si guar-dano le cose da un punto di vista più
ampio, l'intero sistema (applicazione più container)
rappresentano un solo oggetto e possono essere considerati
nel loro complesso la vera "ap-plicazione".
In tale ottica l'applicazione complessiva muterà
il suo comportamento e caratte-ristiche ad ogni deploy
e undeploy di pagine JSP, servlet o applicazioni nel
complesso.
In definitiva, per come sono spesso organizzate le applicazioni
attualmente, non è semplice dare una definizione
che resti valida nel tempo di cosa sia e cosa non sia
una applicazione; ancora più delicato è
definire quindi una serie di regole per stabilire le
procedure di gestione (accensione, ripartenza, reload
di classi e componenti, etc..) della applicazione stessa.
La API JMX
JMX è stata progettata da Sun con lo scopo di
fornire uno strumento di amministrazione di differenti
tipologie di software tramite uno strumento unico, sufficientemente
generico ma al tempo stesso potente, e che offrisse
gli strumenti necessari per interagire con le risorse
pre-senti senza legare l'applicazioni client di management
con la risorsa stessa. In effetti uno de-gli obiettivi
principali della API (tant'è che ne compare spessissimo
un riferimento nella do-cumentazioni ufficiale) è
il concetto di disaccoppiamento fra strati differenti
di software.
Il cuore della architettura è il cosiddetto agent,
che può essere visto come un bus di comuni-cazione
fra le applicazioni di gestione e le risorse da gestire.
L'agent può essere configurato in modo da interagire
con alcuni connettori per permettere l'accesso remoto
ed in genere è contornato da degli MBeans, alcuni
dei quali presenti per specifica. Il componente fonda-mentale
dell'Agent Layer è il MBean server, che è
molto semplice ed almeno in teoria, può essere
implementato su differenti piattaforme dalla J2EE alla
J2ME.
L'Instrumentation
Level definisce come debbano essere create le varie
risorse da gestire: una risorsa JMX è quindi
gestibile da una qualsiasi applicazione di management
compatibile con JMX: essa può essere un oggetto,
un programma, un servizio, un device ed altro ancora.
Le risorse sono create tramite la definizione di un
MBeans di cui la specifica fornisce 4 diver-se tipologie:
Gli standard MBeans ed i Dynamic MBeans sono i due tipi
base a cui si aggiun-gono i Model MBeans, e gli Open
MBeans. I primi tre sono parte integrante della specifica,
e devono essere obbligatoriamente presenti in ogni implementazione
JMX, mentre gli Open per il momento non sono ancora
stati inseriti nella specifica ufficiale (attualmente
alla versio-ne 1.1).
Altri Service MBeans proprietari possono essere sviluppati
dai vari produttori ed aggiunti dinamicamente al server.
Oltre alla definizione degli MBeans, l'Instrumentation
Level introduce anche il meccanismo di notifica fra
beans differenti simile al sistema di comunicazione
fra componenti presente nella specifica JavaBeans. Le
notifiche delle modifiche possono essere propagate fra
bean dif-ferenti, il server e le applicazioni di management.
Per il momento la notifica remota tramite protocollo
di comunicazione non è parte integrante della
specifica ufficiale, anche se varie implementazioni
ne offrono diverse forme.
L'Agent
Level è rappresentato da un MBean Server e da
una serie di agent services: il suo scopo è offrire
uno strato di disaccoppiamento fra il client e la risorsa
vera e propria.
Figura 1 - L'agent si pone come strumento di separazione
fra client e risor-se gestite
L'agent
Level è costituito anche da alcuni MBeans di
servizio: quattro di questi sono obbliga-tori e definiti
dalla specifica, mentre altri possono essere aggiunti
dalle varie implementazioni proprietarie. I beans definiti
dalla specifica sono: M-Let service (permette di caricare
dalla rete altri MBeans ed aggiungerli all'agent server),
Timer service (che consente di eseguire o-perazioni
a determinati intervalli di tempo), Monitoring service
(implementano il pattern ob-server e svolgono la funzione
di osservatori delle modifiche oltre a notificarle a
tutti gli ascol-tatori interessati) ed il Relation service
(permette di creare relazioni fra beans).
Figura 2 - Architettura principale dell'Agent JMX
Meccanismi di invocazione
Il disaccoppiamento fra risorsa ed applicazione di management
è offerto grazie al concetto di object name che
rappresenta, tramite un nome, l'oggetto registrato.
Ogni applicazione di management può gestire il
funzionamento di un managed object, passando il suo
nome all'agente per ogni operazione invocata.
Il meccanismo di invocazione remota non si basa sul
modello stub-skeleton (come invece ac-cade ad esempio
in RMI e EJB), ma solo sull'uso di un nome: tramite
un nome si individua un oggetto e tramite un nome se
ne invoca un suo metodo. Se il nome è errato
l'oggetto non verrà trovato o il metodo non potrà
essere invocato.
Ogni modifica introdotta nella interfaccia dell'MBean
non richiede la redistribuzione della interfaccia. Questa
scelta comporta una notevole semplificazione in fase
di gestione di strut-ture complesse e costituite da
un alto numero di oggetti, ma ovviamente elimina del
tutto il type checking sui tipi sui metodi e sui valori
ritornati.
Un bean può essere sostituito, ri-deployed o
semplicemente aggiornato senza la necessità di
aggiornare o ricreare il link con il client.
Un primo semplice esempio
La prima operazione da fare è quella di creare
una interfaccia tramite la quale il managed object potrà
essere manipolato. L'interfaccia deve esporre i metodi
da invocare ed i metodi set/get corrispondenti alle
proprietà che si vorranno accedere e modificare.
La regola sui nomi delle proprietà segue la specifica
JavaBeans, per cui per ogni coppia di metodi setXXX()
e getXXX() il sistema deduce che vi sia una proprietà
dal nome XXX. La mancanza di uno dei due metodi implica
che la proprietà sia a sola lettura o sola scrittura.
I nomi dei metodi da invocare invece non seguono nessuna
regola precisa: per ogni metodo che non inizi per get
o per set si assume che si tratti di un metodo di esecuzione
e non di un metodo di accesso alle proprietà
del bean.
L'interfaccia deve avere nome che termina per MBean,
per poter informare il server che si tratta della interfaccia
remota del managed object. Ad esempio se si volesse
creare la versione JMX del classico HelloWorld, per
prima cosa è necessario implementare la sua interfaccia
e-sponendo la proprietà da stampare (name) ed
il metodo da invocare, sayHello().
Ecco la definizione della interfaccia HelloMBean
public
interface HelloMBean {
public String getName();
public void setName(String name);
public void sayHello();
}
Fatto
questo l'implementazione di tale interfaccia non presenta
particolari aspetti di cui tener conto. Si dovrà
fornire l'implementazione dei metodi remoti e seguire
le classiche convenzio-ni per seguire le regole del
contratto interfaccia-classe:
public
class Hello implements HelloMBean {
String name="";
public static void main(String[] args) {
Hello helloImpl1 = new Hello();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public void sayHello() {
System.out.println("Hello
"+name);
}
}
A
questo punto è necessario creare una classe che
funzioni come applicazione base e che re-gistri tale
MBean all'interno dell'agent JMX.
MBeanServer è l'interfaccia offerta da tutte
le implementazioni tramite la quale si può fra
le altre cose aggiungere un MBean, effettuare la registrazione
nel framework JMX, effettuare le invocazioni/interrogazioni
dei suoi metodi.
Per prima cosa è necessario ricavare un reference
all'agent, cosa che può essere fatta tramite
il metodo di factory createMBeanServer() della classe
MBeanServerFactory. Il valore ritornato dipende dalla
implementazione utilizzata.
Tramite l'agent è possibile quindi registrare
l'MBean Hello
import
javax.management.*;
import com.sun.jdmk.comm.HtmlAdaptorServer;
public
class Agent {
public Agent() {
}
public static void main(String[] args)
{
MBeanServer server = MBeanServerFactory.createMBeanServer();
ObjectName name = null;
try {
name = new ObjectName("example:name=hello");
server.registerMBean(new
Hello(), name);
System.out.println("Partito....");
}
catch (MalformedObjectNameException
ex) {
System.out.println("NotCompliantMBeanException
"+ex1);
}
catch (NotCompliantMBeanException
ex1) {
System.out.println("NotCompliantMBeanException
"+ex1);
}
catch (MBeanRegistrationException
ex1) {
System.out.println("MBeanRegistrationException
"+ex1);
}
catch (InstanceAlreadyExistsException
ex1) {
System.out.println("InstanceAlreadyExistsException
"+ex1);
}
}
}
A
questo punto l'oggetto è registrato presso il
server e potrà essere invocato in ogni momen-to
tramite il meccanismo di reflection offerto dall'agent
stesso. Per comprendere come ciò possa essere
effettuato è necessario fare una prima importante
distinzione: l'agent e di conse-guenza anche il managed
object possono essere acceduti sia tramite un protocollo
di connes-sione da un client remoto, sia da un client
in esecuzione all'interno della stessa JVM.
Nel primo caso, anche se concettualmente più
complesso, le cose da un punto di vista stret-tamente
di programmazione JMX, sono più semplici dato
che tutte le implementazioni di server offrono uno o
più adapter per l'esecuzione remota. Ad esempio
volendo interagire con l'MBean Hello tramite un browser
si dovrà modificare la classe Agent aggiungendo
la registra-zione dell'adapter HTTP al server. Per fare
questo si potrebbe scrivere
HtmlAdaptorServer
adaptor = new HtmlAdaptorServer();
name = new ObjectName("adaptor:protocol=HTTP");
server.registerMBean(adaptor, name); HtmlAdaptorServer
adaptor = new HtmlAdaptorServer();
name = new ObjectName("adaptor:protocol=HTTP");
server.registerMBean(adaptor, name);
adaptor.start();
Si
noti come l'adaptor venga registrato nel server in modo
del tutto simile a quanto fatto per l'MBean Hello.
A tal punto si potrà utilizzare un comune browser
HTTP per collegarsi alla porta 8080 dell'host su cui
è in esecuzione l'agent e verificare la possibilità
di interazione con il mana-ged object.
Figura 3 - la console JMX HTML offerta dall'adapter
HTTP
della implementazione JMX della Sun
Quello
che appare è una console HTML molto simile alla
ben nota JMX-Console di JBoss, tramite la quale sarà
possibile invocare i metodi di set/get sul bean, e quindi
accedere alla managed property "name", così
come invocare il metodo sayHello() del bean.
Figura 4 - Tramite la console HTML è possibile
invocare direttamente
i metodi del bean ed impostare le sue proprietà
Nel
caso in cui si voglia invece effettuare l'invocazione
del managed bean da un programma in esecuzione all'interno
della stessa JVM in cui opera il server, si devono eseguire
alcune o-perazioni leggermente differenti. Per prima
cosa si dovrà ricavare un reference al server;
con la seguente istruzione si ottiene una lista di tutti
i server in funzione sulla macchina
List
ServerList = MBeanServerFactory.findMBeanServer(null);
Assumendo
che ve ne sia uno solo effettivamente funzionante, con
la seguente istruzione si ottiene il riferimento al
server.
MBeanServer
Server = (MBeanServer)ServerList.iterator().next();
A
questo punto si può creare un ObjectName e passarlo
all'agent per effettuare le ricerca fra i managed objects
registrati. Al solito tramite il metodo invoke() si
potrà effettuare l'invocazione del metodo sayHello().
ObjectName
Name = new ObjectName("example:name=user");
Object result = Server.invoke(Name,"sayHello",null,
null);
Si
noti come il metodo invoke() permette di invocare il
metodo esclusivamente conoscendone il nome, ma senza
fare uso di nessun stub del metodo, come invece avviene
in RMI.
In questo caso se qualcuno modificasse l'interfaccia
HelloMBean o il bean corrispondente, per continuare
a garantire al client di gestione il corretto funzionamento
sarebbe necessario so-lamente conoscere il nome del
metodo, ma non ridistribuire codice ai vari client.
Anche il risultato restituito è del tutto generico:
una istanza di tipo Object è sufficientemente
generica per evitare di dover modificare il client per
ogni modifica del software sul server.
Notifica di eventi
Uno dei meccanismi basilari di intercomunicazione fra
MBeans è quello basato sulla gestione degli eventi
e della loro propagazione secondo il modello Sorgente-Ascoltatore.
I messaggi (oggetto Notification) sono propagati da
oggetti registrati nel server, detti broadcaster MBeans
(oggetto NotificationBroadcaster) verso tutti gli ascoltatori
registrati (oggetto NotificationListe-ner) su quella
particolare sorgente dati e per quel particolare tipo
di evento; in questo caso la selezione dei messaggi
viene effettuata tramite un apposito filtro (oggetto
NotificationFilter).
Notification è l'oggetto base che rappresenta
un messaggio: esso deriva da java.util.EventObject,
al quale aggiunge le proprietà type, sequenceNumber
(un progressivo che permette di identi-ficare in modo
univoco un messaggio), un time stamp, ed un campo generico
nel quale poter inserire oggetti di vario tipo (la specifica
non impone nessuna restrizione in tal senso ma per facilitare
il compito dei connettori per protocolli remoti si consiglia
di utilizzare in questo caso oggetti serializzabili).
Il notification type (ricavabile dal metodo Notification.getType())
è rappresentato da una strin-ga che in genere
segue la notazione separata da punto, tipica dei nomi
dei packages, anche se in questo caso non è necessario
seguire nessuna regola particolare relativamente a nomi
di classi o altro. Tutte le stringhe che iniziano per
jmx. sono riservate per il framework JMX uti-lizzato.
Nell'esempio che segue viene utilizzata una stringa
del tipo "com.mokabyte.community.user.remove"
ad indicare un messaggio prodotto in corrispondenza
della rimozione di un utente dalla comunità virtuale
di MokaByte.
Per abilitare un MBean alla produzione di eventi si
deve implementare l'interfaccia NotificationBroadcaster,
che offre i due metodi
public
void addNotificationListener(NotificationListener notificationListener,
NotificationFilter
notificationFilter,
Object
object);
public void removeNotificationListener(NotificationListener
notificationListener);
utili
per la registrazione e deregistrazione dei vari ascoltatori.
Il metodo getNotificationInfo() restituisce informazioni
aggiuntive sul messaggio ricevuto.
Gli ascoltatori di un messaggio devono implementare
l'interfaccia NotificationListener che tramite il metodo
public
void handleNotification(Notification notification,
Object handback)
possono
effettuare le operazioni necessarie al ricevimento del
messaggio.
Quando un ascoltatore viene registrato presso un broadcaster,
esso riceve anche un filtro tramite il quale si può
decidere se inoltrare o meno un messaggio agli ascoltatori:
il metodo
public
boolean isNotificationEnabled(Notification notification)
Permette
di specificare il criterio tramite il quale determinare
se un particolare evento debba essere inoltrato all'ascoltatore
che ha associato tale filtro. Nell'esempio che segue
tale con-trollo verifica in modo molto banale se l'evento
corrisponde alla rimozione di un utente op-pure no.
Come spesso accade in Java, invece di implementare l'interfaccia
NotificationBroadcaster e for-nire l'implementazione
per tutti i suoi metodi, si può utilizzare la
classe di supporto NotificationBroadcasterSupport che
di fatto fornisce una implementazione base per questo
ge-nere di oggetto.
Esempio
L'esempio che segue parte dalla definizione dell'MBean
UserMBean visto in precedenza ed estendendolo in modo
da crearne uno, BroadcastingUser, orientato alla gestione
degli eventi.
public
class BroadcastingUser extends User
implements BroadcastingUserMBean,
NotificationBroadcaster{
private
NotificationBroadcasterSupport Broadcaster = new NotificationBroadcasterSupport();
private long NotificationSequence=0;
public
void addNotificationListener(NotificationListener notificationListener,
NotificationFilter
notificationFilter,
Object
object){
Broadcaster.addNotificationListener(notificationListener,
notificationFilter,
obj);
}
public
void removeNotificationListener(NotificationListener
notificationListener)
throws ListenerNotFoundException {
Broadcaster.removeNotificationListener(notificationListener);
}
public MBeanNotificationInfo[] getNotificationInfo()
{
return null;
}
public void remove() {
// crea un messaggio da inviare
Notification UserNotification
= new Notification("com.mokabyte.community.user.remove",this,
++NotificationSequence,
"User "+this.getName()+"
is removed");
// invia il messaggio tramite
il broadcaster ricevuto
// come parametro
Broadcaster.sendNotification(UserNotification);
}
}
Questo
oggetto crea al suo interno un broadcaster per l'invio
di messaggi.
L'interfaccia MBean da cui deriva è la
public
interface BroadcastingUserMBean {
public void remove();
}
Il
metodo remove() rappresenta il metodo gestito come operazione
JMX da un client di management: alla sua invocazione
esso propagherà un messaggio di tipo "com.mokabyte.community.user.remove",
in modo che tutti gli ascoltatori interessati questo
ge-nere di evento provvedano alla rimozione dal server
come oggetti registrati.
La
classe UserListener rappresenta un ipotetico ascoltatore
di Notification JMX e nel metodo handleNotification()
effettua la deregistrazione dal server. L'oggetto MBeanServer
in questo ca-so viene passato come parametro del costruttore
alla creazione del listener stesso.
public
class UserListener implements NotificationListener {
MBeanServer Server = null;
public UserListener(MBeanServer server)
{
Server= server;
}
// gestisce l'evento ricevuto
public void handleNotification(Notification
notification,
Object handback) {
String NotificationType = notification.getType();
if (NotificationType.equals(
"com.mokabyte.community.user.remove")){
System.out.println(notification.getMessage());
try {
Server.unregisterMBean(
(ObjectName) handback);
}
catch (MBeanRegistrationException
ex) {
System.out.println("MBeanRegistrationException:
"+ex);
}
catch (InstanceNotFoundException
ex) {
System.out.println("InstanceNotFoundException
"+ex);
}
}
}
}
infine
qui di seguito è riportato il codice relativo
al filtro che effettua il controllo sul tipo di messaggio
da inviare. La classe UserFilter implementa il metodo
isNotificationEnabled in modo da controllare se il messaggio
ricevuto è di tipo "com.mokabyte.community.user.remove":
solo in tal caso il broadcaster inoltra il messaggio
a tale ascoltatore.
public
class UserFilter implements NotificationFilter {
public boolean isNotificationEnabled(Notification
notification) {
if (notification.getType().equals(
"com.mokabyte.community.user.remove"))
return true;
else
return false;
}
}
Conclusioni
In primo approccio al framework JMX abbiamo visto i
concetti di base compresi alcuni cen-ni alle tecniche
di programmazione di alcuni semplici MBeans, alla loro
invocazione da re-moto ed alla gestione degli eventi.
Ovviamente dato il poco spazio a dispostone all'interno
di un articolo non è stato possibile affrontare
nel dettaglio tutti gli aspetti che qui sono stati so-lamente
introdotti.
Per chi desiderasse maggiori approfondimenti si consiglia
la lettura del testo [JMXJB] che fra le altre cose riporta
come esempio implementativo nella parte finale l'architettura
a mi-crokernel di JBoss.
Nei prossimi mesi vedremo di affrontare nel modo più
esauriente possibile gli aspetti relativi a tutte le
tipologie di MBeans ed alla loro modalità di
gestione da remoto.
Un'ultima nota relativa alla implementazione degli esempi
mostrati in questo articolo. Dopo aver scaricato i sorgenti
allegati, si dovrà procedere anche al download
ed alla installazione di un Agent Provider JMX. Al momento
le implementazioni forse più utilizzate sono
quella Sun e quella di IBM (vedi i link nella bibliografia),
mentre esiste una terza implementazione meno conosciuta
di AdvanceNet (vedi [ANET]).
Per maggiori dettagli circa l'installazione ed il funzionamento
di tali oggetti si faccia riferi-mento ai siti web dei
produttori.
Bibliografia
e riferimenti
[JMXSpec] - JMX Sun official reference: http://java.sun.com/products/JavaManagement
[JMXRI] - JMX Reference Implementation
http://java.sun.com/products/JavaManagement/download
[TIV] IBM JMX Tivoli implementation : http://www.alphaworks.ibm.com
[ANET] AdvanceNet JMX implementation: http://www.advancenet.com
[JMXJB] "JMX: managing J2EE with Java Management
Extensions" di J. Lindfors, M.Fleury, The JBoss
Group; Ed. SAMS.
Risorse
Gli esempi descritti
in questo articolo sono organizzati in tre cartelle
relative ai tre esempi (invocazione tramite connettore
HTTP, invocazione in JVM, notifica di eventi). Nel file
readme.txt sono presenti le istruzioni per il funzionamento.
|