BigBasti's Blog About Me & my Digital Lifestyle

13Dez/110

SSL Teil 4: Serverseitige Authentifizierung mit Java

Nachdem wir in den letzen Teilen mehr die Theorie durchgenommen haben, möchte ich jetzt zu dem praktischen Teil kommen und euch zeigen wie man diese Theorie in Code umsetzen kann am Beispiel Java. (.NET wird auch noch folgen)

Wir wollen einfach einsteigen und implementieren erst mal nur die eine Serverseitige Authentifizierung, also muss der Server sich uns gegenüber ausweisen und wir sind in der Pflicht sein Zertifikat anzunehmen oder abzulehnen.

Wie in Teil 1 erklärt benötigen wir also zunächst einen Ort wo wir die Zertifikate sichern, denen wir vertrauen. Hier können wir (da wir in Java unterwegs sind) nicht die Windows-eigenen nehmen sondern müssen die so genannten Keystores von Java Nutzen.

Ein Keystore, wie der Name schon sagt ist nichts anderes als ein Speicher für Zertifikate. Wenn ihr Java installiert dann legt Java bereits einen Keystore für euch an, diesen findet ihr dann in eurem Java Verzeichnis Java\jre6\lib\security\cacerts die Datei „cacerts“ enthält hier bereits ähnlich wie bei Windows bereits die CA-Zertifikate von den großen Zertifizierungsstellen.

Es wir desweiterhin in Java unterschieden zwischen  Keystore und Truststore, beides sind im Grunde Keystores und unterscheiden sich nur im Namen und in ihrem Inhalt. Eine einzige Datei kann auch beide beinhalten, ist aber eher unüblich, da man die eigenen (privaten) Zertifikate von den CA-Zertifikaten separieren will.

Zuständigkeiten

So enthalten die Keystores für gewöhnlich die eigenen Privaten Schlüssel und die eigenen von anderen CAs signierte Zertifikate, die bei Verbindungen als Serverzertifikate (Public Keys) genutzt werden. Der Truststore ist das Gegenstück und enthält die Zertifikate denen man vertraut. So müssen wir die Zertifikate die wir vom Server beim Verbindungsaufbau erhalten gegen einen Truststore prüfen.

Fangen wir mit unserem Code an, wir wollen einen Client erstellen, der eine Socket-basierte Verbindung zu einem Server über SSL aufbaut und das vom Server zurückgegebene Zertifikat prüft.

Java ist auf diesem Gebiet sehr entgegenkommend und bietet für fast alle Anwendungsfälle fertige APIs und Klassen die uns fast die ganze Arbeit abnehmen.

Um den Prozess besser aufzeigen zu können schreiben wir uns eine Eigene SSLSocketFactory die für uns den Aufbau der Verbindung übernimmt und unserem Programm im Prinzip ein fertiges Socket liefert.

Dazu erstellen wir eine neue Klasse und lassen diese von SSLSocketFactory erben. Da das eine abstrakte Klasse ist müssen eir einige Methoden implementieren. Wenn das erledigt ist sieht unsere Klasse schon so aus:


/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    @Override
    public String[] getDefaultCipherSuites() {

    }

    @Override
    public String[] getSupportedCipherSuites() {

    }

    @Override
    public Socket createSocket(Socket socket, String string,
            int i, boolean bln) throws IOException {

    }

    @Override
    public Socket createSocket(String destinationAddress,
            int destinationPort) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(String string, int i,
            InetAddress ia, int i1) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
            InetAddress ia1, int i1) throws IOException {

    }
}

Nun müssen wir diesen Methoden Leben einhauchen, aber keine Angst das ist nicht so schlimm wie es aussieht, denn die einzige Änderung die wir machen müssen ist es der SSLSocktFactory beizubringen mit unserem Truststore zu arbeiten, der Rest der Funktionalität soll unberührt bleiben.

Fügen wir also einen Konstruktor zu unserer Klasse hinzu der das Initialisieren unserer Internen SSLSocketFactory mit unserem Truststore übernimmt.


/** Delegate {@link SSLSocketFactory}. */
private transient SSLSocketFactory factory;

private File caFile;
private KeyStore ks;

private SSLContext context;
private TrustManagerFactory tmf;
private X509TrustManager defTrustManager;
private SavingTrustManager tm;

/**
 * Neue Truststore-basierte SSL-SocketFactory
 * @param truststoreLocation Vollqualifizierter Pfad zum Truststore oder
			leer lassen für Java-Default-Truststore
 * @param truststorePass Passwort für den Truststore. Wenn leer wird
			"changeit" ala Passwort benutzt.
 */
