MokaByte Numero 15 - Gennaio 1998
Foto
 
Java  Start Up 2
di
Fabrizio Giudici
Giovanni Puliti
Per chi si affaccia alla tecnologia Java per la prima volta  ecco una miniserie introduttiva sulla tecnologia.    

 


 

Dopo l’articolo del mese scorso in cui abbiamo affrontato gli argomenti introduttivi della tenologia Java, questo mese proseguiamo il discorso introducendo gli aspetti avanzati di tale piattaforma, analizzando in particolare gli strumenti per la soluzione delle problematiche tipiche della programmazione generica e di quella orientata ad internet.
Verranno affrontati alcuni argomenti di particolare importanza, partendo  dall’approfondimento di alcune librerie standard, ed integrando con alcuni esempi pratici la teoria esposta.
Per quanto riguarda le librerie per il networking, verrà descritta la gestione di socket TCP ed UDP e la realizzazione di demoni.
Successivamente si darà spiegazione di come sia possibile interfacciare una applicazione o una applet, con una base di dati per mezzo della libreria JDBC.
Per permettere una graduale acquisizione degli argomenti affrontati, interromperemo questo escursus su Java a questo punto, rimandando al mese successivo la trattazione delle tecnologie di Serializzazione e Remote Method Invocation (RMI) e JavaBeans.
 

ALCUNE CLASSI DI UTILITÀ
Vediamo prima di tutto un paio di classi di utilità generale, che torneranno utili nel seguito. Esse sono Vector e Hashtable, due modi diversi per raggruppare insieme oggetti diversi.
Vector è una specie di array dinamico di oggetti, nel quale possiamo aggiungerne e rimuoverne a piacere elementi generici (è la classe Vector alloca dinamicamente la memoria richiesta per contenerli).
I metodi principali sono:
 

Un Hashtable invece, è una sorta di “memoria associativa”, di fatto una tabella a due colonne in cui ogni riga rappresenta un’associazione tra due oggetti. Uno dei due oggetti viene chiamato “chiave”, ed è usato come riferimento per manipolare l’oggetto ad esso associato. I metodi principali di Hashtable sono:
  Vediamo subito un esempio:
import java.util.*;
 
 

public class Containers

  {

    public static void main (String[] args)

      {

        Vector v = new Vector();
 
 

        v.addElement("String 1");

        v.addElement(new Integer(2));
 
 

        for (Enumeration e = v.elements(); e.hasMoreElements(); )

          {

            Object o = (Object)e.nextElement();

            System.out.println(o);

          }
 
 

        Hashtable t = new Hashtable();
 
 

        t.put("one", new Integer(1));

        t.put("two", new Integer(2));

        t.put("three", new Integer(3));

        System.out.println(t);
 
 

        System.out.println(t.get("two"));
 
 

        for (Enumeration e = t.elements(); e.hasMoreElements(); )

          {

            Object o = (Object)e.nextElement();

            System.out.println(o);

          }

      }

  }

È particolarmente interessante la classe Enumeration: essa viene usata per accedere in maniera sequenziale agli oggetti contenuti in un contenitore, senza far riferimento alla particolare implementazione (in questo caso senza sapere che Vector implementa un accesso casuale con il metodo elementAt()). Ogni contenitore deve essere in grado di costruire la sua versione di Enumeration, in grado cioè di ispezionarlo, e restituirla con il metodo elements(). Come si vede nell’esempio precedente, lo stesso codice (il blocco for) può essere usato per accedere agli elementi in due contenitori completamente diversi come Vector e Hashtable.
 
 
 

NETWORKING CON JAVA
Essendo stato sviluppato appositamente per operare in rete, Java non poteva non avere una API completa e flessibile dedicata al networking. Come avviene per tutte le API di Java, quello che ci viene messo a disposizione è solo un set minimale di primitive che consentono di effettuare tutte le operazioni fondamentali; al di sopra di questo set è possibile implementare qualcosa di più sofisticato.
Avendo a che fare con macchine in rete, si rendono necessarie due cose fondmaentalmente: da un lato è necessario un formalismo per individuare in maniera esatta ed univoca un host all’interno di una rete, e dall’altro un meccanismo che ci consenta di trasferire dati da un host ad un altro. Visto che Java fa esplicitamente riferimento ad Internet, la terminologia e le interfacce sono prese direttamente dallo standard TCP/IP.

