Java Applet Tutorial: Applet erstellen, einbinden und mit Javascript ansprechen

Wenn ich an Java Applets denke, ist damit immer ein verstaubtes Web 1.0 – Bild assoziiert, gepaart mit animierten gifs und anderen Schandtaten. Heutzutage haben die Applets ihre Nische gefunden und werden vor allem da benutzt, wo Javascript noch zu langsam / umständlich ist, bspw. mathematische Zwecke. Wir widmen uns dem Thema mal ganz von vorne. Selbst wenn keine Javakenntnisse bestehen, soll man also noch gut folgen können. Soweit der Plan.

Was am Ende bei rumkommen wird

Wir werden ein Java-Applet bauen, das den Inhalt von Ordnern auf dem PC des Users in einer Liste anzeigt. Auch das Löschen von Dateien ist möglich. Das Java-Applet wird von außen per Javascript angesteuert, um in andere Verzeichnisse wechseln zu können. Wäre zwar nicht nötig, aber um zu zeigen dass – und vor allem wie – sowas geht, tun wir es.

So, angeschnallt!

Applet anlegen

Wir werden Netbeans verwenden. In Eclipse sollte es aber simultan gehen. Ich setze ein für die Java-Entwicklung konfiguriertes Netbeans voraus (incl. JDK). Wird hier in den ersten 2 Schritten kurz angeschnitten. Okay.

  1. Neues Projekt -> „Java Application“
    Neues Projekt anlegen

    Neues Projekt anlegen

  2. Das Projekt nennen wir fileapplet. Die Main-Klasse heisst com.dav.FileApplet. Bedeutet: Es wird im Package com.dav eine Klasse mit dem Name FileApplet.java angelegt. That’s what we want.
    Projekt anlegen

    Projekt anlegen

  3. Jetzt putzen wir die automatisch angelegte main erstmal weg, die brauchen wir nicht. Die Datei sollte nun so aussehen:
    package com.dav;  public class FileApplet  {  } 

    Gut, damit lässt sich arbeiten.
  4. Erster GUI-Entwurf

    Unsere Klasse erbt von JApplet und bekommt einen Konstruktor (der in Java genauso heißt wie die Klasse). Weiterhin kloppen wir alle import’s dazu, die wir im kompletten Projekt brauchen werden, dass wir Ruhe haben.

    package com.dav;  import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.File; import java.security.*; import java.util.Vector;  public class FileApplet extends JApplet {     public FileApplet()     {             } } 

    Wie ihr im Video gesehen habt: Wir haben ein Label ganz oben, welches den aktuellen Pfad beinhaltet. Darunter sitzt die Liste als Dateinamensanzeige. Und ganz unten ist letztendlich der Löschen-Button. Weiterhin brauchen wir eine Variable, die den aktuellen Dateipfad aufnimmt.

    Damit sieht unsere Klasse jetzt so aus:

    public class FileApplet extends JApplet {     private String path;      private JLabel lblPath;     private JList lstFiles;     private JButton btnDelete;      public FileApplet()     {         //assign LayoutManager to panel         JPanel contentPane = (JPanel)this.getContentPane();         contentPane.setLayout(new BorderLayout(10, 10));          //add Label for Path on Top         lblPath = new JLabel("Hier steht der Pfad später");         contentPane.add(lblPath, BorderLayout.NORTH);          //add List with all files of path to center         lstFiles = new JList();         contentPane.add(new JScrollPane(lstFiles), BorderLayout.CENTER);          //add button to bottom         btnDelete = new JButton("Löschen");         contentPane.add(btnDelete, BorderLayout.SOUTH);     } } 

    Hier sehe ich jetzt nichts groß erklärenswertes. Ist halt der Java-way of life, um Elemente zu erzeugen und auf das Panel zu packen. Es kommt ein LayoutManager (BorderLayout) zum Einsatz, der hier genauer erklärt wird. Tut aber eigentlich nix zur Sache und ist bloß zur ordentlichen Strukturierung der Elemente da.

    Wir wollen Ergebnisse!

    So, das reicht erstmal. Die UI ist nun vollständig und wir schauen uns das mal an. Dazu builden wir das Projekt und navigieren zur entstandenen fileapplet.jar ins dist/ – Verzeichnis unseres Projektordners – zumindest packt Netbeans die .jar da hin. Alleine ist sie aber nicht lauffähig, deswegen erstellen wir eine index.html im selben Ordner, die wie folgt aufgebaut ist:

    <!DOCTYPE html> <html>  <head>  	<meta charset="utf8" /> 	<title>File Javaapplet</title> </head>  <body>  	<object type="application/x-java-applet;version=1.4.1" name="jsap" id="jsap" width="400" height="600"> 		<param name="archive" value="fileapplet.jar">  		<param name="code" value="com.dav.FileApplet"> 		<param name="mayscript" value="yes">  		<param name="scriptable" value="true">  	</object>  </body>  </html> 

    Oracle selbst empfiehlt zwar einen anderen Weg der Einbindung. Allerdings funktioniert die object-Variante in allen relevanten Browsern und macht uns dehalb glücklich – ohne diese miese Verschachtelung von verschiedenen Techniken, die uns da Oracle aufschwätzen will. Eine andere Art der Einbindung ist die per <applet>-Tag, welcher allerdings deprecated ist.

    Sollte also eigentlich alles klar sein, was den Code betrifft. Wenn der Benutzer mit einer Java-Version < 1.41 daherkommt, wird er zum Update gebeten. Wir haben als Parameter archive unser .jar drinne, geben beim Parameter code unser Package samt Filename (ohne Extension) ein. mayscript und scriptable sind für später. Wir wollen das Applet ja irgendwann mal mit Javascript beackern. Wichtig ist die Angabe von Breite und Höhe in genau dieser Notation, also nix mit CSS oder so. Sonst quittiert Firefox den Dienst und zeigt das Applet nicht an.

    So, war doch garnicht so schwer. Da erstrahlt das Applet im Browser! Allerdings geht halt noch nix, weil wir bisher noch keine Funktionalität implementiert haben.

    Bring on the Code!

    Okay, bauen wir uns erstmal eine Funktion, die einen Vector mit allen Dateinamen im aktuellen Verzeichnis zurückgibt:

    //return a vector containing the filenames in path public Vector<String> getFileList() {     Vector<String> fileNames = new Vector<String>();      try     {         File dir = new File(path);         File[] fileList = dir.listFiles();         for (File f : fileList)         {             if (!f.isDirectory())                 fileNames.add(f.getName());         }     }     //silence exception in case of invalid path     catch (Exception ex) {}        return fileNames; } 

    Weiterhin brauchen wir auch noch was, um eine Datei zu löschen:

    public boolean deleteFile(String name) {     File f = new File(name);      if (!f.exists() || !f.canWrite() || f.isDirectory())         return false;      return f.delete(); } 

    Geht ja gut voran. Weiter gehts mit der Methode, die dann später von Javascript aus angesteuert wird: setPath.

    public void setPath(String newpath) {     newpath = newpath.replace("\\", "/");      if (!newpath.endsWith("/"))         newpath += "/";      this.path = newpath;     lblPath.setText(this.path);      AccessController.doPrivileged(new PrivilegedAction()     {         public Object run()         {             lstFiles.setListData(getFileList());             return null;         }     }); } 

    Hier kümmern wir uns drum, dass alle Backslashes zu Forward-Slashes gemacht werden und setzen den Pfad als Text unseres Labels. Außerdem kümmern wir sich noch drum (jaja, kein guter Stil…), dass die Dateiliste neu gefüllt wird. Und zwar mit den Dateien im nun aktuellen Verzeichnis. Das abgedrehte Konstrukt um die wirklich relevante Zeile lstFiles.setListData(getFileList()); ist nötig, da das Applet sonst keinen Zugriff auf das Dateisystem des Benutzers hätte. Hirn ausschalten und einfach so übernehmen.

    Letztendlich sind wir damit schon so gut wie fertig. Alles was noch gebraucht wird, ist ein EventHandler für den Löschen-Knopf. Wenn also auf den Löschen-Button gedrückt wird, sollen alle markierten Dateien in der Liste entfernt werden. Dazu ergänzen wir im Konstruktor zum Button:

    btnDelete.addActionListener(new ActionListener() {     public void actionPerformed(ActionEvent e)     {         Object selectedValues[] = lstFiles.getSelectedValues();         for (int i = 0; i < selectedValues.length; i++)         {             deleteFile(path + selectedValues[i]);         }          lstFiles.setListData(getFileList());     } }); 

    Paradoxerweise ist das Löschen von Dateien ohne diesen AccessController – Aufbau möglich (Vermutung: Da die Lösch-Funktion nicht per Javascript getriggert wird, gelten dafür andere Gesetze). Damit sind wir mit dem Java-Teil fertig – hier das Endprodukt, unsere komplette Klasse:

    package com.dav;  import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.File; import java.security.*; import java.util.Vector;  public class FileApplet extends JApplet {     private String path;      private JLabel lblPath;     private JList lstFiles;     private JButton btnDelete;      public FileApplet()     {         //assign LayoutManager to panel         JPanel contentPane = (JPanel)this.getContentPane();         contentPane.setLayout(new BorderLayout(10, 10));          //add Label for Path on Top         lblPath = new JLabel();         contentPane.add(lblPath, BorderLayout.NORTH);          //add List with all files of path to center         lstFiles = new JList();         contentPane.add(new JScrollPane(lstFiles), BorderLayout.CENTER);          btnDelete = new JButton("Löschen");         //add listener to button -> delete files         btnDelete.addActionListener(new ActionListener()         {             public void actionPerformed(ActionEvent e)             {                 Object selectedValues[] = lstFiles.getSelectedValues();                 for (int i = 0; i < selectedValues.length; i++)                 {                     deleteFile(path + selectedValues[i]);                 }                  lstFiles.setListData(getFileList());             }         });         //add button to bottom         contentPane.add(btnDelete, BorderLayout.SOUTH);          setPath("C:/");     }      public void setPath(String newpath)     {         newpath = newpath.replace("\\", "/");          if (!newpath.endsWith("/"))             newpath += "/";          this.path = newpath;         lblPath.setText(this.path);          AccessController.doPrivileged(new PrivilegedAction()         {             public Object run()             {                 lstFiles.setListData(getFileList());                 return null;             }         });     }      //return a vector containing the filenames in path     public Vector<String> getFileList()     {         Vector<String> fileNames = new Vector<String>();          try         {             File dir = new File(path);             File[] fileList = dir.listFiles();             for (File f : fileList)             {                 if (!f.isDirectory())                     fileNames.add(f.getName());             }         }         catch (Exception ex) {}          return fileNames;     }      public boolean deleteFile(String name)     {         File f = new File(name);          if (!f.exists() || !f.canWrite() || f.isDirectory())             return false;          return f.delete();     } } 

    Fertig! … oder?

    Gut, dann erneut „build“. Jetzt ergänzen wir die HTML-Datei noch um den notwendigen Javascript-Code, zum wechseln des Pfades im Applet. Dazu kommt in den <body>:

    Pfad: <input type="text" id="filepath" value="C:/" /> <input type="button" id="ok" value="ok" /> 

    Und im <head> ergänzen wir:

    <script src="http://code.jquery.com/jquery-1.5.min.js"></script> <script> $(document).ready(function() { 	$("#ok").click(function() 	{ 		var filepath = $("#filepath").val(); 		document.jsap.setPath(filepath); 	}); }); </script> 

    Hier nun also der gesamte HTML-Code:

    <!DOCTYPE html> <html>  <head>  	<meta charset="utf8" /> 	<title>File Javaapplet</title> 	<script src="http://code.jquery.com/jquery-1.5.min.js"></script> 	<script> 	$(document).ready(function() 	{ 		$("#ok").click(function() 		{ 			var filepath = $("#filepath").val(); 			document.jsap.setPath(filepath); 		}); 	}); 	</script> </head>  <body>  	<object type="application/x-java-applet;version=1.4.1" name="jsap" id="jsap" width="400" height="600"> 		<param name="archive" value="fileapplet.jar">  		<param name="code" value="com.dav.FileApplet"> 		<param name="mayscript" value="yes">  		<param name="scriptable" value="true">  	</object>   	<p> 		Pfad: <input type="text" id="filepath" value="C:/" /> 		<input type="button" id="ok" value="ok" /> 	</p> 	 </body>  </html> 

    Verblüffend einfach, wie die Javascript-Brücke zum Applet hergestellt wird, oder? Einfach document.AppletName.Funktionsaufruf. Das wars schon. Ehrlich.

    Okay, versuchen wirs mal. … Hm … Ernüchterung macht sich breit. Der Pfad im Label wechselt zwar, wie gewünscht – bloß warum klappt die Dateianzeige nicht? Die Antwortet lautet: Signatur.

    Das Applet signieren

    „Unkritische“ Java-Applets werden (natürlich abhängig von der Sicherheitseinstellung im Browser) straight-away ausgeführt. Ohne Benachrichtigung. Sobald wir aber kritische Bereiche anfassen (was wir mit dem Auflisten und Löschen von Dateien definitiv tuen), müssen wir unser Applet signieren. Dazu gibt es zum einen fest in den Browsern verdrahtete Zertifizierungsstellen, bei denen man sich gegen Gebühr ein Zertifikat holen kann. Allerdings kann man auch den kostenlosen Weg wählen und das Applet selbst signieren. Das ist, was wir tun werden.

    • Dazu eine Dos-Box aufgemacht (Start -> Ausführen -> cmd) und zum Pfad eurer JDK-Installation ins /bin – Verzeichnis gewechselt. Bei mir: D:\programme\Java\jdk1.6.0_22\bin.
    • Jetzt kopiert ihre eure fileapplet.jar ins /bin-Verzeichnis hinein und führt aus:
      keytool.exe -genkey -keystore davidsKeystore -alias david
      Ihr dürft euren Vorname gerne einsetzen ;). Nach dem Absenden des Befehls werden ein paar Angaben von euch gefordert, u.a. ein Passwort.
    • Der nächste Befehl:
      keytool -selfcert -alias david -keystore davidsKeystore
      Wieder mit dem Passwort bestätigen.
    • Und der letzte Befehl ist jetzt das eigentliche signieren:
      jarsigner -keystore davidsKeystore fileapplet.jar david

    Damit ist euer Applet selfsigned. Jetzt muss erstmal die java.exe per Taskmanager gekillt werden, da eure alte, unsignierte .jar noch am laufen ist. Jetzt kopiert ihr die fileapplet.jar aus dem /bin Ordner nun wieder nach /dist zurück, oder wo eben euer Projekt lebt. Nach dem Start der index.html erscheint nun hoffentlich:

    Java Warnung

    Java Warnung

    Nach dem Klick auf „Ausführen“ sollte nun endlich alles so klappen, wie gewünscht. Checkt ihr „Inhalten dieses Urhebers immer vertrauen“, werdet ihr beim nächsten mal nicht mehr genervt. Fertig.

    Abrechnung

    Ich habe das Entwickeln von Applets als sehr schmerzvoll empfunden – unzählige Male musste ich java per Taskamager killen, den Browser neu starten und temporäre Dateien leeren, um sicher zu sein, dass ich auch wirklich das aktuelle Ergebnis vor mir habe. Hinweisen sollte man noch auf die java-Konsole, die sich per Rechtsklick auf das java-Symbol im Systray starten lässt. Dort werden Exceptions und Fehler untergebracht.

    Java Konsole

    Java Konsole

    Alles zusammen

    Alle Dateien zusammen herunterladen. Disclaimer: Ich bin kein ausgewiesener Java-Experte und hatte mit Applets bisher nicht sonderlich viel am Hut. Nicht an Kritik zögern, wenn ihr was besser wisst.

    Bonus: Ein „vertrauenswürdiges“ Zertifikat wieder entfernen

    Jetzt wo bekannt ist, dass nur ein flüchtiger Klick auf „Ausführen“ genug sein kann um von einem Applet die komplette Platte geputzt zu bekommen, wollt ihr sicher mal nachschauen, wem ihr in der Vergangenheit so alles euer „Vertrauen“ ausgeprochen habt ;). In der Systemsteuerung unter „Programme“ den Eintrag Java wählen. Im Tab „Sicherheit“ bei „Zertifikate“ lassen sich Zertifikate wieder entfernen.

    vertrauenswürdige Zertifikate

    vertrauenswürdige Zertifikate

    Das Entfernen des Zertifikats hat aber nur dann Wirkung, wenn die temporären Dateien von Java geleert werden. Solang da noch eine als vertrauenswürdig klassifizierte .jar rumliegt, greift das Entfernen des Zertifikats noch nicht.

    temporäre Dateien

    temporäre Dateien

    Im Reiter „Allgemein“ unter bei „Temporäre Internet-Dateien“ auf „Anzeigen“ und da aufräumen. Auch der Browser cached eventuell selbst noch.

    Further Reading


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0