public ServerSSLAuthSocketFactory(String truststoreLocation,
								  String truststorePass){
	super();

	//Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
	if(!truststoreLocation.isEmpty()){
		caFile = new File(truststoreLocation);
	}else{
		//Pfad zum default Java TrustStore finden
		caFile = new File("cacerts");
		if (!caFile.exists() || !caFile.isFile()) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP
					+ "lib" + SEP + "security");
			caFile = new File(dir, "cacerts");
			if (!caFile.exists() || !caFile.isFile()) {
				caFile = new File(dir, "cacerts");
			}
		}
	}

	//Prüfen, ob ein TrustStore Password angegeben wurde
	String truststorePassword = truststorePass.isEmpty() ? "changeit" : truststorePass;

	//TrustStore öffnen
	try {
		InputStream in = new FileInputStream(caFile);
		ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, truststorePassword.toCharArray());
		in.close();
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (NoSuchAlgorithmException ex){
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (CertificateException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (FileNotFoundException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}catch (IOException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

	try {
		context = SSLContext.getInstance("TLS");
		tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
		context.init(null, new TrustManager[] {defTrustManager}, null);
		factory = context.getSocketFactory();
	} catch (NoSuchAlgorithmException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyManagementException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

}

Das sieht auf den ersten Blick recht komplex aus, ist aber im Grunde recht simpel.

  • In den Zeilen 23 - 28 prüfen wir ob der Konstruktor einen Pfad zu einem Truststore übergeben bekommen hat, und wenn dies nicht der Fall ist, wird der Pfad zu dem Standard-Java-Truststore in eurem JRE-Verzeichnis ermittelt.
  • Direkt danach in Zeile 41 prüfen wir ob ein Passwort übergeben wurde. Wenn keins übergeben wurde wird "changeit" genutzt. "changeit" ist das Defaultpasswort für den Standard-Java-Truststore.
  • In Zeile 45 - 48 öffnen wir den Keystore und laden dessen Inhalt in unsere Kokale KeyStore Variable.
  • Das Wichtigste geschieht nun in Zeile 67 - 72 hier holen wir uns einen Verweis auf den SSLContext (67) und erstellen uns mit Hilfe unseres KeyStores einene TrustManagerFactory(68-69). Anschließend lassen wir die TrustManagerFactory für uns einen neuen X509TrustManager "produzieren" der dann den Verweis auf unseren eigenen Keystore trägt. (70)
  • Nun sagen wir dem SSLContext, dass es unseren Trustmanager nutzen soll (71) für die darüber laufende Verbindungen und erstellen uns eine SSLSocketFactory (72)

Nun haben wir intern eine SocketFactory, die unseren eigenen (über den Konstruktor angegebenen) Keystore nutzt. Der Rest der Klasse die wir erstellt haben dient hierbei eigentlich nur als Wrapper und Durchreiche, der Wichtigste Teil war der Konstruktor.

Die restlichen Methoden können nun also alle Anfragen direkt an die produzierte SSLSocketFactory weiterleiten. So sieht dann die fertige Klasse aus:


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    /** Delegate {@link SSLSocketFactory}. */
    private transient SSLSocketFactory factory;

    private File caFile;
    private KeyStore ks;

    private SSLContext context;
    private TrustManagerFactory tmf;
    private X509TrustManager defTrustManager;
    private SavingTrustManager tm;

    /**
     * Neue Truststore-basierte SSL-SocketFactory
     * @param truststoreLocation Vollqualifizierter Pfad zum
	 *			Truststore oder leer lassen für Java-Default-Truststore
     * @param truststorePass Passwort für den Truststore.
	 *			Wenn leer wird "changeit" ala Passwort benutzt.
     */
    public ServerSSLAuthSocketFactory(String truststoreLocation,
				String truststorePass){
        super();

        //Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
        if(!truststoreLocation.isEmpty()){
            caFile = new File(truststoreLocation);
        }else{
            //Pfad zum default Java TrustStore finden
            caFile = new File("cacerts");
            if (!caFile.exists() || !caFile.isFile()) {
                char SEP = File.separatorChar;
                File dir = new File(System.getProperty("java.home") + SEP
                        + "lib" + SEP + "security");
                caFile = new File(dir, "cacerts");
                if (!caFile.exists() || !caFile.isFile()) {
                    caFile = new File(dir, "cacerts");
                }
            }
        }

        //Prüfen, ob ein TrustStore Password angegeben wurde
        String truststorePassword =
				truststorePass.isEmpty() ? "changeit" : truststorePass;

        //TrustStore öffnen
        try {
            InputStream in = new FileInputStream(caFile);
            ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(in, truststorePassword.toCharArray());
            in.close();
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex){
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }catch (IOException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

        try {
            context = SSLContext.getInstance("TLS");
            tmf = TrustManagerFactory.getInstance(TrustManagerFactory
													.getDefaultAlgorithm());
            tmf.init(ks);
            defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
            context.init(null, new TrustManager[] {defTrustManager}, null);
            factory = context.getSocketFactory();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyManagementException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String string,
					int i, boolean bln) throws IOException {
        return factory.createSocket(socket, string, i, bln);
    }

    @Override
    public Socket createSocket(String destinationAddress,
					int destinationPort) throws IOException, UnknownHostException {
        return (SSLSocket)factory.createSocket(destinationAddress, destinationPort);
    }

    @Override
    public Socket createSocket(String string, int i,
					InetAddress ia, int i1) throws IOException, UnknownHostException {
        return factory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return factory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
					InetAddress ia1, int i1) throws IOException {
        return factory.createSocket(ia, i, ia1, i1);
    }
}

Da wir nun eine Factory haben die uns Sockets liefert, die auf unseren Truststore zugreift, können wir diese nun nutzen um eine Verbindung zu SSL Servern aufzubauen. Das Schöne hierbei ist, dass Java an sehr vielen Stellen SocketFactories nutzt, so können wir fast in allen Szenarien unsere Klasse einsetzen.

So können wir unsere SSLFactory nun ganz simpel testen:


public static void main (String [] args) throws IOException{

    int port = 443; // default https port
    String host = "httpsurl";

    try {
      SSLSocketFactory factory
       = (SSLSocketFactory) new ServerSSLAuthSocketFactory("","");

      SSLSocket socket = (SSLSocket) factory.createSocket(host, port);

      // enable all the suites
      String[] supported = socket.getSupportedCipherSuites();
      socket.setEnabledCipherSuites(supported);

      Writer out = new OutputStreamWriter(socket.getOutputStream());
      // https requires the full URL in the GET line
      out.write("GET https://" + host + "/ HTTP/1.1\r\n");
      out.write("Host: " + host + "\r\n");
      out.write("\r\n");
      out.flush();

      // read response
      BufferedReader in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));

      // read the header
      String s;
      while (!(s = in.readLine()).equals("")) {
          System.out.println(s);
      }
      System.out.println();

      // read the length
      String contentLength = in.readLine();
      int length = Integer.MAX_VALUE;
      try {
        length = Integer.parseInt(contentLength.trim(), 16);
      }
      catch (NumberFormatException ex) {
        // This server doesn't send the content-length
        // in the first line of the response body
      }
      System.out.println(contentLength);

      int c;
      int i = 0;
      while ((c = in.read()) != -1 && i++ < length) {
        System.out.write(c);
      }

      System.out.println();
      out.close();
      in.close();
      socket.close();

    }
    catch (IOException ex) {
      System.err.println(ex);
    }
}

Tragt in Zeile 4 einfach den gewünschten SSL-Host ein wie z.B. Google oder Facebook und startet das kleine Programm. Falls ihr keine Parameter beim Erzeugen der Factory (Zeile 7 und 8) angegeben habt solltet ihr auch keine Probleme beim Verbinden haben und wenn das Programm durchgelaufen ist seht ihr die Antwort des Servers im HTML Format.

Falls ihr doch einen eigenen Truststore eingetragen habt müsst ihr sicherstellen, dass dieser den Public Key des Servers zu dem ihr euch verbinden wollt beinhaltet, sonst wird die Verbindung nicht erfolgen.

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

5Aug/110

Java: SubjectDN eines X509Zertifikates zu OpenSSL kompatibel machen

Java stellt uns zum angenehmen Arbeiten mit X509-Zertifikaten die Klasse java.security.cert.X509Certificate zur Verfügung. Hier können wir uns sehr einfach über die methode .getSubjectDN().getName() den Distinguished Name des Zertifikats besorgen.

Wenn man in der Javawelt bleibt ist das auch alles ok und funktioniert Problemlos. Will man aber andere Programme wie zum Beispiel OpenSSL anbinden, bekommt man hier ein Problem, denn OpenSSL liefert einen anderen SubjectDN String als unsere Java Klasse.

Wobei mit anders meine ich, dass die Reihenfolge der ausgegebenen Parameter anders ist, hier ein Beispiel:

Ausgabe von OpenSSL (über die X509_NAME_oneline Methode):

/C=DE/ST=NRW/L=Bielefeld/O=ORG/OU=AB1/CN=130.122.146.121

Wogegen die Java-Klasse folgenden String liefert (.getSubjectDN().getName()) :

CN=130.122.146.121, OU=AB1, O=ORG, L=Bielefeld, ST=NRW, C=DE

Die Reihenfolge scheint genau umgekehrt zu sein und auch die Trenner sind anders, und wenn man den String nun als Auth-String benutzen will bekommt man ein Problem.

Leider bietet uns Java hier keine Möglichkeit den String anders zu sortieren, man kann zwar über die Methode getSubjectX500Principal.getName(String format) einen RFC als Parameter mitgeben, welcher die Sortierung etwas anpasst, aber teilweise noch komischere Ergebnisse liefert.

Hier bleibt einem nun leider nichts anderes übrig, als selbst einzugreifen und die Sortierung selbst vorzunehmen. Dazu habe ich eine kleine Klasse geschrieben die das für euch erledigt. Diese hat zwei Methoden, eine die einen OpenSSL Kompatiblen String ausgibt und eine bei der ihr die Reihenfolge der Parameter selbst bestimmen könnt:

 


/**
 * Teilt die Informationen aus dem X509Certificate.SubjectDN in die
 * einzelnen Bestandteile auf und macht diese einzeln verfügbar
 * @author sebastian gross blog.bigbasti.com
 */
public class CertificateDataSeperator {

    private String _C = "";
    private String _ST = "";
    private String _L = "";
    private String _O = "";
    private String _OU = "";
    private String _CN = "";
    private String _EMAILADRESS = "";

    /**
     * Erstellt eine neue instanz
     * @param cert Das Zwetifikat aus dem die Daten gelesen werden sollen
     * @param seperator Die Zeichenfolge ander die parameter
     *        getrennt werden sollen, bei Java ist Standard ", "
     */
    public CertificateDataSeperator(X509Certificate cert, String seperator){
        String [] subjectDNData;

        subjectDNData = cert.getSubjectDN().getName().split(seperator);

        seperateData(subjectDNData);
    }

    /**
     * Duchläuft das Array und ordnet die Informationen
     * @param data Array mit dem SubjectDN String
     */
    private void seperateData(String [] data){
        for(String s : data){
            if((s.toUpperCase()).startsWith("C=")){
                _C = s;
            }else if((s.toUpperCase()).startsWith("ST=")){
                _ST = s;
            }else if((s.toUpperCase()).startsWith("L=")){
                _L = s;
            }else if((s.toUpperCase()).startsWith("O=")){
                _O = s;
            }else if((s.toUpperCase()).startsWith("OU=")){
                _OU = s;
            }else if((s.toUpperCase()).startsWith("CN=")){
                _CN = s;
            }else if((s.toUpperCase()).startsWith("EMAILADRESS=")){
                _EMAILADRESS = s;
            }
        }
    }

    /**
     * Generiert einen angepassten SubjectDN String
     * @param params Die gewünschten Werte in der gewünschten Reihenfolge als Array
     * @param seperator Die Zeichenfolge, die zum Trennen der Parameter genutzt werden soll
     * @return Einen angepassten SubjectDN String
     */
    public String getNewDNString(String [] params, String seperator){
        StringBuilder sb = new StringBuilder();

        for(String s : params){
            if((s.toUpperCase()).equals("C")){
                sb.append(_C);
            }else if((s.toUpperCase()).equals("ST")){
                sb.append(_ST);
            }else if((s.toUpperCase()).equals("L")){
                sb.append(_L);
            }else if((s.toUpperCase()).equals("O")){
                sb.append(_O);
            }else if((s.toUpperCase()).equals("OU")){
                sb.append(_OU);
            }else if((s.toUpperCase()).equals("CN")){
                sb.append(_CN);
            }else if((s.toUpperCase()).equals("EMAILADRESS")){
                sb.append(_EMAILADRESS);
            }

            if(!s.equals(params[params.length-1])){
                sb.append(seperator);
            }
        }

        return sb.toString();
    }

    /**
     * Erzeugt einen DN String in dem Format, wie ihn auch die
     * X509_NAME_oneline Methode von OpenSSL generiert, dies ist
     * auch das Format, welches von dem Exim Email Server genutzt wird.
     * Das Forman ist wiefolgt: /C=$/ST=$/L=$/O=$/OU=$/CN=$
     * @return OpenSSL
     */
    public String getOpenSSLCompatibleDNString(){
        StringBuilder sb = new StringBuilder();

        sb.append("/").append(_C).append("/").append(_ST).append("/").append(_L).append("/")
          .append(_O).append("/").append(_OU).append("/").append(_CN);

        //EMAILADRESS Ist ein optionales Feld und könnte nicht gesetzt sein
        if(_EMAILADRESS != null){
            sb.append("/").append(_EMAILADRESS);
        }

        return sb.toString();
    }

    public String getC() {
        return _C;
    }

    public String getCN() {
        return _CN;
    }

    public String getEMAILADRESS() {
        return _EMAILADRESS;
    }

    public String getL() {
        return _L;
    }

    public String getO() {
        return _O;
    }

    public String getOU() {
        return _OU;
    }

    public String getST() {
        return _ST;
    }
}

Mit dieser kleinen Klasse ist es sehr einfach einen OpenSSL kompatiblen SubjectDN zu erhalten:


X509Certificate myCert = session.getClientCertificate();
CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", ");
String dn = sep.getOpenSSLCompatibleDNString();

Wenn man selbst die Reihenfolge der parameter bestimmen will kann man das z.B. so machen:


X509Certificate myCert = session.getClientCertificate();
String [] myParams = new String[]{"O", "CN", "L"};
CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", ");
String dn = sep.getNewDNString(myParams, "|");