Gli indirizzi
L’indirizzo di un host è rappresentato da un’istanza della classe InetAddress. Essa fornisce una serie di metodi per la manipolazione di indirizzi:
 

Vediamo con un  esempio come utilizzare tali strumenti
import java.io.*;
import java.net.*;

public class AddressExample {
    public static void main (String[] args)
      throws Exception {
        InetAddress addr = InetAddress.getLocalHost();
        System.out.println("LocalHost: " + addr);
        System.out.println("LocalHost: " + addr.getHostName());

        InetAddress infomedia = InetAddress.getByName("www.infomedia.it");
        System.out.println("Infomedia: " + infomedia);
        System.out.println("Infomedia: " + infomedia.getHostName());
      }
  }

 

I socket
Un socket è un flusso di byte che può essere trasmesso tra due host diversi collegati in rete. Secondo lo standard TCP/IP fondamentalmente possono esistere due tipi di socket:
  Dal momento che un socket collega tra loro due host, dev’essere descritto da una coppia di indirizzi Internet; e dal momento che una macchina può aprire contemporaneamente più socket, esiste un altro parametro, detto porta, che permette di identificare i socket aperti. La porta è un numero a 16 bit ed esistono definizioni standard che associano una data porta ad un dato servizio; per esempio l’FTP usa le porte 20 e 21, il Telnet la porta 23, l’HTTP la porta 80. Quando un server riceve una richiesta di apertura di un socket da parte di un client, esso utilizza il numero di porta per scegliere quale programma di servizio (demone) deve essere attivato (per esempio un Netscape Fastrack piuttosto che un server FTP).

I socket in Java sono implementati con quattro classi distinte:

Tutti i socket hanno in comune i seguenti metodi:

Socket di tipo TCP
Un socket di tipo TCP deve essere in generale costruito con almeno tre parametri: la porta locale, l’indirizzo remoto e la porta remota:
 

I parametri dell’host remoto possono essere successivamente letti e modificati con opportuni metodi:
  Quando il socket non serve più deve essere chiuso con il metodo

    public void close();

Dal momento che un socket TCP implementa un flusso di dati “orientato alla connessione”, Java permette di associarvi una coppia di stream di i/o, che possono essere ottenuti con i metodi:

    public InputStream getInputStream();
    public OutputStream getOutputStream();

Da questo punto di vista, i socket TCP si gestiscono in modo molto simile ai file: possiamo associarvi stream di tipo DataInput e DataOutput per effettuare i/o di tipi primitivi.
L’esempio seguente illustra un semplice mini-programma che implementa un servizio simile ad “Echo”, presente in tutte le macchine Unix, che non fa altro che leggere dal client stringhe terminate da un newline e rispedirle indietro.

import java.net.*;
import java.io.*;

public class TCPClient
  {
    public void start()
      throws Exception
      {
        DataInputStream stdIn = new DataInputStream(System.in);
        Socket socket = new Socket("localhost", 7777);
        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
        DataInputStream is = new DataInputStream(socket.getInputStream());

        for (;;)
          {
            System.out.print("input: ");
            String userInput = stdIn.readLine();

           if  (userInput.equals("QUIT"))
              break;

            os.writeBytes(userInput + '\n');
            System.out.println("echo: " + is.readLine());
          }

        os.close();
        is.close();
        socket.close();
      }

    public void main (String[] args)
      throws Exception
      {
        TCPClient tcpClient = new TCPClient();
        tcpClient.start();
      }
  }
 

Se vogliamo creare un socket TCP dal lato server, le cose sono un po’ più complesse. Infatti, in generale, un server può rispondere contemporaneamente a più richieste. Per questo motivo di solito non si usa direttamente un socket server per trasferire dati, ma come “intermediario” per creare dinamicamente un socket che serva una singola richiesta. Questo concetto è chiarito nell’esempio successivo, dove un ruolo fondamentale è giocato dal metodo

public void  accept();

che attende una nuova richiesta ed apre “al volo” un nuovo socket per gestirla.

mport java.net.*;

import java.io.*;
 
 