Nun soll erst O, dann CN und zum Schluss L ausgegeben, jeweils von einem | (pipe) getrennt.

Das Ganze lässt sich natürlich je nach verwendeten optionalen SSL Parametern noch beliebig erweitern, und ist eine einfache und schnelle Methode um den SubjectDN neu zu sortieren.

4Aug/110

Visual Studio 2010 und das Intellisense im Vergleich

Da ich Beruflich auch viel im Javaumfeld arbeite, komme ich in Kontakt mit verschiedenen IDEs. Am besten gefällt hier das kostenlose NetBeans von Sun Oracle. Und bereits nach kurzer Zeit sieht man die vielen Unterschiede der IDEs, besonders fällt hier aber der Unterschied des Code Completion Features der IDEs auf.

Im Visual Studio haben wir Intellisense kennen- und lieben gelernt, es ist schnell und liefert uns die Informationen die wir haben wollen. Doch schaut man hier mal auf das andere Ufer, wird man schnell feststellen, dass hier doch tatsächlich noch Nachholbedarf besteht.

Schauen wir uns beispielsweise ein typischen Intellisense Pupup an, der uns Informationen zu der Methode Create() der HttpWebRequest-Klasse liefert:

Intellisense Pupup

Intellisense Pupup

Wir bekommen eine Auflistung der uns zur Verfüfung stehenden Methoden (1) und die markierte Methode wirft noch ein weiteres Popup auf, welches uns den Methodenkopf, eine Kurze Beschreibung und die möglichen Exceptions liefert (2).

Eigentlich doch alles was manbraucht, stimmt! Vergleichen wir das Ganze doch mal mit dem "Intellisense" von NetBeans 7:

NetBeans Popup

NetBeans Popup

Was haben wir hier, als erstes taucht, wie im Visual Studio auch, eine Liste mit dem möglichen Befehlen auf (1), diese ist hier etwas großzügiger gestaltet und nimmt etwas mehr Platz auf dem Bildschirm ein, liefert uns aber auch auf den ersten Blick den Return-Typ der Funktion (das was rechtsbündig in der Liste steht) - Im VS müsste man erst die Methode auswählen und auf den "Hilfspopup" warten um an diese Information zu kommen.

Als zweites taucht auch hier ein weiteres Popup auf, mit Details zur Funktion an sich (2). Und das ist der Teil wo es interessant wird, denn hier bekommen viel mehr Informationen als im VS.
Als erstes fällt auf, dass es viel mehr zu lesen gibt, hier wird nämlich das dazugehörige Javadoc geladen und angezeigt, dieses bietet natürlich eine Fülle an Informationen und sogar Beispielcode für die gerade gewählte Funktion.

Um diese Informationen im VS zu erhalten müssen wir die Funktion markieren und dann [F1] drücken, damit sich dann der Browser öffnet und uns zu dem passenden Artikel auf MSDN navigiert, wo wir dann alle nötigen Infos inkl. Beispiele finden, aber warum der Umweg?

Als nächstes fällt auf, dass das in dem zweiten Popup auch Links auftauchen (3), diese kann man natürlich auch anklicken und man wird innerhalb des kleinen Fensters zu dem geklickten Dokueintrag weitergeleitet - das ist wirklich sehr sehr praktisch! Dazu kommt noch oben eine kleine Navigation (4) um zwischen den besuchten Seiten zu wählen, den Artikel im Browser zu öffnen oder direkt zu der gewählten Klasse zu wechseln.

Das macht wirklich Spaß mit dieser Hilfe zu arbeiten und man kann schnell die nötigen Informationen nachschlagen ohne überhaupt die Maus bewegen zu müssen.

Natürlich bin ich mit dessen bewusst, dass es auch für das VS Erweiterungen gibt wie ReSharper oder CodeRush, die sich dieser "Schwäche" annehmen und versuchen es zu verbessern, diese kosten aber erstens Geld und machen aber auch nicht alles perfekt.

Aber es gibt auch Sachen die mich umgekehrt an NetBeans extrem stören, wie zb. die Tatsache, dass NetBeans sich nicht mehrkt welchen Befehl ich als letztes benutzt habe.

Sehr sehr nervig!

Wenn wir das obere Bild anschauen, will ich die methode callVRFY() aufrufen und das mehr fach hintereinander. NetBeans wird mir jedes Mal den Vorschlag genau so machen wie wir ihn hier sehen, ich muss also jedes Mal 12x [Pfeil nach unten] drücken um den Eintrag auszuwählen oder den Namen direkt voll ausschreiben.

Visual Studio reagiert hier deutlich intelligenter, denn es merkt sich, dass man diese Methode nun 2x benutzt hat und schlägt beim nächsten Mal wenn man "call" tippt direkt die callVRFY() Methode als erstes vor.

Eine weitere Sache die bei Visual Studio genial ist, ist die Tatsache, dass ich eigentlich den "call"-Teil des Methodennamen überspringen kann und direkt anfangen kann "VRFY" zu tippen, Intellisense würde dennoch erkennen, dass ich die "callVRFY" Methode meine und diese für mich auswählen.

Beispiel:

Visual Studio Popup

Wie man sieht schlägt mit VS hier alle Klassen & Methoden vor die "response" im Namen haben. - Super!

Das sind nun natürlich zwei "Kleinigkeiten" die ich mir hier herausgepickt habe, aber glaubt mir wenn man plötzlich auf diese verzichten muss ist das mehr als nervig!

Zum Schluss kann ich sagen, dass beide IDEs von ein an der lernen können und dass ich, egal mit welcher IDE ich gerade arbeite das ein oder andere Feature aus der anderen IDE vermisse. Bleibt noch zu hoffen, dass die Entwickler sich gegenseitig auf die Finger schauen und diese netten kleinen Features gegenseitig "ausleihen" ;)

Wie steht ihr dazu? Seit ihr mit dem Intellisense glücklich, nutzt ihr Erweiterungen (ReSharper etc..)?

6Jan/112

Java: Webservice anlegen und konsumieren

Aus gegebenen Anlass gibt es heute mal ein kleines Tutorial wie man in Java ein WebService anlegt und diesen dann benutzt. Das ganze wird mit NetBeans 9.6.1 als IDE und einem Glassfish als Application Server realisiert.

Erstmal kurz: was ist ein WebService?
Ein WebService unter Java ist (vergleichbar mit RMI) eine Art Dienst, der sowohl von anderen Anwendungen als auch von Webseiten genutzt werden kann. Anders als eine gewöhnliche Webseite besitzt ein WebService kein eigenes Benutzerinterface sondern stellt nur in einer Schnittstellenbeschreibung seine Dienste nach außen zur Verfügung.

Diese Schnittstellenbeschreibungen werden in WSDL Dateien (Web Service Description Language) gespeichert, diese sind XML Dokumente, die genau beschreiben, welche Methoden der WebService bietet und wie diese angesprochen werden. Glücklicherweise müssen wir diese WSDL Dateien nicht selbst erstellen sondern können uns diese generieren lassen, aber dazu gleich mehr.

Kommunikation mit dem WebService
Unser WebService wird dann später über das SOAP Protokoll (Simple Object Access Protocol) kommunizieren, dieses ist ebenfalls XML basierend und auch hier müssen wir nicht selbst Hand anlegen sondern lassen alles für uns generieren ;)

In diesem Beispiel werden wir einen einfachen WebService erstellen und eine gewöhnliche App, die dann mit dem WebService kommunizieren wird. Wir werden hier die NetBeans IDE 6.9.1 benutzen, diese wird uns hierbei viel Arbeit abnehmen, sodass der unangenehme Teil der Arbeit klein gehalten wird ;)

Downloads - falls nicht bereits vorhanden
NetBeans - Download
Glassfish - Download
JDK - Download

Legen wir mal los

Als Erstes sollten wir damit starten den WebService zu erstellen. Dazu legen wir ein neues Web Application Projekt an und nennes es "CheckUser" die Restlichen Dialoge könnt ihr unverändert lassen und schließlich auf Fertigstellen klicken.

Nun sollte das Projekt angelegt sein und ihr solltet den Quelltext der index.jsp vor euch haben. Diese Datei benötigen wir nicht da wir einen WebService erstellen wollen, deswegen könnt ihr diese Löschen, wenn ihr mögt.

Nun müssen wir einen neuen WebService anlegen, das machen wir, indem wir auf unser Projekt rechtsklicken und über "New -> WebService" wählen. Nennt den Service "UserDB" und packt es in das "com.bigbasti.db.user.service" Package rein. Zum Schluss macht ihr noch den Haken bei "Implement Web Service as Stateless Session Bean" rein und klickt auf Fertig.

Was ist nun passiert? NetBeans hat für uns nun einen neuen Ordner im Projekt namens "Web Services" angelegt und auch eine neue Enterprise Bean die so heißt wie unser Service ist im Projekt aufgetaucht. NetBeans kümmert sich größtenteils selbst um diese automatisch generierten Beans so sollten wir erst einmal die Finger davon lassen.

Nun solltet ihr die UserDB.java vor euch aufgeklappt sehen, diese Klasse ist mit der Annotation Stateless und WebService markiert. Da ein WebService mindestens eine Methode (Operation besitzen muss wird euch wahrscheinlich ein Fehler angezeigt. Das wollen wir nun ändern und fügen dem Service eine neue Operation hinzu.

Macht dazu einen Rechtsklick innerhalb der UserDB Klasse und klickt auf "Insert Code..." wählt hier nun "Add Web Service Operation...". In dem nun angezeigten Fenster solltet ihr eure Operation "checkUserName" nennen und den Rückgabetyp auf Boolean stellen. Desweiteren fügt ihr einen Parameter hinzu und tauft ihn "userName".

Bestätigt das Fenster mit "OK" und ihr solltet folgendes zu sehen bekommen:

Diese neue Methode ist mit "WebMethod" annotiert und weist so darauf hin, dass auf diese von außen zugegriffen werden darf. Alle Parameter sind ebenfalls annotiert sodass diese zugreifbar sind. Diese Annotation ist wichtig, damit wir später WSDLs und Requests generieren können. Beim Arbeiten mit dieser Methode könnt ihr diese aber für euch ausblenden ;)

Diese erzeugte Methode funktioniert genauso wie jede andere Java Methode, somit könnt ihr mit ihr wie gewohnt arbeiten. Diese Methode soll für uns prüfen, ob ein Benutzername bereits von jemandem verwendet wird. Da wir das simel halten wollen nutzen wir eine LinkedList als Datenbank :) So können wir eine einfache Prüffunktion implementieren:

Nun haben wir einen WebService mit einer Operation. Diese wollen wir nun mal testen. Dazu verwenden wir zunächst die Test-Tools vom Glassfish Server. Dazu müssen wir unsere Web Application zunächst Deployen (Veröffentlichen), das machen wir ganz einfach indem wir das Projekt rechts-klicken und dann "Deploy" wählen. Nun wird der Glassfish Server gestartet, die Anwendung kompiliert und auf dem Server veröffentlicht.

Wenn dieser Vorgang erfolgreich war könnt ihr im "Services"-Tab unter "Servers->GlassFish Server 3" euere Web App sehen (Siehe Bild 11). Nun wollen wir mal gucken ob unsere Operation funktioniert, dazu macht ihr einen Rechtsklick auf dem "GlassFish Server 3" und wählt View Admin Console (Bild 12).

Nun könnt ihr euch einen Kaffee holen...

Wenn die Admin Console irgendwann offen ist, und es kann dauern, klickt ihr auf "Anwendungen" und wählt eure "CheckUser" App. In dem "Allgemein"-Tab seht ihr nun unten eine Tabelle mit allen Komponenten eurer App, sucht hier unseren Service "UserDB" und klickt auf "Endpunkt anzeigen".

Hier seht ihr nun alle wichtigen Informationen zu eurem Web Service, für uns ist im Moment die Tester URL wichtig (Roter Pfeil) Diese öffnen wir mal in einem neuen Tab.

Was wir hier sehen ist die Testseite für unseren Dienst. Hier hat der Glassfish für jede von uns erstellte Operation (momentan haben wir nur eine) eine Testmethode erstellt. Diese Testmethode macht nichts anderes, als eine SOAP Nachricht zu generieren und an unseren Dienst zu senden und uns die Antwort von diesem anzuzeigen.