public class TCPServer

  {

    public void start()

      throws Exception

      {

        ServerSocket serverSocket = new ServerSocket(7777);
 
 

        for (;;)

          {

            System.out.println("Waiting... ");

            Socket socket = serverSocket.accept();

            System.out.println("New socket " + socket);

            DataInputStream is = new DataInputStream(socket.getInputStream());

            DataOutputStream os =

               new DataOutputStream(socket.getOutputStream());
 
 

            for (;;)

              {

                String userInput = is.readLine();
 
 

                if (userInput == null || userInput.equals("QUIT"))

                  break;
 
 

                os.writeBytes(userInput + '\n');

                System.out.println("Replying " + userInput);

              }
 
 

            os.close();

            is.close();

            System.out.println("Closed socket " + socket);

            socket.close();

          }

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        TCPServer tcpServer = new TCPServer();

        tcpServer.start();

      }

  }

Il server dimostrativo appena implementato è di nuovo un servizio “Echo”: non fa altro che leggere linee terminate da un newline e rispedirle indietro; termina quando il client chiude la comunicazione mandando la riga “QUIT”. Non è adatto per servire più richieste contemporaneamente: infatti il loop principale ritorna alla accept() solo dopo che la transazione corrente è stata terminata.

Per servire più richieste contemporaneamente si può usare un thread parallelo come nell’esempio seguente:

import java.net.*;

import java.io.*;
 
 

class ServerThread extends Thread

  {

    private Socket socket;
 
 

    public ServerThread (Socket socket)

      {

        this.socket = socket;

      }
 
 

    public void run()

      {

        try

          {

            service();

          }
 
 

        catch (Exception e)

          {

            e.printStackTrace(System.out);

          }

      }
 
 

    public void service()

      throws Exception

      {

        DataInputStream is = new DataInputStream(socket.getInputStream());

        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
 
 

        for (;;)

          {

            String userInput = is.readLine();
 
 

            if (userInput == null || userInput.equals("QUIT"))

              break;
 
 

            os.writeBytes(userInput + '\n');

            System.out.println("Replying " + userInput);

          }
 
 

        os.close();

        is.close();

        System.out.println("Closed socket" + socket);

        socket.close();

      }

  }
 
 

public class TCPParallelServer

  {

    public void start()

      throws Exception

      {

        ServerSocket serverSocket = new ServerSocket(7777);
 
 

        for (;;)

          {

            System.out.println("Waiting... ");

            Socket socket = serverSocket.accept();

            System.out.println("New socket " + socket);

            ServerThread serverThread = new ServerThread(socket);

            serverThread.start();

          }

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        TCPParallelServer tcpServer = new TCPParallelServer();

        tcpServer.start();

      }

  }

L’esempio appena visto è di fatto l’analogo dei “demoni forkati” di Unix e Windows, ma usando un thread al posto di un processo duplicato è molto più “leggero” dal punto di vista delle risorse di sistema operativo.
L’esempio precedente può essere facilmente generalizzato (dichiarando service() come abstract) per implementare un generico demone di servizio.
 

Socket di tipo UDP
Come si è detto precedentemente, un socket UDP serve per la trasmissione di “piccoli” pacchetti di dati detti datagrammi (secondo lo standard TCP/IP non più lunghi di 1500 byte). Java definisce una classe apposita, DatagramPacket, per la loro implementazione. Di fatto non è altro che un contenitore di un array di byte con un po’ di metodi per definire e leggere gli indirizzi:

Per gestire i datagrammi esiste la classe DatagramSocket. Per crearne un’istanza è sufficiente specificare il numero della porta:

    public DatagramSocket (int port);

I datagrammi possono poi essere spediti e ricevuti con i metodi send() e receive(), come nell’esempio seguente:

import java.net.*;

public class UDPExample
  {
    public  static void main (String[] args)
      throws Exception
      {
        byte[] msg = {'H', 'e', 'l', 'l', 'o'};
        InetAddress addr =  InetAddress.getByName("192.5.6.7");

        DatagramSocket s = new DatagramSocket(2222);

        DatagramPacket hi = new DatagramPacket(msg, msg.length, addr, 2223);
        s.send(hi);

        byte[] buf = new byte[1000];
        DatagramPacket recv = new DatagramPacket(buf, buf.length);
        s.receive(recv);
      }
  }
 

Va notato che per spedire un datagramma è necessario specificare ogni volta la porta di destinazione: ciò non deve sorprendere, dal momento che un socket UDP non crea alcuna connessione ed ogni datagramma viene trattato indipendentemente dagli altri.
Per implementare un socket broadcast è sufficiente utilizzare indirizzi IP aventi tutti “1” nel campo host: per esempio 130.251.255.255 se la sottorete è 130.251.*.*. Per quanto riguarda i socket multicast, essi hanno una classe apposita, MultiCastSocket, che si gestisce analogamente ad un DatagramSocket:

import java.net.*;

public  class MulticastExample
  {
    public  static void  main (String[] args)
      throws Exception
      {
        byte[] msg = {'H', 'e', 'l', 'l', 'o'};
        InetAddress group =  InetAddress.getByName("228.5.6.7");
        InetAddress addr =  InetAddress.getByName("192.5.6.7");

        MulticastSocket s = new MulticastSocket(2222);
        s.joinGroup(group);

        DatagramPacket hi = new DatagramPacket(msg, msg.length, addr, 2223);
        s.send(hi);

        byte[] buf = new byte[1000];
        DatagramPacket recv = new DatagramPacket(buf, buf.length);
        s.receive(recv);
        s.leaveGroup(group);
      }
  }
 

L’unica novità è data dai metodi joinGroup() e leaveGroup() che servono, rispettivamente, per “entrare” ed “uscire” dal gruppo di host. Per quanto riguarda gli indirizzi multicast, essi devono essere stati esplicitamente definiti dall’amministratore di sistema.
 

Uniform Resource Locators (URL)
I socket sono l’oggetto più a basso livello per quanto riguarda il networking. Java mette a disposizione un meccanismo un po’ più evoluto, le “connessioni URL”.
Le URL, nate con il WWW ma ormai usate per ogni tipo di protocollo, consentono di rappresentare in maniera compatta una generica risorsa disponibile in Internet. Una tipica URL ha la forma:

 protocollo://indirizzo_host/percorso_della_risorsa/nome_della_risorsa:porta
Le Uniform Resource Locators vengono manipolate in Java per mezzo della classe URL. Tutta una serie di metodi permette di estrarre o modificare i campi che le compongono: Una volta che una URL è stata creata, per leggere il documento corrispondente bisogna aprire una connessione associata:

    public URLConnection openConnection();

Questo metodo ci restituisce un oggetto di tipo URLConnection, che di fatto “nasconde” il socket usato per la connessione. Da questo socket possiamo ottenere gli stream di i/o mediante i metodi:

che a questo punto possiamo utilizzare come al solito. Nell’esempio successivo è illustrato un pezzo di codice che legge una pagina WWW:
import java.io.*;

import java.net.*;
 
 

public class URLExample

  {

    public void start()

      throws Exception

      {

        URL url = new URL("http://www.infomedia.it");

        URLConnection conn = url.openConnection();

        DataInputStream is = new DataInputStream(conn.getInputStream());
 
 

        for (;;)

          {

            String line = is.readLine();
 
 

            if (line == null)

              break;
 
 

            System.out.println(line);

          }
 
 

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        URLExample ue = new URLExample();

        ue.start();

      }

  }

Java prevede che si possano registrare oggetti in grado di gestire protocolli customizzati e oggetti di tipo MIME. La classe UrlConnection definisce il metodo:

    public Object getObject();
che è in grado di sfruttarli. Per esempio, è possibile definire un gestore di oggetti di tipo MIME in grado di riconoscere i documenti di tipo MPEG e passare il controllo ad un’istanza di una nostra classe MPEGObject, in grado  di leggere il documento. Ad alto livello è sufficiente invocare getObject() per ottenere tale istanza. Il meccanismo di implementazione non è particolamente complesso, ma è lungo da descrivere ed esula dagli scopi di questa trattazione.
 
 
 

LA CONNETTIVITÀ CON I DATABASE: LA JDBC API

A cosa serve collegarsi in rete? Tipicamente a consultare o modificare dati che risiedono su un server. Appare quindi ovvio che Java sia equipaggiato con una API specifica per il collegamento con database (remoti o non).
Una delle API più diffuse per l’accesso a database relazionali è la ODBC (Open DataBase Connectivity), introdotta da Microsoft ed universalmente supportata. Pur essendo diffusamente utilizzata, essa ha un paio di problemi strutturali:

1. È di difficile apprendimento, in quanto non esiste separazione tra funzionalità di base e funzionalità avanzate; come conseguenza, anche le operazioni più semplici e più frequenti implicano un’eccessiva complessità.
2. Non ha una forte tipizzazione dei dati, ad esempio in C si fa frequente uso dei puntatori void*, demandando al programma applicativo la responsabilità di determinare per ispezione il tipo corretto delle variabili, aprendo così la porta a sottili bug di programmazione.