Probieren wir das Ganze mal aus, probiert einfach mal ein paar Namen durch und verwendet auch ab und zu einen der von uns Reservierten Namen, ihr werdet feststellen, dass unsere Methode super funktioniert.

Dabei zeigt das Test-Tool auch an welche SOAP Requests es generiert und welche Antworten unser Service gibt, so wird folgender SOAP Aufruf getätigt um zu prüfen, ob der Name vorhanden ist:

Hier finden wir alle unsere Werte aus der Klasse wieder. Der Name der Operation die wir mit diesem Request aufrufen wollen (Rot), diese Operation benötigt einen Parameter namens "userName" den wir hier angeben (Blau) und der Wert für diesen Parameter (Orange). Das Ganze ist dann noch in einen SOAP Envelope verpackt und wird so zum Server geschickt.

Dieser führt die gewünschte Operation aus und gibt uns eine Antwort:

Die Response ist weitestgehend genauso aufgebaut die der Request. Der Knoten hat nun noch "Response" an den Namen der Methode/Operation angehängt bekommen (Rot) und auch das Ergebnis der Operation ist angegeben (Blau).

Das Ergebnis können wir nun mit Hilfe eines Parsers auslesen, aber dazu gleich mehr. Nach dem Test wissen wir nun, dass unser dienst läuft und auch dass die Operation zuverlässig arbeitet, Zeit unsere "Consumer"-Applikation zu bauen.

Web Service benutzen (konsumieren)

Nun haben wir also einen Web Service laufen, und natürlich wollen wir diesen nun auch nutzen (konsumieren ist das Fachwort), aus einer App mit der sich Benutzer irgendwo registrieren können. Diese App muss natürlich irgendwie prüfen, ob der gewünschte Benutzername schon vorhanden (bzw. bereits benutzt) ist, das wird die App über unseren Web Service UserDB erledigen.

Legen wir zunächst ein neues Projekt in NetBeans an, diesmal eine normale Java Application, die wir "RegisterUser" nennen.

Nun müssen wir unserer frischen Applikation beibringen mit einem WebService zusammen zuarbeiten. Das macht man über so genannte Schnittstellen, denn diese beschreiben genau was möglich ist und wie man da dran kommt. Schnittstellen haben hier das Format WSDL, und wenn ihr gut aufgepasst habt habt ihr sicher schon gesehen dass der GlassFish für unser Service eine WSDL erstellt hat.

Geht die Schritte in den Bildern 13,14 und 15 durch (falls ihr den Tab schon geschlossen habt), sodass ihr wieder die URLs des Services angezeigt bekommt. Der Blaue Pfeil im Bild 15 zeigt euch die URL der WSDL, diesen link klickt ihr bitte an, sodass ihr die XML Datei zu Gesicht bekommt.

Auf den Inhalt werde ich hier nicht weiter eingehen, das könnt ihr euch ja bei Wiki durchlesen ;) Die URL zu der WSDL sieht bei mir wie folgt aus:

http://localhost:8080/UserDBService/UserDB?wsdl

Diese könnt ihr mal in die Zwischenablage kopieren, da wir diese gleich benötigen um einen Web Service Client zu erstellen. Das machen wir mit einem Rechtsklick auf das Projekt und wählen "New->Web Service Client". Hier wählt ihr als Bezugsort für die WSDL "URL" aus und fügt die URL ein. Als Paketnamen habe ich "com.bigbasti.user.register" gewählt.

NetBeans lädt nun die WSDL herunter und erzeugt daraus für euch die nötigen Klassen für den Zugriff auf diese Schnittstelle, deswegen sind einige neue Dateien bei euch im Projektordner aufgetaucht (Bild 24). Der etwas ausgegraute Ordner "Generated Sources" enthält hierbei die wichtigen Klassen und Methoden für das Erzeugen von SOAP Requests und für das Lesen von Responses. Diese Dateien solltet ihr auch möglichst nicht anfassen.

Nun sind wir komplett gerüstet um unseren Web Service zu nutzen. Der Code der dafür geschrieben werden muss ist auch nicht weltbewegend:

Als Erstes sind die imports wichtig, damit wir Zugriff auf die generierten Klassen haben. Mit denen können wir nun (Blaue Pfeile) die nötigen Instanzen der Service Objekte anlegen. Ist das erst geschafft können wir mit unserem UserDB Objekt, das den Service repräsentiert bequem arbeiten und die Service Operationen aufrufen, als ob es eine gewöhnliche kleine Klasse wäre (Oranger Pfeil).

Nachdem wir unsere Operation aufgerufen haben bekommen wir eine Antwort ob der Name schon benutzt wird oder nicht. So könnte es aussehen wenn man das obige Programm startet:

Wie man sieht funktioniert das Ganze sehr gut und es hat gerade mal ein paar Minuten gedauert diesen Service zu erstellen. Ihr könnt natürlich selber weiter herumspielen und weitere komplexere Operationen hinzufügen, die mit echten Datenbanken kommunizieren.

5Jan/113

Unbegrenzte Anzahl an Parametern uebergeben

Mir ist aufgefallen, dass viele gar nicht wissen, dass man Methoden so gestalten kann, dass diese eine unbestimmte menge an Parametern akzeptieren. Das gibt es sowohl in Java als auch in .NET (und wohl jeder anderen höheren Programmiersprache)

Wie sieht das Ganze aus? Naja wenn ihr schon mit C#/.NET Gearbeitet habt werdet ihr wohl nicht um diese Funktion herum gekommen sein:


string var = String.Format("Hallo mein Name ist {0} und ich komme aus {1}", "Bernd", "Kassel");

Die Zahlen in den Geschweiften Klammern sind hierbei Platzhalter, die später über eine Variable gefüllt werden. Von diesen Platzhaltern kann man beliebig viele angeben, dementsprechend müssen dann aber auch genauso viele Parameter mit den Werten angegeben werden.

Wie sieht hier die Deklaration dieser Methode aus?


public void DoSomething(int someValue, params string[] values)
{
    foreach (string value in values){
        Console.WriteLine(value);
	}
}

Diese Methode nimmt nun einen normalen int Parameter an und ab dann können beliebig viele Werte vom Typ String folgen:


DoSomething(12345, "Hallo", "Wer", "Bist", "Du");

Genauso kann man das auch in Java machen, hier ist es sogar etwas schöner gelöst wie ich finde:


//Methoden Deklaration
public DoSomething(int someValue, String... values) {
	for(String vaule : values){
		System.out.println(value);
	}
}

//Nutzung:
DoSomething(12345, "Hallo", "Wer", "Bist", "Du");

In Java werden hier nach dem Variablentyp drei Punkte angehängt die signalisieren, dass dieser Typ nun beliebig oft folgen kann.

Dabei wird intern aber auch nur ein Array mit den Werten erzeugt, das kann man bei der .NET Variante bereits in der Deklaration erkennen. So lässt sich in Java die main-Methode auch so schreiben, ohne dass es zu Problemen kommt:


public static void main(String... args) {
    for(String s : args){
        System.out.println(s);
    }
}

Es sei an dieser Stelle noch erwähnt, dass der Parameter, der beliebig oft folgen kann immer als letztes angegeben werden muss - aus offensichtlichen Gründen ;-)

Diese Schreibweise kann an einigen Stellen für deutlich bessere Lesbarkeit sorgen und vereinfacht einfach nur die Anwendung der Funktionen, sollte man auf jeden Fall schon mal gesehen haben.

5Jan/111

Fehler: Web Services testen unter NetBeans 6.9.1 (mit Glassfish 3)

Gestern habe ich ziemlich viel Zeit damit verbracht einen ziemlich nervigen Bug in NetBeans 9.6.1 zu umgehen, der auftritt, wenn man WebServices testen möchte.

Wenn ihr einen eigenen WebService anlegt wie zum beispiel in diesem NetBeans Tutorial beschrieben und diesen anschließend mit den Glassfish-eigenen Testern testen wollt werdet ihr diesen Fehler hier sehen:

Service Testen schlägt fehl

Service Testen schlägt fehl

Wie es sich nun herausstellte machen wir als User keinen Fehler, sondern NetBeans, denn NetBeans generiert für uns eine Falsche URL, die er dann dementsprechend nicht aufrufen kann und uns diesen Fehler anzeigt.

Interessant an dieser Stelle ist auch, dass dieser Fehler nur in Verbindung mit Stateless WebServices auftritt. Denn wenn man die @Stateless Annotation aus dem Code entfernt scheint es doch zu gehen.

Da man aber im Normalfall alle Services stateless sind, müssen wir uns die passende URL selber basteln. Die korrekte URL ist nämlich so aufgebaut:

http://localhost:8080/[ServiceName]/[ServiceKlasse]?Tester
in meinem obigen Fall wäre es dann

http://localhost:8080/myServiceService/myService?Tester

Die korrekte URL findet ihr übrigens auch auf der Glassfish Konsole im Bereich Anwendungen->[Anwendung]->[Euer WebService]->Endpunkt Anzeigen - hier stehen alle wichtigen URLs (WSDL, Tester, Service)

Das ist natürlich etwas blöd, da diese Praktische Funktion von NetBeans die sehr einfach mit nur einem Rechtsklick erreichbar ist nicht funktioniert, aber gut was solls, schreiben wir die URLs halt selbst ;)

Dieses Problem tritt bei der 7.0 Beta von NetBeans übrigens nicht mehr auf.

13Dez/102

Java: New oder nicht New?

Heute gibt es wieder mal nach langer Zeit ein Java Thema. Dieses Thema ist auf den ersten Blick sehr trivial und man schreibt das bestimmt vielfach am Tag ohne drüber nachzudenken.

Konkret gehts heute um return-Statements und auf welche Art diese einen Wert zurück geben. Ein Beispiel soll dies verdeutlichen, nehmen wir an wir haben eine toString() Methode. Diese kann man auf 2 Arten implementieren:


	public String toString(){
		return "hallo";
	}

	public String toString2(){
		return new String("hallo");
	}

Der Unterschied liegt auf der Hand, in der ersten Methode geben wir direkt eine Zeichenfolge zurück und in der zweiten erzeugen wir erstmal ein Objekt von Typ String und geben das dann zurück. Funktioniert beides, aber was benutzt du, und warum?

Denn so unscheinbar das auch aussieht gibt es hier Unterschiede, denn je nachdem welche der beiden Funktionen wir ausführen bekommen wir etwas anderes, ein Beispiel:


		String t1 = toString();
		String t2 = toString();

		String b1 = toString2();
		String b2 = toString2();

		System.out.println(t1.equals(t2));	//true
		System.out.println((t1 == t2));		//true

		System.out.println(b1.equals(b2));	//true
		System.out.println((b1 == b2));		//false

		System.out.println(b1.equals(t1));	//true
		System.out.println((b1 == t1));		//false

Interessant sind hier immer jeweils die zweiten Ausgabezeilen, denn diese sagen, dass wir den == Operator nicht benutzen dürfen und bekommen somit unterschiedliche Referenzen auf verschiedene Objekte zurück.

Nur nicht im ersten Fall, wo wir das Ergebnis ohne new zurück geben, dort können wir den == Operator verwenden - demnach bekommen wir immer exakt die gleiche Referenz zurück egal wie oft wir diese Methode aufrufen, aber wie kann das sein?

Die Antwort liegt wieder in den Grundlagen :) denn auch in der ersten Methode geben wir ein Objekt vom Typ String zurück. Dieses Objekt wird erzeugt wenn die Klasse gestartet wird und ist ab dann Klasseninventar, was unter anderem heißt dass es exakt nur ein einziges Mal vorhanden ist.

Daher geben alle Abfragen dieses Strings immer die eine Referenz auf diesen Klassenstring zurück, was dafür sorgt dass wir dem == Operator hier anwenden dürfen und auch folgendes funktioniert:


String a = "hallo";
String b = "hallo";

System.out.println(a == b);	//true

Da Strings in Java ebenfalls immutable ist können diese ihren Inhalt nicht ändern, womit die Referenz immer die selbe sein wird.

Warum nun das Ganze? Ich wollte einfach auf diesen kleinen aber doch wichtigen Unterschied hinweisen, da es da zu Fehlern kommen kann wenn man Ergebnisse vergleichen will und man das nicht weiß.

6Sep/104

Java und C# – Die feinen Unterschiede

Immer wieder begegnen mir Leute, die Java und C# in die selbe Schupp-lade stecken. Man hört dann meist Sätze wie "C# ist Java von Microsoft", "Dann lerne ich doch gleich lieber Java statt Microsofts Kopie". Jeder der sich mit der .NET Plattform beschäftigt wird natürlich wissen, wie falsch diese Aussagen sind, die meist nur auf alten Vorurteilen und Unwissenheit basieren.

Die Anfänge

Natürlich haben diese Vorurteile irgendwo etwas wahres dran, so ist es nicht von der Hand zu weisen, dass sich die Syntax der beiden Sprachen sehr sehr ähnlich ist, und auch die Ausführung des Codes in einer VM kann man auch zu den Gemeinsamkeiten zählen. So ist C# ja auch entstanden, als eine Mischung aus Java und C++. Dies sollte es den Leuten erleichtern auf die neue Plattform zu wechseln und schnell einen Einstieg zu finden, desweiteren hat es sich in der Technischen Welt bewährt gute und erfolgreiche Konzepte zu kopieren und zu perfektionieren/verbessern. So würde ich auch C# mehr als eine Weiterentwicklung von Java betrachten.

Wo liegen die Unterschiede?

Natürlich fragt man sich dann, wo die Unterschiede sind und wenn man sich etwas damit befasst, wird man feststellen, dass es doch ziemlich viele Unterschiede und (meiner Meinung nach sehr nützliche) Weiterentwicklungen gibt.

Hier ein paar Beispiele:

  • Properties - Saubere Model-Klassen ohne einen riesen GetBla & SetBla Gestrüppe
  • Delegates (Funktionszeiger) - Übergabe von Funktionen als Parameter uvm.
  • LINQ (Language Integrated Queries)  - Objektorientierte SQL/XML-Abfragen
  • Events - Erleichtert strickte Trennung von Schichten und Informationsweitergabe
  • Lambda Ausdrücke - Viel Arbeit auf wenig Platz (Wird eventuell im JDK 1.7 Einzug halten)
  • Enums und Colections

Desweiteren gibt es auch wichtige Unterschiede, die unter der Haube ablaufen:

  • Assembly-Konzept - Einfache und schnelle Interaktion zwischen den .NET Sprachen wie C#, Visual C++.Net, Visual Basic.Net oder Delphi.Net
  • Namespaces - Auf den ersten Blick sehr ähnlich zu Packages , aber nicht auf den zweiten ;-)

Natürlich gibt es noch vieles mehr was an Unterschieden zu entdecken gibt. Wer da interessiert ist kann dich mal diese Seite anschauen mit vielen Beispielen und Vergleichen.

Einsatzbereiche

Sogar in den Einsatzgebieten ähneln sich die Plattformen sehr, denn wie mit Java kann man auch in .NET sowohl für Desktop, für Web und für mobile Plattformen entwickeln, wobei die mobilen Plattformen noch auf Windows Mobile beschränkt ist. (Aber Mono für Android ist ist ja auch schon unterwegs)

Dank Mono kann man viele .NET Programme auch sehr einfach auf Unix Systeme portieren, womit man teilweise auch die Plattformunabhängigkeit von Java erreicht. (Mono unterstützt zur Zeit das komplette .NET Framework 2.0)

Die Zukunft