Specialmente la seconda caratteristica rende ODBC in pratica improponibile in Java, che invece tipizza fortemente i dati. Per questi motivi, Sun ha creato un’apposita API Java per l’interfacciamento con i database: la JDBC API (Java DataBase Connectivity). Essa è efficiente, si integra consistentemente con l’ambiente Java, è semplice da usare, non pone restrizioni sulle operazioni eseguibili, è facilmente utilizzabile nei sistemi di sviluppo visuale e rapido (RAD).
Per presentare un’interfaccia omogenea pur mantenendo una grande flessibilità d’uso, la JDBC API è organizzata in diversi “strati”: il programma applicativo vede solo lo strato superiore, che è indipendente dal database che interroga, mentre il compito di interfacciarsi effettivamente con il database è delegato ad una serie di driver sottostanti, che possono essere scritti in codice Java o nativo.
La JDBC fa esplicito riferimento al linguaggio SQL, che è il più diffuso strumento per dialogare con un database. Vediamo subito un esempio pratico di interrogazione SQL scritta in Java:
 

import java.io.*;

import java.sql.*;
 
 

public class JDBCExample1

  {

    public void start()

      throws Exception

      {

        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

        Connection con = DriverManager.getConnection("jdbc:odbc:dbdemo",

                                                     "user",

                                                     "password");

        Statement st = con.createStatement();
 
 

        ResultSet rs = st.executeQuery("SELECT * FROM users " +

                                       "ORDER BY last_name,first_name");
 
 

        while (rs.next())

          {

            int id = rs.getInt(1);

            String first_name = rs.getString(2);

            String last_name  = rs.getString(3);

            String address    = rs.getString(4);

            String phone      = rs.getString(5);
 
 

            System.out.println("First name: " + first_name + "\n" +

                               "Last Name:  " + last_name + "\n" +

                               "Address:    " + address + "\n" +

                               "Phone:      " + phone);

          }
 
 

        rs.close();

        con.close();

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        JDBCExample1 jdbcExample1 = new JDBCExample1();

        jdbcExample1.start();

      }

  }

La prima operazione da eseguire è assicurarsi che il driver prescelto sia stato caricato in memoria; un approccio tipico è fare uso del metodo statico Class.forName() che forza il caricamento di una classe. Subito dopo si usa un oggetto di tipo Connection per aprire la connessione con un database. Come si vede nell’esempio, per individuare una fonte di dati si usa un URL che specifica il protocollo ed il nome della macchina su cui risiede il database. Il codice SQL viene poi passato ad un oggetto di tipo Statement, che lo verifica e lo traduce in formato comprensibile per JDBC. Infine, il metodo executeQuery() interroga il database e produce un oggetto ResultSet che contiene i risultati e che può essere ispezionato campo per campo. L’accesso ai campi è, come al solito, verificato a runtime ed in caso di errori (per esempio se si cerca di leggere una stringa in un intero) viene generata un’eccezione.
L’esempio seguente dimostra come sia possibile aggiornare i dati in un database:

import java.io.*;

import java.sql.*;
 
 

public class JDBCExample2

  {

    public void start()

      throws Exception

      {

        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

        Connection con = DriverManager.getConnection("jdbc:odbc:dbdemo",

                                                     "user",

                                                     "password");

        Statement st = con.createStatement();

        st.execute("INSERT INTO users (last_name, first_name, address, phone) "

                 + "VALUES('Marco', 'Verdi', 'Via del Fosso', '234234')");

        con.close();

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        JDBCExample2 jdbcExample2 = new JDBCExample2();

        jdbcExample2.start();

      }

  }

Negli esempi precedenti si è supposto di conoscere a priori l’esistenza di una tabella (“users”), composta di righe contenenti un intero ed una stringa. È comunque possibile interrogare dinamicamente un database, sia per quanto riguarda il numero di oggetti contenuti che per la loro composizione, consentendo quindi una grande flessibilità d’uso. A questo scopo esistono due classi speciali, DatabaseMetaData e ResultSetMetaData, che consentono di ispezionare la struttura dei dati piuttosto che il loro valore. Vediamo un semplice esempio di interrogazione dinamica:

import java.io.*;

import java.sql.*;
 
 

public class JDBCExample3

  {

    public void start()

      throws Exception

      {

        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

        Connection con = DriverManager.getConnection("jdbc:odbc:dbdemo",

                                                     "user",

                                                     "password");

        DatabaseMetaData dmd = con.getMetaData();
 
 

//

// QUI ALCUNE RIGHE SONO COMMENTATE PERCHE' SE SI UTILIZZA ACCESS , IL

// DRIVER  NON SUPPORTA PIENAMENTE  METADATA

//

//        ResultSet tables = dmd.getTables(null, "", null, null);
 
 

//        while (tables.next()){

//            String table = tables.getString("TABLE_NAME");

            String table = "users";

            System.out.println("TABLE: " + table);
 
 

            Statement stmt = con.createStatement();

            ResultSet rs = stmt.executeQuery("SELECT * FROM " + table);

            ResultSetMetaData rsmd = rs.getMetaData();
 
 

            int n = rsmd.getColumnCount();
 
 

            for (int i = 1; i < n; i++)

              {

                String col = rsmd.getColumnName(i);

                String typ = rsmd.getColumnTypeName(i);

                System.out.println(col + ":" + typ);

              }

          }
 
 

        con.close();

      }
 
 

    public static void main (String[] args)

      throws Exception

      {

        JDBCExample3 jdbcExample3 = new JDBCExample3();

        jdbcExample3.start();

      }

  }

In definitiva, se si conosce già l’SQL sono necessari pochi minuti per scrivere un programma Java che interagisce con un database. Grazie alla perfetta integrabilità di JDBC con gli strumenti per lo sviluppo rapido di applicazioni (RAD), è possibile creare potenti applet per interagire con database già esistenti in un time to market molto breve.
 

Considerazioni implementative
Il problema, a questo punto, è che i browser, per motivi di sicurezza, consentono ad un applet di collegarsi in rete solo con il server dal quale è stato scaricato (i browser più recenti consentono, o lo faranno a breve,  di superare questo vincolo introducendo il concetto di trusted applet per mezzo di firme digitali; ma in questo momento non considereremo questo approccio).
Questo vincolo ci costringe di fatto a mettere il server WWW sulla stessa macchina dove risiede il database da consultare. Spesso questa non è una soluzione accettabile: può capitare di dover accedere contemporaneamente a database installati su macchine diverse, dove magari non è possibile installare un server WWW, tipicamente per motivi di sicurezza (ad esempio si vuole mantenere il database dietro il proprio firewall).
Una possibile soluzione consiste nell’utilizzare la cosiddetta “architettura client/server a tre strati”: tra il client (visualizzazione) ed il server (implementazione del database) si inserisce un livello intermedio che fa da “filtro”, implementando quelle che vengono chiamate “business rules”, cioè gli algoritmi che decidono quali dati visualizzare, in funzione del tipo di applicazione che si sta considerando.
Mentre il database (o i database) rimangono sulle macchine dove sono stati installati, è sufficiente scrivere una piccola applicazione in Java su una terza macchina (quella centrale), che è poi quella dove è stato installato il server WWW. È quest’applicazione, che non essendo un applet non ha limiti di operatività, ad effettuare l’interrogazione del database vera e propria, mentre l’applet si appoggia ad essa per ottenere i dati di cui hanno bisogno. Questa soluzione offre molti vantaggi:

1. Non richiede nessuna modifica e/o aggiornamento né ai database già esistenti né alle macchine sulle quali risiedono.
2. Permette di utilizzare driver JDBC scritti in codice nativo, che vanno installati solo sulla macchina intermedia e non devono essere così distribuiti sui client (dove, tra l’altro, si avrebbero evidenti problemi di portabilità).
3. Permette di centralizzare sulla macchina intermedia i controlli sulle politiche di accesso, per cui solo la macchina intermedia ha necessità di penetrare l’eventuale firewall che protegge i database.
 

 

In conclusione

Per questo mese interrompiamo qui l’analisi delle caratteristiche avanzate di  Java, dato che in effetti gli argomenti trattati sono molti. Rimandiamo l’appuntamento al mese prossimo per terminare questa trattazione introduttiva sulle tecnologie avanzate di Java.

  

Fabrizio Giudici & Giovanni Puliti
 
 


MokaByte Web  1998 - www.mokabyte.it

MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it