Microsoft entwickelt die .NET Plattform sehr aktiv. Vor Kurzem ist die 4te Version des Frameworks inklusive einer neuen Visual Studio Version erschienen. Weitere Releases und Weiterentwicklungen dürften somit auch bald folgen.

Bei Java schaut man momentan noch in eine ungewisse Zukunft, da Sun vor einer Weile von Oracle aufgekauft wurde, weis man nun nicht mit Sicherheit was aus den Produkten wird. So musste die Community bereits den Verlust von Open Solaris hinnehmen und es hagelte auch schon die ersten Patentklagen wegen dem Aufbau der Java VM auf verschiedenen Plattformen. - Keine Rosigen Aussichten für Java Fans.

So scheinen nun sich die Feindbilder zu ändern. Lange Zeit war Microsoft wohl der Anführer der Bösewicht Nummer 1 - Liste, doch das Unternehmen hat sich gewandelt und stellt inzwischen sogar zu vielen Projekten sogar den Code zur Verfügung, so ist zum Beispiel ein großer Teil des .NET Frameworks einsehbar und auch das ASP.NET MVC Framework lässt sich komplett einsehen.

Die .NET Plattform ist inzwischen eine sehr mächtige geworden, und alle die es runterspielen oder ignorieren kann ich einfach nicht ernst nehmen. Und auch wenn man ein Open-Source-Junkie oder einfach nur ein Microsoft-Feind ist sollte man dennoch wissen, wovon man redet.

18Feb/100

Interessantes aus der Java Welt Teil 2

In diesem Artikel möchte ich kurz was über statische Objekte in Java erzählen. Das ist ein sehr interessantes Thema, dass oft zu unerwarteten Fehlern führen kann.

Ich möchte hier zunächst mit einem kleinem Beispiel anfangen. Lest euch diesen kurzen Code durch und schaut mal ob ihr sagen könnt ob dieser kompiliert werden kann, wenn nein warum nicht und wenn ja was kommt raus?

Ernsthaft, nehmt euch eine Minute Zeit:


public class Demo {

	static String text = "Ich bin die Klasse Demo";

	public Demo getInstance(){
		System.out.println("Ich bin die Methode");
		return null;
	}

	public static void main(String [] args){
		System.out.println(new Demo().getInstance().text);
	}
}

Lasst uns den Code mal gemeinsam durchgehen. Wir deklarieren eine statische Klassenvariable vom Typ String und initialisieren diese auch gleich, desweiteren haben wir eine Methode getInstance() die einen Text auf der Konsole ausgibt und dann den Wert null zurückgibt!

Als letztes folgt die main()-Methode die das Programm ausführt, schauen wir uns diese etwas genauer an, denn hier ist der interessante Teil!

In der Main()-Methode wird eine Instanz der eigenen Klasse angelegt und dann die Funktion getInstance() ausgeführt, soweit alles ok, aber nun gibt die getInstance-Methode null als Rückgabewert zurück, also wird hier eine komplett "leere" Klasse zurückgegeben, und aus dieser "leeren" Klasse lesen wir nun die Variable text aus und geben diese auf Der Konsole aus.

Kann das Funktionieren? Kann ich eine Variable aus einer nicht instanzierten "leeren" Klasse benutzen? - Ja das können wir. Dementsprechend erhalten wir folgendes im Konsolen Fenster:


Ich bin die Methode
Ich bin die Klasse Demo

Doch warum ist das so? Dazu muss man wissen, dass es so etwas wie das Klasseninventar und Objektinventar gibt. Alle Statischen Objekte gehören zum Klasseninventar und alle nicht statischen zum Objektinventar.

Wo ist der Unterschied? Worin unterscheidet sich eine Klasse von einem Objekt? - Ein Objekt entsteht aus einer Klasse, daher kann es immer nur eine einzige Klasse geben, aber mehrere Objekte.

Schauen wir unsere Demo-Klasse mal an. Wir können beliebig viele Demo-Objekte aus der Demo-Klasse erstellen aber die Klasse selbst bleibt einzigartig.

Und genau das selbe geschieht auch mit den Objekten des Klasseninventars, alle diese Objekte sind nur ein einziges Mal vorhanden! Das heist, auch wenn man 100 Objekte vom Typ Demo anlegt hat man trotzdem nur eine einzige text-Variable! - Man braucht eine Weile um das zu schlucken, das gebe ich zu!

Aber vielleicht wird es etwas ersichtlicher an einem Beispiel. Nehmen wir an, wir programmieren mit Java eine Besucherzähler Klasse. Diese Klasse wird jedes Mal erzeugt wenn ein Benutzer eine Webseite aufruft und erhöht den Wert einer Zähler Variable um 1.

Da aber jeder Besucher seine eigene Instanz der Klasse bildet hat jeder Benutzer sein eigenes Besucherzähler Objekt. Wenn die Variable, die für das Zählen der Besucher zuständig ist nicht Statisch ist, hat jeder Benutzer eine neue - eigene - Variable die erhöht wird, und wenn mehrere Benutzer zur selben Zeit auf der Seite sind erhöhen Sie NUR ihre eigene Variable aus dem Objektinventar und nicht die allgemeine Variable.

Man muss hier also die Zähler Variable statisch machen, damit alle Objekte, egal wie viele es sind, immer auf die selbe Variable zugreifen, nämlich die aus dem Klasseninventar!

Kommen wir zurück auf das obige Beispiel, denn dieses funktioniert nur, da die text-Variable Statisch ist und somit im Klasseninventar liegt. Entfernt man das Wörtchen "static" vor der Definition, wird das Programm mit einer NullPointerException enden:


Ich bin die Methode
Exception in thread "main" java.lang.NullPointerException
	at Twitter.Demo.main(Demo.java:11)

Denn nun liegt die text-Variable im Objektinventar und da das Objekt mit null angegeben ist, also "leer" ist endet der Aufruf in einer Exception.

Soviel dazu, ich hoffe diese kleine Theoriestunde hat euch gefallen. Ich würde mich über Feedback (und evtl. Verbesserungen) freuen, wollt ihr zukünftig mehr solcher Themen hier haben?

29Jan/109

Tutorial: Model-View-Controller (MVC) Struktur in Java Projekten nutzen

Die MVC-Architektur erfreut sich in letzter Zeit sehr hoher Beliebtheit, ständig hört man über neue Frameworks die auf diese Architektur schwören. (Beispiele: Spring Framework, ASP.NET MVC, Objective C...)

Bild 1: Der Aufbau - Quelle: Wikipedia

Doch was steckt eigentlich dahinter und warum sollte man seine Struktur überdenken?

Ich denke jeder, der eine Anwendung geschrieben hat schon die Erfahrungen gemacht, dass der Code immer unübersichtlicher wird, je größer ein Projekt wird.

Hier setzt MVC an, denn durch die strickte Teilung von Präsentation (View), der Programmlogik (Controller) und der Datenschicht (Model) erhöht sich die Lesbarkeit und Wartbarkeit des Codes. So kann man ein Projekt viel einfacher um weitere Funktionen ergänzen ohne den halben Sourcecode durchzugucken.

In diesem Artikel möchte ich diese Architektur an einem kleinem Java Beispiel demostrieren. Das Programm ist natürlich nicht wirklich zu gebrauchen, es soll lediglich das Vorgehen nahelegen.

Wie bereits oben erwähnt soll die Präsentation, also das was der Benutzer zu sehen bekommt (üblicherweise das Formular, also die GUI) von der Logik getrennt werden. Deshalb dürfen in der View-Klasse nur Sachen stehen, die der Darstellung der Elemente dienen. Das was passiert wenn man eins dieser Elemente anklickt wird dann über den Controller gesteuert.

Die Berechnungen an sich stehen dann im Model, wo auch alle Objekte verweilen, die unser Programm nutzt.

Unser Demo Programm ist sehr einfach aufgebaut, wir haben nur vier Klassen:

Bild 2: Der Aufbau des Projekts

Bevor ihr euch wundert, das Programm soll uns später die Quadratwurzel einer Zahl berechnen.

An Bild 1 könnt ihr erkennen, dass die Controller Klasse die wichtigste ist, da sie eigentlich das Ganze Programm steuert und dafür sorgt, dass die View und das Model sich verständigen können. Deswegen muss die Controller Klasse die anderen Klassen kennen, die anderen brauchen sich gegenseitig aber nicht!


public class Main {

    static WurzelController controller;

    /**
     * Diese Klasse wird nur dazu benutzt alle nötigen
     * Komponenten zu Initialisieren und die erste
     * View anzuzeigen
     */
    public static void main(String [] args){
        controller = new WurzelController();

        controller.showView();
    }
}

Die Klasse Main macht hier auch nichts anderes, als die anderen Klassen zu instanzieren und uns die View anzuzeigen.

An den Konstruktoren kann man auch die Abhängigkeiten erkennen. Eigentlich macht man der View auch das Model bekannt (siehe Bild 1), aber da unser Beispiel zu simpel ist können wir uns diese Bekanntschaft sparen.

Das Model ist ebenfalls sehr einfach aufgebaut, es enthält die nötigen Rechenoperationen die für unser Programm wichtig sind und bietet öffentliche Methoden mit denen der  Controller die Werte abfragen kann


/**
 * Das Model ist komplett unabhängig von den anderen
 * Klassen und weiß nicht was um ihn herum geschieht.
 * Es ist völlig egal ob man dieses Model aus einem
 * Fenster oder einer Konsolen Eingabe verwendet -
 * beiden würde funktionieren.
 */</pre>
<pre>public class WurzelModel {

    long _value;

    public WurzelModel(){
        zurückSetzen();
    }

    public void zurückSetzen(){
        this._value = 0;
    }

    public void berechneWurzel(long wert){
        this._value =  (wert * wert);
    }

    public long getWurzel(){
        return this._value;
    }
}

Wichtig hierbei ist, dass diese Klasse keine Beziehungen zu den anderen Klassen hat! Man muss sie so aufbauen, dass man sie ohne eine Änderung in einem anderen Projekt benutzen könnte.

Unsere View ist ganz typisch aufgebaut. Es werden die Steuerelemente initialisiert und auf der JForm plaziert, desweiteren muss die View Methoden bieten, mit denen man die ActionListener für die auf dem Formular liegenden Steuerelemente setzen kann. Über diese wird unser Controller mit der View kommunizieren. Dazu kommen noch die getter und setter Methoden für die Textfelder, diese werden benötigt, damit man aus dem Controller Zugriff darauf hat!


/** Die View-Klasse diese Enthält nur die Präsentation
 * hier sollte man keinerlei Programmlogik finden
 * alle Berechnungen und Reaktionen auf Benutzeraktionen
 * sollten allesammt im Controller stehen
 */</pre>
<pre>public class WurzelView extends JFrame{

    private JLabel lbl1 = new JLabel("Eingabe: ");
    private JTextField txtEingabe = new JTextField(3);
    private JButton cmdCalc = new JButton("Wurzen Berechnen >");
    private JTextField txtErg = new JTextField(5);
    private JButton cmdClear = new JButton("Zurüclsetzen");

    public WurzelView(){
        super("Wurzel Berechnen");

        initForm();
    }

    /**
     * Die JForm initialisieren und alle Steuerelemente
     * darauf positionieren
     */
    private void initForm(){
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(new FlowLayout());
        this.setBounds(200, 200, 500, 100);

        this.add(lbl1);
        this.add(txtEingabe);
        this.add(cmdCalc);
        this.add(txtErg);
        this.add(cmdClear);

    }

    public void resetView(){
        this.txtEingabe.setText("");
        this.txtErg.setText("");
    }

    public String getEingabe(){
        return this.txtEingabe.getText();
    }

    public void setErgebnis(String erg){
        this.txtErg.setText(erg);
    }

    /**
     * Funktionen bereitstellen, mit denen man später aus
     * dem Controller die nötigen Listener hinzufügen kann
     */
    public void setWurzelBerechnenListener(ActionListener l){
        this.cmdCalc.addActionListener(l);
    }

    public void setResetFormListener(ActionListener l){
        this.cmdClear.addActionListener(l);
    }
}

Bleibt nur noch der Controller. Dieser ist auch sehr simpel aufgebaut:


/**
 * Der Controller muss beide die View und das Model kennen
 * da dieser für die Kommunikation zwischen den Beiden sorgt
 */
public class WurzelController {

    private WurzelView _view;
    private WurzelModel _model;

    public WurzelController(){
        this._model = new WurzelModel();
        this._view = new WurzelView;

        addListener();
    }</pre>
<pre>
    public void showView(){
        this._view.setVisible(true);
    }</pre>
<pre>
/**
     * Die Listener, die wir aus den Internen Klassen generieren
     * werden der View bekannt gemacht, sodass diese mit
     * uns (dem Controller) kommunizieren kann
     */
    private void addListener(){
        this._view.setWurzelBerechnenListener(new WurzelBerechnenListener());
        this._view.setResetFormListener(new ResetFormListener());
    }

    /**
     * Inneren Listener Klassen implementieren das Interface ActionListener
     *
     * 1: Hier wird erst der eingegebene Wert aus der View geholt
     * 2: Der Wert wird dem Model übergeben und die Wurzel berechnet
     * 3: Die berechnete Wurzel wird aus dem Model geladen und
     * 4: Wieder der View zum Darstellen übergeben
     *
     * ACHTUNG: Fehlerprüfung muss noch implementeirt werden
     */
    class WurzelBerechnenListener implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            long wert = Long.valueOf(_view.getEingabe());
            _model.berechneWurzel(wert);
            _view.setErgebnis(String.valueOf(_model.getWurzel()));
        }
    }

    /**
     * Hier wird dem View und dem Model gesagt ihre gespeicherten
     * Werte zu löschen.
     */
    class ResetFormListener implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            _view.resetView();
            _model.zurückSetzen();
        }
    }
}

Wie man sieht werden hier nur die dem Konstruktor übergebenen Referenzen auf die View und das Model gespeichert und dann die Erzeugten ActionListener der View übergeben.

Die ActionListener sind interne Klassen die nun automatisch von unserer View aufgerufen werden, wenn der Benutzer einen der Buttons klickt! Praktisch oder?

Wenn man die WurzelBerechnenListener Klasse anschaut kann man auch die Typische Kommunikation zwischen den Schichten sehen. Die View lößt ein Event aus, für dessen Abhandlung der Controller verantwortlich ist. Nun holt der Controller sich alle nötigen Daten aus der View (in diesem Fall ist es nur ein Wert, die Eingabe) und lässt das Model das Quadrat berechnen. Schließlich holt der Controller sich die berechneten Daten zurück und lässt die View diese anzeigen.

Im Grunde gar nicht kompliziert oder? So sieht das Fenster zur Laufzeit aus:

Bild 3: Das Programm zur Laufzeit

Wenn man ein neues Projekt anfängt sollte man sich natürlich erst einmal genau überlegen ob man die MVC Architektur benutzt, denn diese ist bei kleinen Projekten mit einem höheren Aufwand verbunden. Wenn man aber weiß, dass in absehbarer Zeit an dem Programm weitere Änderungen oder Erweiterungen vorgenommen werden sollte man MVC in Betracht ziehen!

ACHTUNG: Habe eben erst gemerkt, dass ich überall "Wurzel" geschrieben habe, das ergibt natürlich gar keinen Sinn! :D oh mann, muss wohl sehr verwirrt gewesen sein! Quadrat müsste es natürlich heißen. :) Keine Lust das nun alles nochmal zu ändern!

Die Sourcen könnt ihr hier laden: Download

Get Adobe Flash playerPlugin by wpburn.com wordpress themes